花指令全面总结
字数 1153 2025-08-19 12:42:22
花指令全面总结与实战教学
一、花指令概念与原理
1.1 花指令定义
花指令是企图隐藏不想被逆向工程的代码块的一种方法,通过在真实代码中插入垃圾代码同时保证程序正确执行,使程序无法良好反编译,达到混淆视听的效果。主要用于加大静态分析的难度。
1.2 反编译器工作原理
反编译器从exe入口AddressOfEntryPoint开始,依序扫描字节码并转换为汇编:
- 线性扫描:从入口开始依次解析每条指令,遇到分支指令不会递归进入分支
- 递归下降:遇到分支指令时会递归进入分支进行反汇编
X86指令集长度不固定(1-5字节不等),通过巧妙构造可以引导反汇编引擎解析错误指令,扰乱指令长度。
1.3 关键机器码
0xE8 CALL 后跟4字节地址
0xE9 JMP 后跟4字节偏移
0xEB JMP 后跟2字节偏移
0xFF15 CALL 后跟4字节存放地址的地址
0xFF25 JMP 后跟4字节存放地址的地址
0x68 PUSH 后跟4字节入栈
0x6A PUSH 后跟1字节入栈
二、花指令编写原则
2.1 基本原则
- 保持堆栈平衡
- 构造不影响程序逻辑的内联汇编代码
- 嵌入jmp/call/ret等机器码指令干扰反汇编
2.2 常用指令含义
push ebp ; 把基址指针寄存器压入堆栈
pop ebp ; 把基址指针寄存器弹出堆栈
nop ; 不执行
add esp,1 ; 指针寄存器加1
sub esp,-1 ; 指针寄存器加1
jmp 入口地址 ; 跳到程序入口地址
retn ; 反回到入口地址
xor eax,eax ; 寄存器EAX清0
三、花指令分类与实现
3.1 基础花指令
3.1.1 简单jmp花指令
__asm {
jmp Label1
db thunkcode1; 垃圾数据
Label1:
}
3.1.2 多节形式与多层乱序
JMP Label1
Db thunkcode1
Label1:
...
JMP Label2
Db thunkcode2
Label2:
...
3.2 进阶花指令
3.2.1 互补条件代替jmp
__asm {
Jz Label
Jnz Label
Db thunkcode; 垃圾数据
Label:
}
3.2.2 跳转指令构造
__asm {
push ebx
xor ebx, ebx
test ebx, ebx
jnz LABEL7
jz LABEL8
LABEL7:
_emit 0xC7
LABEL8:
pop ebx
}
3.2.3 call&ret构造
__asm {
call LABEL9
_emit 0x83
LABEL9:
add dword ptr ss:[esp], 8
ret
__emit 0xF3
}
3.3 自定义花指令
3.3.1 替换ret指令
__asm {
call LABEL9
_emit 0xE8
_emit 0x01
_emit 0x00
_emit 0x00
_emit 0x00
LABEL9:
push eax
push ebx
lea eax, dword ptr ds:[ebp-0x0]
add dword ptr ss:[eax-0x50], 26
pop eax
pop ebx
pop eax
jmp eax
_emit 0xE8
_emit 0x03
_emit 0x00
_emit 0x00
_emit 0x00
mov eax,dword ptr ss:[esp-8]
}
3.3.2 控制标志寄存器跳转
通过精通标志寄存器,使用对应跳转指令构造永恒跳转。
3.3.3 利用函数返回确定值
// 利用MessageBox实现花指令
MessageBox(NULL, "加花指令", "标题", 0);
// 调用失败返回0,成功返回非零值,可用于构造条件跳转
四、花指令分析与去除
4.1 手动去花步骤
- 识别花指令模式(如互补跳转jz/jnz)
- 将干扰字节转换为数据(IDA中按D键)
- 将花指令部分nop掉(填充0x90)
- 重新反汇编(按C键)
- 重新声明函数(按P键)
4.2 IDC脚本自动化去花
4.2.1 基础脚本示例
#include <idc.idc>
static main() {
auto StartVa, StopVa, Size, i;
StartVa = 0x00411960;
StopVa = 0x00411A27;
Size = StopVa - StartVa;
for (i = 0; i < Size; i++) {
if (Byte(StartVa) == 0x74) { // jz指令
if (Byte(StartVa + 1) == 0x03) { // 特定模式
PatchByte(StartVa, 0x90); // nop填充
MakeCode(StartVa);
StartVa++;
Message("Find FakeJmp Opcode!!\n");
continue;
}
}
StartVa++;
}
Message("Clear FakeJmp Opcode Ok\n");
}
4.2.2 高级匹配脚本
#include <idc.idc>
static matchBytes(StartAddr, Match) {
auto Len, i, PatSub, SrcSub;
Len = strlen(Match);
while (i < Len) {
PatSub = substr(Match, i, i+1);
SrcSub = form("%02X", Byte(StartAddr));
SrcSub = substr(SrcSub, i % 2, (i % 2) + 1);
if (PatSub != SrcSub) return 0;
if (i % 2 == 1) StartAddr++;
i++;
}
return 1;
}
static main() {
auto Addr, Start, End, Condition, i;
Start = 0x411960; // 起始地址
End = 0x11A3B; // 结束地址
Condition = "5033C085C07502"; // 匹配模式
for (Addr = Start; Addr < End; Addr++) {
if (matchBytes(Addr, Condition)) {
Message("Find FakeJmp Opcode!!\n");
for (i = 1; Byte(Addr + i) != 0x58; i++) { // 直到pop eax
PatchByte(Addr + i, 0x90); // nop填充
MakeCode(Addr + i);
}
}
}
AnalyzeArea(Start, End);
Message("Clear FakeJmp Opcode Ok");
}
五、花指令实战案例
5.1 简单花指令实例
#include <stdio.h>
#include <windows.h>
void func1() {
__asm {
lea eax, lab1
jmp eax
_emit 0x90
};
lab1:
printf("func1\n");
}
void func2() {
__asm {
cmp eax, ecx
jnz lab1
jz lab1
_emit 0xB8
};
lab1:
printf("func2\n");
}
int main() {
func1();
func2();
return 0;
}
5.2 看雪.TSRC 2017CTF秋季赛第二题分析
- 使用不安全的scanf函数,缓冲区仅0xCh长,可溢出覆盖返回地址
- 关键验证逻辑加花混淆,IDA无法静态分析
- 发现0x00413131处可疑数据块,按C键转换为代码
- 使用OD跟踪执行流程,识别有效指令
5.3 TEA加密算法加花实例
#include <stdio.h>
#include <stdint.h>
void encrypt(uint32_t* v, uint32_t* k) {
uint32_t v0 = v[0], v1 = v[1], sum = 0, i;
uint32_t delta = 0x9e3779b9;
uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
for (i = 0; i < 32; i++) {
sum += delta;
v0 += ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
v1 += ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
}
v[0] = v0; v[1] = v1;
}
int main() {
int a = 1;
uint32_t flag[] = {1234, 5678};
uint32_t key[] = {9, 9, 9, 9};
__asm {
_emit 075h
_emit 2h
_emit 0E9h
_emit 0EDh
}
encrypt(flag, key);
printf("%d,%d", flag[0], flag[1]);
return 0;
}
六、高级技巧与注意事项
6.1 裸函数中的花指令
裸函数需要自行处理栈帧,是插入花指令的理想位置:
void __declspec(naked)__cdecl cnuF(int* a) {
__asm {
push ebp
mov ebp, esp
sub esp, 0x40
push ebx
push esi
push edi
mov eax, 0xCCCCCCCC
mov ecx, 0x10
lea edi, [ebp-0x40]
rep stosd
}
*a = 1;
_asm {
call LABEL9;
_emit 0xE8;
_emit 0x01;
_emit 0x00;
_emit 0x00;
_emit 0x00;
LABEL9:
push eax;
push ebx;
lea eax, [ebp-0x0]
add [eax-0x50], 26;
pop eax;
pop ebx;
pop eax;
jmp eax;
__emit 0xE8;
_emit 0x03;
_emit 0x00;
_emit 0x00;
_emit 0x00;
mov eax, [esp-8];
}
__asm {
pop edi
pop esi
pop ebx
mov esp, ebp
pop ebp
ret
}
}
6.2 花指令另类应用
将花指令中的垃圾数据替换为特征码,用于SMC自解码定位:
__asm {
Jz Label
Jnz Label
_emit 'h'
_emit 'E'
_emit 'l'
_emit 'L'
_emit 'e'
_emit 'w'
_emit 'o'
_emit 'R'
_emit 'l'
_emit 'D'
Label:
}
七、总结与防御建议
7.1 花指令关键点
- 利用反编译器的线性扫描特性
- 通过跳转和垃圾数据干扰反汇编流程
- 保持栈平衡和程序正常执行
- 多种形式组合增加分析难度
7.2 防御建议
- 动态调试与静态分析结合
- 编写自动化脚本识别常见模式
- 注意栈指针变化和异常跳转
- 关注互补条件跳转等可疑模式
通过系统学习和实践,掌握花指令的原理与对抗方法,能够有效提升逆向工程能力。