简介
花指令(junk code)是一种专门用来迷惑反编译器的指令片段,这些指令片段不会影响程序的原有功能,但会使得反汇编器的结果出现偏差,从而使破解者分析失败。比较经典的花指令技巧有利用 jmp
、call
、ret
指令改变执行流,从而使得反汇编器解析出与运行时不相符的错误代码
铺垫
反汇编算法
线性扫描
从程序入口点开始遍历一整个代码端,逐行对命令进行反汇编
缺点:在冯诺依曼体系结构下,无法区分数据与代码,从而导致将数据解释为指令操作码
递归下降
从程序入口点开始读取机器码进行反汇编,通过程序控制流来确定汇编的下一条指令,遇到无条件跳转直接跳转,遇到条件跳转则从两个命令执行分支处进行解析(优先解析顺序执行分支)
常用花指令硬编码
硬编码:汇编指令对应的机器码,通常以16进制表示
1
2
3
4
5
6
7
8
9
10
11
|
addr: 汇编指令跳转的目标地址
immedn: 目的地址与当前指令下一条指令地址的距离, 是一个偏移值
nop ----> 90 //1字节指令,空指令
call addr ----> E8 immed32 //5字节指令,immed32为4字节
call far ptr addr ----> 9A immed48 //7字节指令,immed48为6字节
jmp short near ptr addr ----> EB immed8 //2字节指令,immed8为1字节
jmp near ptr addr ----> E9 immed32 //5字节指令
jmp far ptr addr ----> EA immed48 //7字节指令
loop near ptr addr ----> E2 immed8 //2字节指令
ret ----> C2 n16 //3字节指令 等价于pop n和pop eip
retn ----> C3 //1字节指令 等价于pop eip
|
两大分类
可执行花指令
可执行花指令指的是花指令代码在程序的正常执行过程中会被执行,但执行这些代码没有任何意义,执行前后不改变任何寄存器的值,同时这部分代码也会被反汇编器正常识别,其目的依然是加大静态分析的难度,让你难以识别代码的真正意图,有时这种花指令可以破坏反编译的分析,使得栈指针在反编译引擎中出现异常
压栈后恢复栈地址
1
2
3
4
|
_asm {
push eax;
add esp, 4;
}
|
call&ret构造
1
2
3
4
5
6
7
8
9
10
|
_asm {
call label
label:
add dword ptr ss : [esp], 7;//注意变长指令
ret
}
real_code
//特点: 可执行花指令,最终会跳转至real_code标签处执行
//call指令本质是push nextcode和jmp desAddr,ret指令本质是jmp [esp]和sub esp,4
//在ret和real_code中间可以任意插入花指令,但要根据插入字节数添加对应add值
|
所以也可以通过push和jmp配合实现
1
2
3
4
5
6
7
8
9
10
11
12
13
|
_asm {
push eax
push ecx
jmp label_1
label_2:
mov eax,[esp]
add [esp],8
jmp eax
label_1:
call label_2
pop eax
}
printf("Hello world!\n");
|
通过压栈、退栈和跳转混淆代码,IDA将退栈指令识别为破坏堆栈平衡而报错,识别这类花指令后,将跳转和无效压栈等操作指令全部NOP掉即可
混淆特征码
如下一些指令功能可以用替代指令完成,目的即增加反汇编分析的复杂程度:
1
2
3
4
|
mov op1,op2 ----> push op2 / pop op1
jmp label ----> push label / ret
call label ----> push label_next_instruction / push label / ret
push op ----> sub esp,4 / mov [esp],op
|
永真条件跳转
可见后文
不可执行花指令
不可执行花指令就是在程序执行过程中不会被执行,但利用静态分析算法的缺陷使反汇编分析执行会出错的垃圾数据,就会导致解析错误,这时我们需要跳过这些花指令才能保证程序正常运行
简单e8跳转
简单的花指令0xe8是跳转指令,可以对线性扫描算法进行干扰,但是递归扫描算法可以正常分析
1
2
3
4
5
|
_asm {
jmp label1
_emit 0xe8
label1:
}
|
OD还是能被骗过去,但是因为ida采用的是递归扫描的办法所以能够正常识别
jz和jnz条件跳转
利用jz和jnz的互补条件跳转指令来代替jmp,两个跳转一个指向无效数据,一个指向正常数据来干扰递归扫描算法即ida不能正常识别
1
2
3
4
5
6
7
8
|
_asm {
jz lable2
jnz lable2
_emit 0xe8
lable2:
}
//特点: 不可执行花指令,无论是jz还是jnz指令,最终都跳转至label
//在jnz label和label之间可以插入任意花指令
|
将这类花指令NOP即可,也可以将条件跳转指令换成无条件跳转jmp,此时ida反编译就不会报错,原理可分析递归下降算法
永真条件跳转
通过设置永真或者永假的,导致程序一定会执行,由于ida反汇编会优先反汇编接下去的部分(false分支),也可以调用某些函数会返回确定值,来达到构造永真或永假条件
1
2
3
4
5
6
7
8
9
10
11
12
13
|
_asm{
push ebx
xor ebx,ebx
test ebx,ebx
jnz label1
jz label2
label1:
_emit junkcode
label2:
pop ebx //需要恢复ebx寄存器
}
//特点: 可执行花指令,有2条跳转分支,但只有一条分支为永真会被执行,另一条永假不执行
//在虚假分支处可以插入任意花指令
|
破坏堆栈平衡
汇编中函数如果有参数或局部变量,在调用前会对堆栈进行保护 ,在返回前要还原函数调用前的堆栈,这一过程程序在编译时会自动加上,如果反编译器检测到指令破坏了堆栈平衡,即函数返回时与调用时堆栈状态发生了变化,就会报错,可以利用这一点构造破坏堆栈平衡的花指令
1
2
3
4
5
6
7
8
|
_asm {
test eax,0 // 构造必然条件实现跳转,绕过破坏堆栈平衡的指令
jz label
add esp,0x1
label:
}
printf("Hello World!\n");
//不可执行花指令
|
这类破环堆栈平衡的指令实际不会执行,但是由于IDA在反汇编分析时会分别从两个条件跳转处开始分析,因此判定堆栈不平衡导致反汇编失败,解决方法是NOP掉破坏堆栈平衡的指令
参考
花指令总结
逆向基础花指令
恶意代码分析