*本文原创作者:追影人,本文属FrBuf原创奖励计划,未经许可禁止转载前言 [逆向工厂]第一章节中介绍了逆向技术的相关基础知识,其中提到逆向的两种形式:静态分析、动态分析。 本章将对静态分析技术进行讲解,重点阐述静态分析的原理方法,程序的静态结构,常见流程控制语句的静态反编译形态,并且通过实例来掌握利用IDA逆向工具的静态逆向分析技术。 一、静态分析原理与方法上一篇章介绍到,程序运行前需要将硬盘内编译好的程序文件装载进内存,然后将指令送入CPU执行,此时程序就像“复活”一样,按照指令的“先后顺序”开始工作,这便是程序的“动态”。 与其相反,静态分析就是在程序尚未运行的状态时进行逆向分析的行为。但是,静态分析并非在硬盘上直接进行,仍需将文件存入内存进行分析,只是此时程序文件只是单纯作为数据被逆向软件加载。 1、“简单暴力”的静态分析既然静态分析只是把程序作为数据文件加载进逆向软件,进行查看分析,那岂不是用UltraEidt这类文本编辑软件直接查看即可。 上图是使用UE打开的某程序,可以看到程序的所有数据,通过查看文件PE头可以获取所有头信息,一些文件信息查看工具也正是利用该原理,直接读取静态文件按照PE格式提取相对应的信息。 上图所展示的区域是程序的代码段,这些看似很乱的字节码其实都对应着一条条的汇编语句,在这里UE只是很粗暴的将原始编译数据进行了展示,并没有进行反编译处理。 Q:静态分析中UE这种过于“暴力”的工具可以干什么? 最常用的便是使用UE处理字符串,使用“查找”功能搜索标题关键字。 在UE中修改标题,保存后再运行,程序效果如下: 你可以利用这样的手段自己随意修改程序的字串信息包括各种版权信息等(当然小心侵权哈),最重要的是一些程序会将ky等重要信息以明文的形式存储,所以使用UE可以找到这些ky。 这样做的前提是,字串均以明文形式存储,未加密处理,但通常商业程序会通过“壳”保护技术对程序加密保护,关于“壳”方面的知识在后续篇章介绍。 、“高级”静态分析相比上面“原生”字节码,童鞋更愿意阅读“高级”点的语言。既然同一种架构平台的字节码与汇编语句是一一对应的,那么根据这个原理便可将“二进制”文件反编译成汇编语言。 上图是IDA逆向工具反编译结果,相比UE的“暴力”,这个非常人性化,在第三节中重点介绍该软件的一些知识。 相比C\C++这类编译型程序,C#等带有解释运行的程序静态逆向的结果就更为“高级”了,甚至堪比源代码,下图便是某安全公司分析国产敲诈者病毒,该病毒为C#编写。 可清晰看到程序的执行流程,心疼作者没有用c写,暴露的这么赤裸裸。(其实不管用什么语言写,只要没有刻意去做代码保护,都容易被逆向分析出完整流程。) 二、代码结构上一章我们讲到hlloworld程序,本节将源代码做一些改变后继续。 将编译好的的程序用IDA打开,代码的整体结构如下图: 由图可知,程序先是执行__tmainCRTStartup函数,然后执行main函数。 __tmainCRTStartup函数反汇编代码: __tmainCRTStartup函数中调用main函数,在调用main函数之前,程序已经执行了一部分指令,例如:全局对象的构造函数,一些全局变量、对象和静态变量、对象的空间分配和赋初值,一些初始化代码(如设置环境变量等)。 main函数执行结束后返回到__tmainCRTStartup函数,执行一些收尾工作,如:全局对象的析构函数,释放空间、释放资源使用权等操作。 总结:程序在执行main函数前已经有代码运行,main函数的结束不代表进程的结束。 hlloworld程序的区段信息:hlloworld程序四个区段:1、.txt,默认的代码区段,它的内容全是指令代码 、.idata,包含其他外来的DLL的函数及数据信息,即输入表,在这里可以看见程序链接dll和调用dll中函数的情况。 3、.rdata,默认只读数据区块,一般两种情况用到,一是MS的链接器产生EXE文件中用于存放调试目录,二是用于存放说明字符串。 4、默认的读/写数据块,全局变量,静态变量一般放在这个区段。 以上是PE最基本的四个区段,还有一些区段描述,如下表所示: 名称描述.data输出表,当创建一个输出API或数据的可执行文件时,连接器会创建一个.EXP文件,这个.EXP文件包含一个.data区块,其会被加载到可执行文件中.rsrc资源,包括模块的全部资源,如图标,菜单,位图等,这个区块是只读的.crt用于C++运行时(CRT)所添加的数据.tlsTLS的意思是线程局部存储器,用于支持通过_dclspc(thrad)声明的线程局部存储变量的数据,这包括数据的初始化值,也包括运行时所需要的额外变量.rloc可执行文件的基址重定位,基址重定位一般仅Dll需要的.sdata相对于全局指针的可被定位的短的读写数据.pdata异常表,包含CPU特定的IAMGE_RUNTIME_FUNTION_ENTRY结构数组,DataDirctory中的IMAGE_DIRECTORY_ENTRY_EXCEPTION指向它.本节概述了反汇编代码的结构与一般组成,有利于大家在逆向分析中对代码的整体把握。 三、IDAPro与静态分析“工欲善其事必先利其器”,IDAPro无疑是静态反汇编工具里最强大的一个,下面就IDA简单介绍。 1、窗口管理,可以查看反汇编、hx数据、函数列表、字符串、段寄存器、函数调用、函数被调用等情况。代码中的字符串: 区段信息: 函数调用,查看调用和被调用函数的信息,下图是以hlloworld程序中main函数为例: 、图表信息,可以查看代码的执行流程图、函数调用图等。函数被调用(调用main函数的函数): 函数调用(main函数调用的其它函数): 函数执行流程(__tmainCRTStartup函数为例): 3、交叉引用与添加注释在出现XREF的地方就是有交叉引用(如下图); 而在XREF后面的向上箭头,双击它可以跳到它跳转的地方(如下图); 添加注释 在代码行右击鼠标选择“添加注释”或直接敲击“;”键 空格键可以将代码与流程图间切换: 4、反汇编函数中出现的标识含义sub_XXXXXX子程序loc_xxxxxx地址byt_xxxxxx8位数据word_xxxxxx16位数据dword_xxxxxx3位数据unk_xxxxxx未知的5、伪代码转换 IDA伪代码转换插件,可以将汇编语言转换为易读的类似于高级语言的伪代码,将光标放置在汇编代码区,敲击F5键就可将汇编语言转换为伪代码。 四、流程控制语句的反编译形态通常,程序在执行时按照代码的先后顺序执行,但一些语句会使得程序跳过某些代码执行,或者重复执行某段代码,这种改变程序执行流程的语句便是“流程控制语句”。 流程控制语句用来实现对程序流程的选择、循环、转向和返回等控制,不同语言因语法语义的差别,控制语句也有差异。 逆向分析时,如果能快速识别出流程控制语句,对于梳理程序结构和流程将事半功倍,因此熟悉各类流程控制语句及其对应的反编译代码结构尤为重要。本节重点针对C语言流程控制语言的反编译形态进行讲解。 C语言共包含4大类共9种控制语句: 1、选择语句(If、switch语句) 又称分支语句,该类语句从判断点开始,存在不止一条分支可供程序执行,通过给定的条件进行真假判断或者值判断,从而决定执行两个或多条分支的哪个分支。 、循环语句(dowhil、whil、for语句) 程序进入该语句后,重复执行循环体内代码,当满足某种条件后跳出循环语句执行后续代码。 3、转向语句(brak、continu、goto语句) 该类语句可打断程序当前执行的循环体,或者跳到指定的任意标记位处继续执行。 4、返回语句,rturn语句。 返回语句通常用于函数调用过程中的函数返回。 为深入理解掌握各类控制语句在反编译结果的形态,现编写各类控制语句的源码,生成对应程序,再利用IDA反编译,观察其形态。 1、if语言If条件判断语句通常有三种结构 下面是三种结构分别对应的源码、反编译形态、伪代码(通过IDAF5伪代码插件获得)。 If结构 Ifls双路结构 Ifls多路结构 上图可以看到在条件判断语句中,汇编代码通过cmp、tst等比较语句进行条件的判断,然后通过jmp、jl、jns等跳转语句进行流程跳转。 童鞋们会注意到,在源码中,printf函数出现在不同分支中,但是在反编译结果中,printf并没有出现在多个分支,而是分支中压入不同的打印参数,统一跳转到处调用prinf函数。 这种情况是因为在程序生成过程中,编译器根据源码进行优化处理,减少不必要重复,精简程序,缩小体积。 图中伪代码部分可以看出,除变量名称外,伪代码和源代码竟然非常接近甚至一模一样,而在平常的逆向过程中,idaF5伪代码插件经常会用到,可以大大提高分析效率。 图三中伪代码结构并非和源代码一样,而是嵌套形式的双路结构,这是因为程序在编译后是不可逆的,并不能恢复成初始状态。 、Switch语句源代码: inta; scanf_s("%d",a); switch(a) { cas0: printf("input:0\n"); brak; cas1: printf("input:1\n"); brak; cas: printf("input:\n"); brak; dfault: printf("input:othr\n"); brak; } printf("thprogramnd\n"); 反编译结果: txt:pushoffstFormat;"%d" .txt:Ecallds:scanf_s .txt:movax,[bp+var_4] .txt:addsp,8 .txt:Asubax,0 .txt:Djzshortloc_ .txt:Fdcax .txt:jzshortloc_ .txt:movsi,ds:printf .txt:8dcax .txt:9jzshortloc_ .txt:BpushoffstaInputOthr;"input:othr\n" .txt:30jmpshortloc_B .txt:3;--------------------------------------------------------------------------- .txt:3 .txt:3loc_:;CODEXREF:_main+9j .txt:3pushoffstaInput;"input:\n" .txt:37jmpshortloc_B .txt:00;--------------------------------------------------------------------------- .txt:00 .txt:00loc_:;CODEXREF:_main+0j .txt:00pushoffstaInput1;"input:1\n" .txt:3Ejmpshortloc_5 .txt:00;--------------------------------------------------------------------------- .txt:00 .txt:00loc_:;CODEXREF:_main+1Dj .txt:00pushoffstaInput0;"input:0\n" .txt:45 .txt:45loc_5:;CODEXREF:_main+3Ej .txt:45movsi,ds:printf .txt:4B .txt:4Bloc_B:;CODEXREF:_main+30j .txt:4B;_main+37j .txt:4Bcallsi;printf .txt:4Daddsp,4 .txt:50pushoffstaThProgramEnd;"thprogramnd\n" .txt:55callsi;printf .txt:57addsp,4 .txt:5Axorax,ax .txt:5Cpopsi .txt:5Dmovsp,bp .txt:5Fpopbp .txt:60rtn .txt:60_mainndp 反编译结果可见,switch语句也进行了同样优化,多个分支共同调用同一处printf函数。伪代码通过ifls语句、goto语句配合完成整个过程,从这点便可证明switch在底层实现上是与ifls语句相同。 3、Whil、dowhil和for语句当满足执行条件时,程序进入whil循环体,不断重复执行循环体内代码,直到条件为假时离开循环体。 从反编译结果可以看到,通过inc语句对变量i的值进行自加,通过cmp对比i是否小于0×8,通过对比结果再决定是否跳回循环体的第一条指令处。在伪代码中可以看到是以dowhil结构展示,下面我们再看看dowhil语句。 与whil条件为真才进入循环体不同,dowhil语句是先进入循环体然后再判断条件,以决定是否重复执行循环体。 从上面的结果可以看出,dowhil和whil语句的反编译结果一致,这似乎与我们上面提到的不同有出入。这个例子中,i的初始值为0,即第一次执行循环体时,i10为真,因此程序在进行编译时进行了优化,导致其二进制结构和dowhil一致。 for语句也是拥有循环体和判断条件,经常搬砖的同学都晓得for语句是可以转换为whil语句,在编译器眼里也如此,for语句在编译过程中和whil语句一致,反编译的结果也如上图。 4、Brak和continu语句Brak和congtinu语句一般用在whil循环体中,brak用于跳出whil循环体,continu用于结束本次循环进入下次循环。下图可以看到brak语句在条件判断成功后,jmp到循环体后面的语句。 Continu语句执行后,会终止本轮循环体的执行,跳转到循环体的判断语句,通过判断后再决定是否进入下轮循环。 5、goto语句Goto语句可以调到任意标签地址处,因这种跳转破坏了程序的结构性,让代码的执行顺序杂乱无章,一般在软件开发中不推荐使用这种跳转。 下图中伪代码部分再次看出程序的不可逆性,编译器在编译连接时,根据程序的整体结构和部分流程进行综合优化编译,同样的一段源码也许在不同编译器或者不同工程中,生成的编译结果均有差异。 6、rturn语句Rturn语句用于被调函数执行完毕后,返回到主调函数继续执行,通常返回时可带一个返回值。在学习rturn前我们有必要了解程序调用函数的机制。 函数通常分为无返回值函数(void类型)和有返回值函数,前者在编程时无须写rturn,而后者必须以rturn+返回值的形式作为结尾语句。 根据函数的来源,分为系统函数(由系统DLL提供)和自定义函数(用户自己编写),在调用函数时,均通过call语句跳转到函数的入口进行执行,看起来和jmp语句等价,但是call语句会将下一句的位置记录自动保存下来。 等函数执行完毕后,调用rnt语句,返回至刚才记录的位置继续执行。 Call语句都是以call+函数地址的形式存在,逆向工具为提高可读性,通常会将调用系统函数的地方替换成该函数名称,从而一目了然确定调用对象。 因程序编译过程中会丢掉函数名等符号信息,所以逆向结果是无法知道用户自定义函数名称的,故反编译的结果如下图所示,由逆向工具的自定义名称代替。 下图为用户自定义函数的内部,可以直观看到,其内部调用了MssagBp和MssagBoxA两个函数。 在上述多个例子中,我们都会看到在调用函数call语句前面,通常会紧跟一个或多个push语句,push所入栈的数值即函数的参数。 MssagBp函数: BOOLMssagBp(UINTuTyp);//该函数用来播放一个波形声音,声音类型由参数uTyp决定。 结合上图,可见在处push0即为参数uTyp的值。 MssagBox函数: IntWINAPIMssagBox(HWNDhWnd,LPCTSTRlpTxt,LPCTSTRlpCaption,UINTuTyp);//显示一个模态对话框。 上图中9-处便是压入的4个参数。 自定义函数执行到末尾时,便执行D处的rtn进行返回,这句汇编代码即对应源码中的rturn语句,如果需要返回具体数值,则rtn前会将返回值存入ax寄存器。 有关函数调用过程中的具体问题及所涉及的堆栈平衡知识,逆向工厂将在后续动态分析章节中详细介绍。 五、代码分析示例为了清楚地了解静态分析,下面就以一个crackm小程序作为例子进行分析,该程序主要是验证输入的序列号是否正确。 反汇编后的代码框架与函数: 由以上两图可知,start、sub_、sub_B三个函数是主要功能函数。 start函数反汇编代码及伪代码: 这个就是start函数就是windows编程里的Winmain函数,该函数定义了窗口控件、接受windows消息函数、消息循环等。其中lpfnWndProc就是指向接受windows消息函数的指针,由反汇编代码可知,lpfnWndProc指向sub_。 sub_反汇编代码及伪代码: 该函数就是windows编程里通常定义的LRESULTCALLBACKWndProc(HWNDhwnd,UINTmssag,WPARAMwParam,LPARAMlParam)函数了。 该函数有三个消息处理过程,一个是msg==73,另一个是msg==,还有一个缺省处理函数DfWindowProcA。 当消息类型为73时,获取窗口中的输入内容,调用sub_B函数进行验证,根据返回结果弹出对话框进行提示;当消息类型为时,向消息队列发送消息,该消息类型为0;缺省函数DfWindowProcA为应用程序没有处理的任何窗口消息提供缺省的处理,该函数确保每一个消息得到处理。 下面看一下序列号验证函数sub_B的反汇编代码及伪代码: 由代码看出,该校验函数中没有明存的字符串,对输入的字符串进行处理后才进行比较验证的。字符串的处理过程: 汇编代码: loc_: //ax存储输入的字符串地址,dx初始为0 movbl,[ax]//拷贝一个字符串到bl寄存器 rolbx,8//将bx中的值左移八位 adddx,bx //将bx中加到dx上 incax //ax自加1 cmpbytptr[ax],0 //判断是否还有字符 jnzshortloc_ //如果有,继续循环 这段代码的伪代码如下: v1=String; if(!String[0]) gotoLABEL_16; v=0; v3=0; do { LOBYTE(v)=*v1; v=__ROL4__(v,8); v3+=v; ++v1; } whil(*v1); 字符串处理完成后调用wsprintf函数将字符串处理结果按十六进制输出到缓冲区,该函数原型wsprintf(缓冲区,格式,要格式化的值),反汇编代码: pushdx //要格式化的值 pushoffstaLx;"%lX" //格式,十六进制长整型 pushoffstbyt_BF;LPSTR //缓冲区 callwsprintfA //调用wsprintf movbx,offstbyt_BF //将缓冲区地址给bx dx寄存器长3位,以十六进制格式化后,结果长度为8。代码中的字符串为38h、44h、43h、41h、46h、33h、36h、38h,即“8DCAF”。 将输出的缓冲区的字符串与“8DCAF”比较,相同返回1,不同返回0。至此,程序分析结束。 破解这个程序较为简单,以两种破解方式为例: 1、根据代码处理字符串的过程以及要对比的字符串内容(8DCAF),生成相应的输入字符串。 、sub_B函数在处理完成后,对ax寄存器赋值后作为函数的返回值: 输入正确时ax置1,输入错误时ax置0。将分支loc_D中的xorax,ax修改为 movax,1,使得sub_B返回值恒为1,这样无论输入什么内容,都会显示正确弹框。 写在最后本章逆向工厂重点讲述静态逆向分析技术,程序代码结构,常用的流程控制语句的反编译形态,并且结合IDA分析CM程序执行流程。 童鞋们似乎发现借用IDA等静态分析工具,可以从宏观上清晰掌握程序架构,快速分析程序逻辑,但是如果目标程序过大或者逻辑结构十分复杂时,就很难通过静态分析来梳理其流程。 或者当程序采用一些保护技术对代码数据进行加密或混淆后,静态分析工具就无法展现程序的真实面貌,而此时就需要借用另一项技术——动态分析技术。相关内容敬请白癜风的医疗医院北京治疗白癜风什么时候
|