花指令的作用是用来隐藏反汇编后的代码,对于动态调试来说无法隐藏,但是对于静态分析来说就成为必须绕过去的一个问题。
花指令如何实现代码隐藏
花指令能成功干扰反编译器识别代码,主要是与反编译器的实现技术有关。主流反编译器使用两种方法进行反汇编操作,分别是线性扫描和递归下降。
线性扫描
之所以叫线性扫描,就是说像一条线一样从头扫到尾。线性扫描反汇编从一个代码段的第一个字节开始,以线性模式扫描整个代码段,逐条反汇编每条指令,直到完成整个代码段
- 优点:能够完全覆盖程序的所有代码段
- 缺点:没有考虑到代码中可能混有数据
递归下降
递归下降反汇编强调控制流的概念。根据一条指令是否被另一条指令引用来决定是否进行反汇编。
- 优点:它具有区分代码与数据的强大能力。作为一种基于控制流的算法,它很少会在反汇编过程中错误地将数据值作为代码处理。
- 缺点:它无法处理间接代码路径,如利用指针表来查找目标地址的跳转或调用。
而花指令就是利用这两种方法的缺点来实现。我觉得大家反编译的工具一般是IDA Pro
,在设计这类花指令时要通过构造 必然条件 或者 互补条件,使得程序在实际执行时绕过垃圾数据,这样不会影响程序正常执行
常见花指令
相同目标的跳转指令
在反汇编过程中遇到 jz
(跳转到目标地址为零的情况下执行)和 jnz
(跳转到目标地址不为零的情况下执行)指令时,如果它们的目标地址相同,那么程序实际上就是要执行一个无条件跳转,相当于 jmp
指令。然而,IDA Pro
在反汇编时可能会误将 jnz
后面的指令也反汇编出来,尽管实际上这些指令并不会被执行,因为程序在遇到 jnz
指令时已经跳转了。这种情况下,如果后面紧跟着的是一些字节指令(如 call
或 jmp
指令),IDA Pro
可能会将它们错误地解释为与 jnz
相关的指令,导致反汇编结果出现问题。
例如假设原始机器码如下
1 | 74 03 ; jz 目标地址为当前地址 + 3 |
这里的call指令即为无用数据,只需要将call指令转为data数据再反编译即可
固定条件的跳转指令
当条件满足时,相应的判断语句后面的代码将会执行,而当条件不满足时,与之对应的代码则不会执行。如果设置了一个永真语句,后面的代码一定会执行,若是将要执行的代码后继续跟无条件跳转语句,ida则会识别出错。
例如
1 | 33 C0 ; xor eax, eax |
在这个例子中xor
结束zf
标志位一定会被置1,jz
跳转成立,而jmp
则不会执行。更改方法是将jmp
这行代码nop
或改为data
函数跳转
函数调用时将调用指令的下一条指令地址压栈,然后跳转到函数位置执行,相当于:PUSH 下一条指令地址
、MOV EIP,函数位置
,相应地,函数返回时将函数调用时的压栈地址恢复给EIP,相当于POP EIP
。如果破环了其中的堆栈平衡,ida就会报错。
例如:
1 | call label ;调用label函数 |
至于栈顶的返回地址要加多少,这取决于跨过语句的长度。此处构造RET
返回的位置和正常RET
返回的位置相差5个字节,因此栈顶数据加上5个字节