5.2 if…else…语句(2)
按if…else…的逻辑,如果满足if条件,则执行if语句块;否则执行else语句块,两者有且仅有一个会执行。所以,如果编译器生成的代码在0040109C处的跳转条件成立,则必须到达else块的代码开始处。而004010A5处有个无条件跳转jmp,它的作用是绕过else块,因为如果能执行到这个jmp,if条件必然成立,对应的反汇编代码处的跳转条件必然不能成立,且if语句块已经执行完毕。由此,我们可以将这里的两处跳转指令作为“指路明灯”,准确划分if块和else块的边界。
总结:
- ; 先执行影响标志位的相关指令
- jxx ELSE_BEGIN ; 该地址为else语句块的首地址
- IF_BEGIN:
- …… ; if语句块内的执行代码
- IF_END:
- jmp ELSE_END ; 跳转到else语句块的结束地址
- ELSE_BEGIN:
- …… ; else语句块内的执行代码
- ELSE_END:
如果遇到以上指令序列,先考察其中的两个跳转指令,当第一个条件跳转指令跳转到地址ELSE_BEGIN处之前有个JMP指令,则可将其视为由if…else…组合而成的双分支结构。根据这两个跳转指令可以得到if和else语句块的代码边界。通过cmp与jxx可还原出if的比较信息,jmp指令之后即为else块的开始。依此分析,即可逆向分析出if…else…组合的原型。
在Debug编译模式下,所使用的编译选项是Od+ZI,于是在这里不能做流水线优化,分支必须存在,以便于开发者设置断点观察程序流程。
使用O2优化选项,重新编译代码清单5-4。通过IDA查看优化后的反汇编代码,如代码清单5-5所示。
代码清单5-5 模拟条件表达式转换方案1—Release版
- ; 参数标记定义,arg_表示函数参数1
- arg_0= dword ptr 4
- ; 将取得的参数数据放到edx中
- mov edx, [esp+arg_0]
- ; 将eax清0
- xor eax, eax
- ; 对edx和edx执行相与操作,结果不影响edx
- test edx, edx
- ; 检查ZF标记位,edx不等于0则al=1,反之al=0,这里的操作与代码清单4-14类似
- setnz al
- add eax, 5 ;到此,eax的取值只可能是5或6,如果edx为0,则eax为5,反之则为6
- push eax
- push offset Format ; "%d \r\n"
- call _printf
- add esp, 8
- retn
代码清单5-5中的这些指令似曾相识,与条件表达式使用了同样的优化手法。其他3种优化方案同样适用于if…else…。通过以上分析,得出VC++(www.cppentry.com) 6.0编译的代码,在很多情况下,会发现条件表达式的反汇编代码和if…else…组合是一样的,这时,可以根据个人习惯还原出等价的高级代码。
有时候会遇到复杂的条件表达式作为分支或者循环结构的判定条件的情况,这时即使直接阅读高级源码也会让人抓狂。在没有高级源码的情况下,分析者需要先定位语句块的边界,然后根据跳转目标和逻辑依赖慢慢反推出高级代码。