第5章 流程控制语句的识别
流程控制语句的识别是进行逆向分析和还原高级代码的基础,对于想从事逆向分析工作的读者来说,本章的内容非常重要。对于无意从事逆向分析工作的开发人员,通过本章的学习可以更好地理解高级语言中流程控制的内部实现机制,对开发和调试大有裨益。
5.1 if语句
if语句是分支结构的重要组成部分。if语句的功能是先对运算条件进行比较,然后根据比较结果选择对应的语句块来执行。if语句只能判断两种情况:“0”为假值,“非0”为真值。如果为真值,则进入语句块内执行语句;如果为假值,则跳过if语句块,继续运行程序的其他语句。要注意的是,if语句转换的条件跳转指令与if语句的判断结果是相反的。我们以代码清单5-1为例,逐步展开对if语句的分析。
代码清单5-1 if语句构成—Debug版
//C++源码说明:if语句结构组成
if(argc==0){
printf("%d\r\n",argc);
}
//C++源码与对应汇编代码讲解
//C++源码对比,若参数argc等于0,则为真,执行语句块
if(argc==0)
;使用CMP指令,将ebp+8地址处的4字节数据与0相减
;结果不影响argc,但影响标记位CF、ZF、OF、AF和PF
00401028 cmp dword ptr[ebp+8],0
;根据cmp指令影响到的标记位,查看表4-1
;JNE检查ZF标记位的值,如果值等于0,则跳转,表示此时argc的值不等于0,
;于是跳转到地址0x0040103F处
;这个地址为if语句块的结束地址,随后跳转出if语句
0040102C jne main+2Fh(0040103f)
{
;printf函数调用讲解略
}
//C++源码对比,函数返回
return 0;
0040103F xor eax, eax
代码清单5-1中if的比较条件为“argc==0”,如果成立,即为真值,则进入if语句块内执行语句。但是,转换后的汇编代码使用的条件跳转指令JNE判断结果为“不等于0跳转”,这是为什么呢?因为按照if语句的规定,满足if判定的表达式才能执行if的语句块,而汇编语言的条件跳转却是满足某条件则跳转,绕过某些代码块,这一点与C语言是相反的。
既然这样,那为什么C语言编译器不将else语句块提到前面去并把if语句块放到后面去呢?这样汇编语言和C语言中的判定条件不就一致了吗?
因为C语言是根据代码行的位置来决定编译后的二进制代码的地址高低的,也就是说,低行数对应低地址,高行数对应高地址,所以有时会使用标号相减得到代码段的长度。鉴于此,C语言的编译器不能随意改变代码行在内存中的顺序。
根据这一特性,如果将if语句中的比较条件“argc==0”修改为“if(argc>0)”,则其对应的汇编语言所使用的条件跳转指令会是“小于等于0”。
代码清单5-2 if语句大于0比较—Debug版
//C++源码说明:if语句大于0比较
if(argc>0){
printf("%d\r\n",argc);
}
//C++源码与对应汇编代码讲解
//C++源码对比,如果参数argc大于0,结果为真,进入执行语句
if(argc>0)
;使用CMP指令,将ebp+8地址处的4字节数据与0相减
0040103F cmp dword ptr[ebp+8],0
00401043 jle MyIf+42h(00401052)
{
;printf函数调用讲解略
//if语句结束处
}
00401052 pop edi
通过代码清单5-1和代码清单5-2的示例分析,可总结出if语句的转换规则:在转换成汇编代码后,由于当if比较结果为假时,需要跳过if语句块内的代码,因此使用了相反的条件跳转指令。
将4.2.2节的代码清单4-10与代码清单5-2进行对比分析可知,两者间的结构特征十分相似,但使用的条件跳转指令不同。由此可见,在反汇编时,表达式短路和if语句这两种分支结构的实现过程都是一样的,很难在源码中对它们进行区分。
总结:
;先执行各类影响标志位的指令
;其后是各种条件跳转指令
jxx xxxx
如果遇到以上指令序列,可高度怀疑它是一个由if语句组成的单分支结构,根据比较信息与条件跳转指令,找到其跳转条件相反的逻辑,即可恢复分支结构原型。由于循环结构中也会出现类似代码,因此在分析过程中还需要结合上下文。