原理

花指令是,由设计者特别构思,希望使反汇编的时候出错,让破解者无法清楚正确地反汇编程序的内容,迷失方向。经典的是,目标位置是另一条指令的中间,这样在反汇编的时候便会出现混乱。花指令有可能利用各种指令:jmp, call, ret的一些堆栈技巧,位置运算,等等。

花指令的作用是对付静态分析,以下面一段程序说明一下花指令的原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream.h>
#include <windows.h>
void main()
{
_asm
{
jmp l2
_EMIT 0x1//这里就是花指令
_EMIT 0x2//这里就是花指令
_EMIT 0x3//这里就是花指令
_EMIT 0x4//这里就是花指令
l2:
mov eax,0x11111111
}
}

可以看到,程序直接跳转到标签l2了,在jmp指令和l2之间的就是花指令,花指令为什么能起作用呢?那是因为反编译器在反编译的时候不会像我们人一样去理解jmp和l2之间的指令是永远不会执行到的,所以在反编译的时候就把这段乱七八糟的代码作为正常的指令了,而这样的反编译会影响mov eax,0x11111111这个指令的正确识别,所以导致在OD中以上代码不会正确显示。

了解了原理我们就可以自如地设计花指令了,比如再加一段_EMIT 0x5等等。

花指令不光是能够用jmp指令来设计,还可以用call指令配合ret指令来进行设计,原理是这样的:我们知道call指令等于这样两条指令,一是把自身所在位置的下一条指令的地址压入堆栈,二是jmp到call的地址处,而ret指令可以理解为jmp到call指令压入堆栈的地址,因此,可以用call指令这样来写花指令:

1.call一个地址,在call下面随便写一点花指令,但是要注意一点与jmp版花指令不同的,我们要记得自己写的花指令占了多少个字节,比如,占了2字节,至于为什么要记得,往下看

2.在call里面,也就是函数里面,首先pop出压入的地址,然后把这个地址减去花指令占用的字节数,这里是2字节,再重新push进堆栈,然后就ret

这样,call结束以后执行的下一条指令就是我们想要去的位置了,也就是花指令下面的正常的指令了

其实用call来做的话起到的也就是jmp的作用,道理是一样的,只不过手法不同

这个call ret配合的方法是我分析aspack看到的,不知道还有没有其他的花指令的方法,有的话再加上:)

这里有个找花指令的小技巧,当发现jmp或者call的地址在OD中没显示的话,就把jmp或者call指令的下一个指令nop掉吧,肯定是花指令,如果还没有显示的话就继续nop下一条,直到显示的指令和执行的指令一致,所以说,去花指令是个体力活,哈哈

补充一点,如果这个call不跟进去的话,貌似程序就没办法调试了,不知道怎么回事,不清楚原因

下边是一个用call ret实现花指令和跳转的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream.h>
#include <windows.h>
void main()
{
DWORD p;
_asm
{
call l1
l1:
pop eax
mov p,eax//确定当前程序段的位置
call f1
_EMIT 0xEA//花指令,此处永远不会执行到
jmp l2//call结束以后执行到这里
f1://这里用F8OD会终止调试,F7跟进的话就正常,why?
pop ebx
inc ebx
push ebx
mov eax,0x11111111
ret
l2:
call f2//用ret指令实现跳转
mov ebx,0x33333333//这里永远不会执行到
jmp e//这里永远不会执行到
f2:
mov ebx,0x11111111
pop ebx//弹出压栈的地址
mov ebx,offset e//要跳转到的地址
push ebx//压入要跳转到的地址
ret//跳转
e:
mov ebx,0x22222222
}
cout<<hex<<p<<endl;

}