您好,欢迎访问三七文档
当前位置:首页 > 临时分类 > C++反汇编与逆向分析技术揭秘-第5章
第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和PF00401028cmpdwordptr[ebp+8],0;根据cmp指令影响到的标记位,查看表4-1;JNE检查ZF标记位的值,如果值等于0,则跳转,表示此时argc的值不等于0,;于是跳转到地址0x0040103F处;这个地址为if语句块的结束地址,随后跳转出if语句0040102Cjnemain+2Fh(0040103f){;printf函数调用讲解略}//C++源码对比,函数返回return0;0040103Fxoreax,eax代码清单5-1中if的比较条件为“argc==0”,如果成立,即为真值,则进入if语句块内执行语句。但是,转换后的汇编代码使用的条件跳转指令JNE判断结果为“不等于0跳转”,第5章 流程控制语句的识别 111这是为什么呢?因为按照if语句的规定,满足if判定的表达式才能执行if的语句块,而汇编语言的条件跳转却是满足某条件则跳转,绕过某些代码块,这一点与C语言是相反的。既然这样,那为什么C语言编译器不将else语句块提到前面去并把if语句块放到后面去呢?这样汇编语言和C语言中的判定条件不就一致了吗?因为C语言是根据代码行的位置来决定编译后的二进制代码的地址高低的,也就是说,低行数对应低地址,高行数对应高地址,所以有时会使用标号相减得到代码段的长度。鉴于此,C语言的编译器不能随意改变代码行在内存中的顺序。根据这一特性,如果将if语句中的比较条件“argc==0”修改为“if(argc0)”,则其对应的汇编语言所使用的条件跳转指令会是“小于等于0”。代码清单5-2 if语句大于0比较—Debug版//C++源码说明:if语句大于0比较if(argc0){printf(%d\r\n,argc);}//C++源码与对应汇编代码讲解//C++源码对比,如果参数argc大于0,结果为真,进入执行语句if(argc0);使用CMP指令,将ebp+8地址处的4字节数据与0相减0040103Fcmpdwordptr[ebp+8],000401043jleMyIf+42h(00401052){;printf函数调用讲解略//if语句结束处}00401052popedi通过代码清单5-1和代码清单5-2的示例分析,可总结出if语句的转换规则:在转换成汇编代码后,由于当if比较结果为假时,需要跳过if语句块内的代码,因此使用了相反的条件跳转指令。将4.2.2节的代码清单4-10与代码清单5-2进行对比分析可知,两者间的结构特征十分相似,但使用的条件跳转指令不同。由此可见,在反汇编时,表达式短路和if语句这两种分支结构的实现过程都是一样的,很难在源码中对它们进行区分。总结:;先执行各类影响标志位的指令;其后是各种条件跳转指令jxxxxxx如果遇到以上指令序列,可高度怀疑它是一个由if语句组成的单分支结构,根据比较信息与条件跳转指令,找到其跳转条件相反的逻辑,即可恢复分支结构原型。由于循环结构中也会出现类似代码,因此在分析过程中还需要结合上下文。112 第二部分 C++反汇编揭秘5.2 if…else…语句5.1节讲述了if语句的构成,但是,只有if的语句是不完整的分支结构,图5-1对比了两种语句结构的执行流程。图5-1 if与if…else…结构对比如图5-1所示,if语句是一个单分支结构,if…else…组合后是一个双分支结构。两者间完成的功能有所不同。从语法上看,if…else…只比if语句多出了一个else。else有两个功能,如果if判断成功,则跳过else分支语句块;如果if判断失败,则进入else分支语句块中。有了else语句的存在,程序在进行流程选择时,必会经过两个分支中的一个。通过代码清单5-3,我们来分析else如何实现这两个功能。代码清单5-3 if…else…组合—Debug版//C++源码说明:if…else…组合if(argc==0){//执行if语句块printf(argc==0);}else{//执行else语句块printf(argc!=0);}//C++源码与对应汇编代码讲解//C++源码对比,比较参数变量argc==0if(argc==0);使用变量argc减去0004010B8cmpdwordptr[ebp+8],0;使用条件跳转JNE,检查cmp影响标记位;跳转成立,跳转到地址0x004010CD处,即else语句块的首地址004010BCjneIfElse+2Dh(004010cd)第5章 流程控制语句的识别 113{//C++源码对比,进入if语句块,调用printf函数printf(argc==0);;printf函数汇编讲解略}else;直接跳转到地址0x004010DA地址处,当if语句执行后跳转过else语句块004010CBjmpIfElse+3Ah(004010da){//C++源码对比,进入else语句块,调用printf函数printf(argc!=0);;printf函数汇编讲解略004010CDpushoffsetstringargc!=0(00420030)004010D2callprintf(00401150)004010D7addesp,4};else语句结束处004010DApopedi在代码清单5-3中,if语句转换的条件跳转和代码清单5-1中的if语句相同,都是取相反的条件跳转指令。而在else处(地址004010CB)多了一句jmp指令,这是为了在if语句比较后,如果结果为真,则程序流程执行if语句块并且跳过else语句块,反之执行else语句块。else处的jmp指令跳转的目标地址为else语句块结尾处的地址,这样的设计可以跳过else语句块,实现两个分支语句二选一的功能。4.2.3节介绍了条件表达式,当条件表达式中的表达式2或表达式3为变量时,没有进行优化处理。条件表达式转换后的汇编代码和if…else…结构非常相似,将代码清单4-17与代码清单5-3进行分析对比可以发现,两者间有很多相似之处,如没有源码比照,想要分辨出是条件表达式还是if…else…结构实在太难。它们都是先比较,然后再执行条件跳转指令,最后进行流程选择的。通常,VC++6.0对条件表达式和if…else…使用同一套处理方式。代码清单5-3对应条件表达式转换方式4。将代码清单5-3稍作改动,改为符合条件表达式转换方式1的形式,如代码清单5-4所示。代码清单5-4 模拟条件表达式转换方式1//C++源码说明:if…else…模拟条件表达式转换方式1if(argc==0){//等价条件表达式中的表达式1//修改上例,将上例中的printf函数替换成变量赋值语句argc=5;//等价条件表达式中的表达式2}else{//代码上例,将上例中的printf函数替换成变量赋值语句argc=6;//等价条件表达式中的表达式3}//防止变量被优化处理printf(%d\r\n,argc);114 第二部分 C++反汇编揭秘//Debug调试版,由于注重调试功能,没有进行优化,反汇编讲解如下22:if(argc==0){;直接与0进行比较,注意后面的jne,如果不相等就跳走,C源码中的逻辑与汇编代码相反00401098cmpdwordptr[ebp+8],00040109Cjnemain+27h(004010a7)23:argc=5;;这里是if语句块的内容,将参数赋值为50040109Emovdwordptr[ebp+8],524:}else{;注意这里的跳转,正好跳出了else块004010A5jmpmain+2Eh(004010ae)25:argc=6;;这里是else语句块的内容,将参数赋值为6004010A7movdwordptr[ebp+8],626:}27:printf(%d\r\n,argc);;printf函数汇编讲解略004010AE......按if…else…的逻辑,如果满足if条件,则执行if语句块;否则执行else语句块,两者有且仅有一个会执行。所以,如果编译器生成的代码在0040109C处的跳转条件成立,则必须到达else块的代码开始处。而004010A5处有个无条件跳转jmp,它的作用是绕过else块,因为如果能执行到这个jmp,if条件必然成立,对应的反汇编代码处的跳转条件必然不能成立,且if语句块已经执行完毕。由此,我们可以将这里的两处跳转指令作为“指路明灯”,准确划分if块和else块的边界。总结:;先执行影响标志位的相关指令jxxELSE_BEGIN;该地址为else语句块的首地址IF_BEGIN:……;if语句块内的执行代码IF_END:jmpELSE_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章 流程控制语句的识别 115码清单5-5所示。代码清单5-5 模拟条件表达式转换方案1—Release版;参数标记定义,arg_表示函数参数1arg_0=dwordptr4;将取得的参数数据放到edx中movedx,[esp+arg_0];将eax清0xoreax,eax;对edx和edx执行相与操作,结果不影响edxtestedx,edx;检查ZF标记位,edx不等于0则al=1,反之al=0,这里的操作与代码清单4-14类似setnzaladdeax,5;到此,eax的取值只可能是5或6,如果edx为0,则eax为5,反之则为6pusheaxpushoffsetFormat;%d\r\ncall_printfaddesp,8retn代码清单5-5中的这些指令似曾相识,与条件表达式使用了同样的优化手法。其他3种优化方案同样适用于if…else…。通过以上分析,得出VC++6.0编译的代码,在很多情况下,会发现条件表达式的反汇编代码和if…else…组合是一样的,这时,可以根据个人习惯还原出等价的高级代码。有时候会遇到复杂的条件表达式作为分支或者循环结构的判定条件的情况,这时即使直接阅读高级源码也会让人抓狂。在没有高级源码的情况下,分析者需要先定位语句块的边界,然后根据跳转目标和逻辑依赖慢慢反
本文标题:C++反汇编与逆向分析技术揭秘-第5章
链接地址:https://www.777doc.com/doc-5861731 .html