​ 用高级语言编写的程序有两种形式。一种程序是被编译成机器语言在CPU上执行的,例如VisualC++。机器语言与汇编语言几乎是对应的,因此,可以将机器语言转化成汇编语言,这个过程称为反汇编(Disassembler)。例如,在x86系统中,机器码“EB”对应的汇编语句是“jmp short xx’”。另一种程序是一边解释一边执行的,编写这种程序的语言称为解释性语言,例如Visual Basic3.0/4.0、Java。这类语言的编译后程序可以被还原成高级语言的原始结构,这个过程称为反编译(Decompiler)。

​ 所谓静态分析,是指通过反汇编、反编译手段获得程序汇编代码或源代码,然后根据程序清单分析程序的流程,了解模块所完成的功能。

一、文件类型分析

​ 逆向分析程序的第一步就是分析程序的类型,了解程序是用什么语言编写的或用什么编译器编译的,以及程序是否被某种加密程序处理过,然后才能有的放矢,进行下一步工作。这个分析过程需要文件分析工具的辅助。常见的文件分析工具有PEiD、Exeinfo PE等。此类工具可以检测大多数编译语言、病毒和加密软件,本节以PEiD为例简单讲解它们的用法。

​ PEiD是一款常用的文件检测分析工具,具有GUI界面。它能检测大多数编译语言、病毒和加密的壳。如图所示,被分析的文件是用Microsoft Visual C++6.0编译的,对无法分析出类型的文件可能报告“PE Win GUI”(“Win GUI”是Windows图形用户界面程序的统称)。

image-20240710100201779

​ PEiD这类文件分析工具是利用特征串搜索来完成识别工作的。各种开发语言都有固定的启动代码,利用这一点就可以识别程序是由何种语言编译的。被加密程序处理过的程序中会留下加密软件的相关信息,利用这一点就可以识别程序是被何种软件加密的。

​ PEiD提供了一个扩展接口文件userdb.txt,用户可以自定义一些特征码,这样就可以识别新的文件类型了。签名的制作可以用Add Signature插件完成,必要时还要用OllyDbg等调试器配合进行修正。

​ 有些外壳程序为了欺骗PEiD等文件识别软件,会将一些加壳信息去除,并伪造启动代码。例如,将入口代码改成与用Visual C+6.0所编程序入口处类似的代码,即可达到欺骗的目的。所以,文件识别工具给出的结果只能作为参考,至于文件是否被加壳处理过,要跟踪分析程序代码才能知道。

二,反汇编引擎

​ 在安全软件和保护软件的开发过程中经常会用到汇编引擎和反汇编引整,例如OllyDbg、IDA、VMProtect、加壳软件和反编译器等。反汇编引擎的作用是把机器码解析成汇编指令。开发反汇编引擎需要对Intel的i386机器指令编码有深入的了解。不过,一般不需要自己开发反汇编引擎,网上有很多开源的或收费的反汇编引擎可以使用。目前主流的开源x86-64汇编引擎和反汇编引擎,在不同的使用场景中各有优势。下面对常用的汇编引擎和反汇编引擎进行比较,反汇编引擎有ODDisasm、BeaEngine、Udis86、Capstone,汇编引擎有ODAssembler、Keystone、AsmJit。

  1. OllyDbg的ODDisasm

    OllyDbg的ODDisasm:OllyDbg自带的反汇编引擎ODDisasm,优点是具有汇编接口(即文本解析,将文本字符串解析并编码成二进制值),这个特性曾经独树一帜。近些年出现的调试器x64_dbg,功能与OllyDbg的文本解析功能相似,支持的指令集更加完整,Bug更少,同时支持x64平台。

  2. BeaEngine

BeaEngine:能解析的扩展指令集有FPU、MMX、SSE、SSE2、SSE3、SSSE3、SSE4.1、SSE4.2、VMX、CLMUL、AES、MPX。BeaEngine对指令进行了分类,以便判 断不同的指令。BeaEngine还有一个特点是可以解码每一条指令所使用和影响的寄存器,包括标志位寄存器,甚至能精确解码标志位寄存器的所有位置,这个功能用来做 优化器和混淆器是很有优势的。BeaEngine除了支持对x86指令进行反汇编,还支持对x64指令进行反汇编。

  1. Udis86

    ​ Udis86是一款广受欢迎的反汇编引擎,支持的x86扩展指令集包括MMX、FPU(x87)、AMD3DNow!、SSE、SSE2、SSE3、SSSE3、SSE4.1、SSE4.2、AES、AMD-V、INTEL-VMX、SMX。Udis86除了支持对x86指令进行反汇编,还支持对x64指令进行反汇编。Udis86的代码风格精简,功能函数短小,变量命名和接口干净、简单、操作灵活。如果需要自行维护一个分支,使用Uis86几十分钟就能熟悉整个代码架构。

    ​ Udis86的优点是接口灵活,可以使用ud_decode函数对一条指令只进行解码操作,再对解码后的结构使用ud_translate_intel函数转换成文本格式,也可以直接使用ud_disassemble函数一次性完成所有操作,这些接口都只需要一行代码就能实现。

  2. Capstone

​ Capstone可以说是所有反汇编引擎中的集大成者。因为Capstone移植自LLVM框架的MC组件的一部分,所以LLVM支持的CPU架构它也都支持。Capstone支持的CPU 架构有ARM、ARM64(ARMv8)、M68K、MIPS、PowerPC、SPARC,System z、TMS320C64X、XC0RE、x86(包括x86-64)而且,Capstone对x86架构指令集的支持是最全 的,这一点是其他引擎比不上的,它支持的x86扩展指令集有3DNow、3 DNowa、x86-64、ADX、AES、Atom、AVX、AVX2、AVX512CD、AVX512ER、AVX512F、 AVX512PF、BMI、BMI2、FMA、FMA4、FSGSBASE、LZCNT、MMX、SGX、SHA、SLM、SSE、SSE2、SSE3、SSE4.1、SSE4.2、SSE4A、SSSE3、TBM、XOP。
​ 在目前移动端开发火热的背景下,支持ARM的反汇编库却很少。如果要同时进行x86与ARM下编译器方面的开发,能使用统一的接口自然更好。仅从x86-64平台上 的情况来看,无论是解码能力还是指令集支持,Capstone完全超越了BeaEngine。

​ 因为Capstone是从LLVM中移植过来的,Capstone是C语言的项目,而LLVM是C++语言的项目,所以Capstone在移植过程中做了很多适配工作,显得很靡肿。举个 例子,LLVM中的MCInst是一个单条底层机器指令的描述类,因为Capstone是C语言的项目,所以在移植时将这些类变成了结构,把成员函数变成了独立的C函数,例如 MCInst_Init、MCInst_setOpcode。而且,由于LLVM框架的复杂性和高度兼容性,里面的所有的概念都进行了高度抽象,而Capstone通过适配接口将其转换到自己的架 构中,也造成了解码时中间层过多、性能下降。
​ 在一条指令的解码过程中,重要的中间层结构的使用顺序是MCInst-→InternalInstruction-→cs_insn。基础的解码工作依靠LLVM的架构,解码到Capstone的 InternalInstruction中(它是一个包含解码过程中所有细节的内部结构)。解码后,调用update_pub_insn将认为需要公开的内容复制到cs_insn中。其他反汇编引擎都是一 次性解码到目标结构中的。

​ Capstone的解码过程如此复杂,自然会对性能造成影响。Capstone的性能耗时是Udis86的5~6倍。如果换一种方式来测试,Udis86只使用ud_decode函数进行解 码,而Capstone没有独立的解码接口,需要进行一些修改(让它不生成汇编文本),那么Capstone的耗时大概是Udis86的2倍。由此可见,Capstone的文本操作比 Udis86慢得多。

​ 此外,Capstone的内存消耗很大,解码一条指令时传入的指令结构cs_insn必须由动态分配函数来分配,而且要分配两次,一次是cs_insn,另一次是cs_detail,这会造成 巨量的内存碎片。因为每一条指令的结构体都很大(sizeof(cs_.insn)+sizeof(cs_detail)训=1760字节),所以必须使用动态内存,这也是Capstone与其他反汇编引擎不一样的 地方。如果要使用Capstone进行大量的指令分析,就要给它配置一个固定的对象内存分配器,从而稍稍减少内存碎片的生成,提高一点点的性能。可能是基于以上原 因,x64dbg社区在一开始以BeaEngine为基础,但BeaEngine总是爆出Bug,所以后来用Capstone替换了BeaEngine(仅用Capstone进行GUI的文本反汇编)。Capstone虽然 解码速度不高,但是Bug很少(LLVM有苹果那么大规模的公司支撑)。不过,Capstone的流图和指令分析功能还不完善,因此目前在这些方面仍在使用BeaEngine。

  1. Asmjit

​ AsmJit是一个以C++封装的完整的JIT汇编器和编译器,它可以生成兼容x86和x64架构的原生汇编指令,支持x86和x64指令集,包括MMX、SSEx、BMx、ADX、 TBM、XOP、AVXx、FMAx、AVX512等。AsmJit与前面介绍的开源库都不一样,它不像BeaEngine、Udis86、Capstone那样能对二进制指令进行反汇编解析,它只是一个 汇编器。指令都被封装成了类成员函数,通过调用函数的方式来编码:函数中的参数既可以直接使用指定的寄存器、内存操作数,也可以使用X86Gp、Labl等类型的占位 符变量,根据不同的逻辑给这些占位符变量赋值不同的操作数。

  1. Keystone

    ​ Keystone和Capstone是同一系列的引擎,由同一维护者主导开发。Capstone主要负贵跨平台多指令集的反汇编工作,而Keystone主要负责跨平台多指令集的汇编工作。与OllyDbg的汇编器一样,Keystone也只支持文本汇编,不支持像AsmJit那样的函数式汇编。

    ​ Keystone也移植自LLVM框架中MC组件的一部分,所以LLVM支持的CPU架构Keystone也都支持。Keystone支持的CPU架构有ARM、ARM64 (AArch64/ARMv8) Hexagon、MIPS、PowerPC、SPARC、System z、x86(包括16位、32位、64位)。

三、静态反汇编

​ 本节主要介绍常见的反汇编工具及其用法。在进行反汇编前,建议用PEiD等检测工具分析一下文件是否加壳了。如果已经加壳,就要先利用脱壳技术进行脱壳,再进行反汇编。常用的反汇编工具有IDA Pro等。虽然OllyDbg也有反汇编功能,但其侧重动态调试,反汇编辅助分析功能有限。IDA Pro是一款商业软件,属于专家级产品,是逆向工程的必备工具。

  1. IDA Pro

​ IDA安装成功后,会在桌面上生成两个图标,分别为IDA Pro(32-bit)和IDA Pro(64-bit),它们分别对应于32位和64位程序的分析。DA支持的文件类型非常丰富,除了常见的PE格式,还支持DOS、UNX、Mac、Java、NET等平台的文件格式。单击“File”→“Open”菜单项,打开目标文件ReverseMe.exe,IDA一般能自动识别其格式。

image-20240710164506460

​ IDA是按区块装载PE文件的,例如,text(代码块)、.data(数据块入、rsrc(资源块)、.data(输人表)和.edata(输出表)等。IDA反汇编所消耗的时间与程序大小及复杂程度有关,通常需要等待一段时间才能完成。

​ 此过程分为两个阶段。在第一阶段,将程序的代码和数据分开,分别标记函数并分析其参数调用,分析跳转、调用等指令关系并给标签赋值等。在第二阶段,如果IDA能够识别文件的编译类型,就装载对应的编译器特征文件,然后给各函数赋名。随后,IDA会创建一个数据库,其组件分别保存在扩展名为.id0、,idl、.nam和.til的4个文件里,这些文件的格式为IDA专用,在关闭当前项目时,这4个文件将被存档为一个IDB文件。一旦IDA创建了数据库,就不需要再访问这个可执行文件了,除非使用IDA的集成调试器调试这个可执行文件本身。再次分析该目标文件时,IDA只需要打开现有数据库,就会将界面恢复为上次关闭时的状态。
​ “Kernel optionl” “Kernel option2” “Processor option’”这3个选项可以控制反汇编引擎的工作状态,一般使用默认设置。IDA会自动识别程序类别与处理器类型,在大多数情况下,分析选项的默认值会在准确性与方便性之间提供一个折中的参数。如果IDA分析出了有问题的代码,将“KerneloptionI”中的“Make final analysis pass’”选项关闭是一一个很好的方法。在某些情况下,一些代码会因不在预计的位置而不被确认,这时选中“Kernel option2”域中的“Coagulate Data Segments in the finalpass”选项是有帮助的。

  1. ida的配置

​ 合理配置IDA文件可以大大提高工作效率。Windows图形界面的主程序是idag.exe,可通过“Options”(选项)菜单来配置IDA。但这种配置仅对当前的项目有效,在新建项目时会恢复成默认配置。要改变默认配置,必须编辑ida.cfg文件,该文件包含用于控制反汇编行的格式的选项。

​ 在IDA的cfg目录下查找IDA配置文件ida.cfg和GUI配置文件idagui.cfg。ida.cfg是一个文本文件,不能用Windows的“记事本”程序进行编辑。因为“记事本”程序对一些特殊字符的识别效果不好,如果继续编辑和保存文件,文件将被破坏,所以,建议用EditPlus、UltraEdit等工具来编辑ida.cfg等配置文件。
​ ida.cfg文件由两部分组成。第一部分定义文件的扩展名、内存、屏幕等;第二部分配置普通参数,例如代码显示格式、ASCII字符串显示格式、脚本定义和处理器选项等。另外,一些问题的出现也与ida.cfg有关。例如,MAX_ITEM_LINES默认为5000行。对许多大文件来说,可能会因行数不够而发生错误。
​ 要想显示与每个反汇编行有关的其他信息,可以通过“Options”→“General”命令打开IDA的常规选项,然后在“Disassembly’”选项卡中可用的反汇编行部分选择相应的选项。

image-20240710173146875

​ 要设置ASCII字符串风格,可单击“Options””→“ASCII String styles’”选项,打开字符串设置窗口。对应的ida,cfg的部分配置如下。

  1. IDA主窗口

​ IDA分析完目标程序后进入主窗口,界面看上去专业且复杂。IDA相当智能,会尽量分析程序各模块的功能,并给出相应的提示,例如为API函数的参数自动加上注释,相当直观。对那些IDA不能正常分析的代码,则需要进行手工辅助分析。

反汇编窗口:IDA-View是反汇编代码的显示窗口,它有两种形式,分别是图形视图(默认)和文本视图。在图形视图中,IDA以程序流程图的形式显示代码,将函数分解成许多基本块,从而生动显示该函数由一个块到另一个块的控制流程。用户可以使用“Ctrl键+鼠标滚轮”来调整图形的大小,使用空格键在图形视图和文本视图之间切换,或者选择右键快捷菜单中的“Text view’”选项切换到文本视图。选择“View”→“Open subviews’”→“Disassembly”选项,打开反汇编子窗口,就可以用多个子窗口来分析同一段程序,而不必来回翻页查看代码了。其他常用窗口,例如“Functions”和“Proximitybrowser”,也可以使用这个菜单打开。

导航栏:单击菜单项“Viev”→“Toolbars”→“Navigation”,打开导航栏,可以看到被加载文件地址空间的线性视图,“Library function’”为库函数,“Data”为数据,“Regular function”为规则函数,“Unexplored”为未查过的,“Instruction”为指令,“External$ymbol”为外部符号,用户可根据需要快速跳转到相关代码处。

image-20240710174829354

​ 在导航栏中执行右键快捷菜单项“Zoom in”和“Zoom out’”,可以调整导航条的显示比例。对手工分析来说,导航栏的作用非常大,选择适当的倍率可以达到意想不到的效果。

注释:使用IDA可以方便地在代码后面输入注释。在窗口右边空白处单击右键,将显示输人注释的快捷菜单项,一个是“Enter comment”(快捷键是冒号),另一个是“Enter repeatable comment’”(快键是分号)。按“;”键输人的注释在所有交叉参考处都会出现,按“:”键输入的注释只在该处出现。如果一个地址处有两种注释,将只显示非重复注释。

提示窗口:IDA界面下方的提示窗口是IDA的输出控制台,主要用于反馈各种信息,例如文件分析进度、状态消息、错误消息及IDA脚本或插件信息等。

字符串窗口:可以通过单击“View”+“Open Subviews”一→“Strings”选项打开该字符串窗口(Strings Window)。字符串窗口中显示的是从二进制文件中提取的一组字符串,双击窗口中的字符串,反汇编窗口将跳转到该字符串所在的地址处。将字符串窗口与交叉引用结合使用,可以快速定位程序中任何引用该字符串的位置。在字符串窗口单击右键,在弹出的快捷菜单中选择“Stup”选项,可以设置扫描的字符串类型。

输入窗口:输入窗口(Imports)中列出了可执行文件调用的所有函数。输人窗口中的每个条目都列出了一个函数名称,以及包含该函数的库的名称,每个条目列出的地址为相关函数的虚拟地址,双击函数,IDA将跳转到反汇编窗口的函数地址处。

image-20240711102223253

跳转到地址窗口:可以在反汇编窗口上下滚动,直至看到想要访问的地址。若知道目标地址,可以用IDA提供的快捷键“G”打开“Jump to address”(跳转到地址)窗口,输入一个地址(十六进制值),IDA会立即显示该地址的代码。

image-20240711102243042

​ 执行跳转功能后,当需要返回时,只要在工具栏中单击←按钮或按“Esc”键(“Esc”键是一个非常有用的快捷键,功能与浏览器的“后退”按钮类似),列表便会往后翻一页。若要往前翻一页,可以单击→按钮或按“Ctrl+Enter’”组合键。

  1. 交叉参考

​ 通过交叉参考(XREF)可以知道指令代码相互调用的关系。如图所示,“CODE XREF: sub_401120+B↑j”表示该调用地址是401120h,“j”表示跳转(jump)。此外,“o”表示偏移量(offset),“p”表示子程序(procedure)。双击此处或按“Enter”键可以跳转到调用该处的地方。

image-20240711102715156

​ 在“loc_401165”字符上按“X”键,将打开交叉参考窗口

image-20240711102815971

  1. 参考重命名

​ 参考重命名(Renaming of reference)是IDA的一个极好的功能,它可以将反汇编清单中的一些默认名称更改为有意义的名称,增加了代码的可读性。要修改一个名称,只需单击希望修改的名称(使其突出显示),并使用快捷键“N”打开更名对话框。

​ 如图所示,这段代码是窗口函数WndClass的开始处,IDA默认用“sub_401120”为其命名,但“sub_401120”这个字符没有太大的意义。

image-20240711103121227

​ 若加上了注释,则只有这一行才有意义。使用参考重命名功能便可一次性修改所有参考点。在“sub_401120”字符上单击右键,在弹出的快捷菜单中选择“Rename’”((重命名)选项,或者按“N”键,打开“Rename address’”对话框,如图所示。

image-20240711103357636

1
2
3
4
5
Local name:局部符号名的作用域仅限于当前函数。
Include in names list:勾选这个选项,将有一个名称被添加到名称窗口中,
Public name:由二进制文件(例如DLL)输出的名称。
Autogenerated name:自动创建符号名。
Weak name:弱符号,是公共符号的一种特殊形式。

​ 在此处赋予它“WndProc’”这个有意义的名字,然后单击“OK”按钮,马上就可以看到所有“sub_401120”标签的名称都变成新名称了,如图所示。

image-20240711105242279

  1. 标签的用法

​ 单击菜单项“Jump”→“Mark position”,打开“标记当前位置”功能,会出现如图所示的对话框。为这个标记(当前光标位置)加上标签,“WndProc’”标签就是需要返回的位置。当离开这个标记并返回时,选择菜单项“Jump”→“Jump to marked position’”,或者按“Crl+M”快捷键,执行“跳转到标记位置”功能,如图所示。选择返回的标签并双击,即可跳转到指定代码处。

  1. 格式化指令操作数

​ IDA可以格式化指令使用的常量,因此应尽可能使用符号名称而非数字,从而使反汇编代码更具可读性。IDA根据被反汇编指令的上下文、所使用的数据作出格式化决定。对其他情况,IDA一般会将相关常量格式化成一个十六进制常量。

​ IDA可以提供多种进制显示。将光标移到需要转换进制的常量上,单击右键,会弹出如图所示的上下文菜单。该菜单提供的选项可将常量格式化成十进制、八进制或二进制的值。

image-20240711112655728

​ 在大部分情祝下,源代码中使用的是已命名的常量,例如define语句。IDA维护着大量的常见库(例如C标准库、Win32API),用户可以通过右键快捷菜单中的“Use standard symbolic constant”(使用标准符号常量)选项来设置常量,如图所示。

image-20240711112910544

​ 在本例中,根据Create Window参数,确定80000000h处对应的符号是CW_USEDEFAULT,并得到如图所示的反汇编行。

image-20240711113050986

  1. 函数的操作

​ IDA允许手动干预创建、编辑、删除函数。新函数由不属于某个函数的现有指令创建,或者由未被IDA以任何方式定义的原始数据创建。将光标移到要创建函数的第1个字节上,选择“Edit”→“Functions’”+“Create Function”选项,创建一个函数(快捷键是“P”)。必要时,IDA会将数据转换成代码,以便分析函数的结构。如果能找到函数的结束部分,IDA将生成一个新的函数名,以函数的形式重组代码。如果无法确定函数的结束部分或者发现非法指令,这个操作将会终止。删除函数时,可以使用“Edi”→“Functions’”
+“Delete Function’”命令。

  1. 代码和数据的转化

​ 很多工具在进行反汇编的时候可能无法正确区分数据和代码,IDA也不例外,数据字节可能会被错误地识别为代码字节,而代码字节可能会被错误地识别为数据字节。有些程序就是利用这一点来对抗静态反汇编的。IDA的交互性使用户可以将某段十六进制数据指定为代码或数据,即利用人脑来区分代码和数据。

​ 如果确信某段十六进制数据是一段指令,只要将光标移到其第1个字节的偏移位置,执行菜单命令“Edit”→“Code”或按“C”键即可。按“P”键可以将某段代码定义为子程序,并列出参数调用。若要取消定义,可以执行菜单命令“Edit”→“Undefine’”或按“U”键,数据将重新以十六进制形式显示。这种交互式分析功能的介人,使IDA达到了非交互式软件无法达到的效果。

​ 在代码行按“D”键,数据类型会在db、dw与dd之间转换。执行菜单命令“Options”→“Setupdata types’”,可以设置更多的数据类型,如图所示。

image-20240711140958687

  1. 字符串

​ 编程语言的不同造成了字符串格式的不同,例如以“0”结尾的C语言字符串及以“$”结尾的DOS字符串等。IDA支持所有字符串格式。如果确信某段十六进制数据是一个字符串,那么只要将光标移到其第1个字符的偏移位置,执行菜单命令“Edit”+“Strings’”→“ASCII”或按“A”键即可。

image-20240711141305500

​ 按“A”键设置默认是C语言字符串。也可以选择菜单项“Options”+“ASCII string style”,设置其他字符串格式的默认值。

​ IDA有时无法确定ASCII字符串,发生这种错误的原因是这个字符串在程序中没有被直接调用。在本例中,按“G”键,输入地址“0040478E”,会来到如下代码处。

image-20240711143945397

​ 将光标移到0040478Eh处并按“A”键,该处就会被定义并生成一个变量名。如果要将其恢复可按“U”键。IDA会给生成的字符变量加一个前缀“a”,例如“aGetfiletype db’GetFileType’,0”。可以在“Names”窗口看到这些字符串变量(单击按钮N或选择菜单项“View”→“Open subviews“→“Names”即可打开这个窗口)。

  1. 数组

​ IDA有着较强的数组聚合能力。它可以将一串数据声明变成一个反汇编行,按数组的形式显示,从而简化反汇编代码清单。

​ 用IDA打开实例Arrays.exe,找到一个数组,其汇编代码为 mov edi, dword_407030[eax]

image-20240711144459125

​ 将光标移到需要处理的数据处,选择菜单项“Edit”→“Amy”或按“*”键,打开数组排列调整窗口,如图所示。若在“I tems on a line’”文本框中填“0”,每行项数会根据页面自动调整;若想让每行显示更多的数据,可以在反汇编选项中调整右边距(Right margin)。

image-20240711144444698

​ 设置完成,数据按1×3的形式排列

image-20240711144731224

  1. 结构体

​ 在C语言中,结构体(struct)是一种数据结构,可以将不同类型的数据结构组合到一个复合的数据类型中。结构体可以被声明为变量、指针或数组等,从而实现比较复杂的数据结构。

创建结构体对一些常见的文件类型,IDA会自动加载相应的类型库,例如vc6win(Visual C++6.0)。在进行底层分析时,可以增加mssdk(windows.h)、ntddk(ntddk.h)等。这些类型库中有相应的结构体,用户分析代码时可以直接引用。按“Shift+F11”快捷键,打开加载类型库窗口(Loaded Type Libraries),如图所示。

image-20240711145504295

​ 单击右键,在弹出的快捷菜单中选择“Load Type Library”选项(或按“Insert”键),在弹出的“Available Type Libraries’”窗口中选择类型库,如图所示。

image-20240711145741545

​ 此时就可以查看内置的结构体数据结构了,选择“View”→“Open subviews”→“Structures”菜单项,打开结构体管理窗口。按“Insert’”键,在弹出的窗口中单击“Add Standard Structure”按钮打开添加标准结构库窗口,查找需要的结构名,就可以正常使用这些库了。

​ 在默认情况下,IDA会加载常用的结构。在结构体管理窗口按“Insert’”键,然后单击“Cancel”按钮,ReverseMe程序内常用的结构体数据结构就会显示出来。在WNDCLASSA结构一行双击,展开结构,在程序代码的相应位置会直接以结构体的形式显示,如图所示。

image-20240711150256117

​ IDA会通过各种措施来改善结构体代码的可读性。如果程序正在使用某个结构体,而IDA并不了解其布局,IDA将允许用户自定义结构体,并将自定义的结构体放到反汇编代码清单中,例如下面这段c程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h> 

struct student
{int id;
char name[20];
int age;
};
struct student stu[2]={{01,"Mary",14},{02,"Angela",15}};
int main(void)
{
struct student *p;
for(p=stu;p<stu+2;p++)
printf("%5d %-20s%4d\n",p->id,p->name,p->age);
return 0;
}

​ 如图所示的代码是IDA在反汇编时由于没有定义结构体而自动生成的。

image-20240711151331553

​ [esi+18h]等是调用的结构体中的数据,可以用有意义的名字来代替这些无意义的数字。双击“unk_407030”字符,来到结构体数据处,利用“D”键、“A”键或image-20240711151514542按钮(数组的项数设置为20)重新定义数据,结果如下。

image-20240711151640347

​ 打开结构体窗口,按“Insert’”键增加一个结构体类型“student”。

image-20240711151736793

​ 按“D”键加入数据(例如id、age)。重复按“D”键,在db、dw和dd之间切换,直至变成“dd”(表示dword类型)。按“A”键加入的ASCII字符(例如name)为结构的成员。此处数组大小为20,

image-20240711153117129

​ 如果要创建一个大小可变的结构体,可以将此处自定义的数组元素大小设为0。新增结构成员时,IDA会自动为其命名。

​ 现在,将光标定位在00407030h处,执行菜单命令“Edit”→“Structs’”→“Struet var’”,将出现如图所示的窗口,供用户选择结构体类型。

image-20240711153256314

​ 选择student结构体,单击“OK”按钮,即可将数据纠正过来。使用同样的方法,重复执行“Struct var’”命令,将0040704Ch处的数据转换成student类型。转换后的数据如图所示。

image-20240711153213965

​ 最后,可以在操作数类型中重新定义现有数据。选中需要重新定义的数据,例如[esi+18h],单击菜单项“Edit”→“Operand types”→“Offset””→“Oset(Struct)”或按“T”键,执行结构偏移功能,选择student结构体,依次将[esi],[esi+4]重新定义效果。即使结构体中的成员较多,也不必逐一替换。IDA提供了批处理操作,可以通过一次操作完成全部工作。选择所有需要替换的代码,执行“Oset(Struct)”菜单命令或按“T”键,打开结构偏移设置窗口,如图所示。窗口右边显示了与si有关的所有操作,成员名前面不同的符号表示计算后的状态,“√”表示完全匹配。

image-20240711153658753

​ IDA还可以在已经分析好的数据中建立结构体。在00407030处选择一块已经重新组织的数据,使用菜单命令“Edit”→“Structs’”+“Create struct from data’”创建结构体。

  1. 枚举类型

​ 可以在反汇编时用IDA动态定义和操作枚举类型(Enumerated Types)。看看下面这段简单的C程序,在用IDA进行反汇编后,得到了一些没有意义的数字。

1
2
3
4
5
6
7
8
9
#include <stdio.h> 
int main(void)
{
enum weekday { MONDAY, TUESDAY, WEDNESDAY, THUSDAY, FRIDAY, SATURDAY, SUNDAY };

printf("%d,%d,%d,%d,%d,%d,%d",MONDAY,TUESDAY, WEDNESDAY, THUSDAY, FRIDAY, SATURDAY, SUNDAY );

return 0;
}

image-20240711162955968

​ 可以用枚举类型来表示这些数字。执行“View”→“Open subviews”→“Enumerations’”选项,打开枚举窗口,按“Insert’”键插入一个新的枚举类型“weekday”。在新建的weekday枚举类型中按“N”键添加枚举成员,如图所示,“0”对应于“MONDAY”,“1”对应于“TUESDAY”,依此类推。

image-20240711163238153

​ 可以在操作数类型中重新定义现有数据。将光标移到需要重新定义的数据处,可以执行菜单项“Edit”→“Operand types””→“Enum member”或按“M”键将其转换成指定的枚举成员,也可以在选中数字后执行右键快捷菜单中的“Symbolic constant”命令。处理后的代码如图所示,IDA用“MONDAY”“TUESDAY”等代替了无意义的数字0、1等,使代码变得易读了。

image-20240711163511846

  1. 变量

​ 先来看一段用W32Dasm反汇编的ReverseMe的代码。在如下代码中,参数的传递过程不够明确,只知道一些数据传人了这个函数,因此可以进行改善。

image-20240711163709990

​ IDA会自动认出哪些参数被放到了栈中,代码如下。

image-20240711164441734

​ 与前面一样,在IDA里可以给传递的变量赋予有意义的名称。在任何函数栈(例如Msg)上双击或按“Ctl+K”快捷键,打开栈窗口,将光标移到tagMSG上,即可显示各结构成员。

  1. IDC脚本

​ IDA集成了一个脚本引擎,可以让用户从编程的角度对IDA的操作进行全面控制。脚本的存在极大地提高了IDA的可扩展性,使IDA中许多重复的任务可以由脚本来完成,用户可以在使其自动化的同时对一些特殊情况进行控制。IDA支持使用两种语言编写脚本,分别是IDC和Python。IDA的原始嵌入式脚本语言叫作IDC。IDC本身是一种类C的语言脚本控制器,语法与C语言类似,简单易学。IDA从6.8版本开始直接支持Python集成式脚本,更加灵活、方便。所有的IDC脚本中都有一条包含idc.idc文件的语句,这是IDA的标准库函数,变量定义形式为“auto var’”,其他逻辑、循环等语句与C语言类似。相关语法和函数功能,请查看IDA帮助系统中的相关主题。

  1. IDA调试器

​ IDA支持调试器功能,因此很好地弥补了静态分析能力的不足,将静态分析与动态分析结合起来,提高了分析的效率。

加载目标文件使用IDA打开目标软件。这时使用菜单项“Debugger’”→“Select Debugger’”,将根据当前的文件类型显示适合的调试器列表.此时,“Debugger’”菜单会以其他形式展开,可以单击菜单项“Debugger’”一“Start Process’”调试目标文件。

image-20240711173636697

​ 另一种调试目标文件的方式是附加到一个正在运行的进程上。能否使用IDA调试器附加进程的方式,取决于目前IDA是否打开了可执行文件。如图所示,单击菜单项“Debugger’”→“Attachto process..”即可附加进程。

image-20240711173947923

​ IDA调试器除了能进行本地调试,还能进行远程调试。对于远程调试,IDA自带大量的调试服务器,包括用于Windows3264、Windows CE/ARM、Mac OS X32/64、Linux32/64/ARM和Android的服务器。运行远程调试服务器后,IDA将与该服务器通信,在远程计算机上启动目标进程。

调试器界面:IDA进人调试器模式后,界面上将显示几个默认的窗口

image-20240711175250107

​ 除此外,在“Modules’”窗口中显示了所有加载到进程内存空间中的可执行文件和共享库。双击模块名称将打开该模块输出的符号列表。

调试跟踪:调试器的基本功能是跟踪所调试目标进程的行为。IDA在“Debugger”菜单里提供了相应的调试命令,每个命令都有相应的快捷键,常用的快捷键如表所示。

image-20240711180908715

image-20240711180918985

断点:断点是调试器的必备功能。设置断点的目的是在程序中的特定位置中断。设置断点的快捷键是“F2”,右键快捷菜单中的对应选项是“Add Breakpoint”。已经设置断点的地址将以一条红色的光条突出显示。再次按下“F2”键将关闭断点,从而删除它。使用菜单项“Debug8er”→“Breakpoints“→”Breakpoint List”可以查看当前设置的所有断点。

​ IDA调试器支持条件断点。设置断点后,在右键快捷菜单中选择“Edit Breakpoint”选项,打开如图所示的对话框。“Location”设置框中是断点的地址。勾选“Enabled”复选框,说明该断点当前处于活动状态。勾选“Hardware”复选框,表示以硬件断点的方式实现该断点。

image-20240711181745462

​ 在“Condition’”设置框中输入一个表达式,即可创建条件断点。如表所示,可以为软件断点和硬件断点指定条件。IDA断点的条件通过IDC表达式指定。

image-20240711181902234

跟踪:IDA的跟踪(Tracing)功能类似于OllyDbg的Run trace功能,可以将调试程序执行过程中的事件记录下来。跟踪分为两类:一类是指令跟踪,通过菜单项“Debugger’”→“Tracing”→“Instruction Tracing”调用,IDA负责记录地址、指令和寄存器的值;另一类是函数跟踪,通过菜单项“Debugger”→“Tracing”+“Function Tracing’”调用,用于跟踪call指令的调用,并将结果记录下来。

  1. 远程调试

​ IDA Pro支持通过TCP/IP网络对应用程序进行远程调试,例如远程调试Windows、Linux、Android和Mac OS X二进制文件。IDA附带了用于实现远程调试会话的服务器组件。运行IDA Pro界面的系统称为调试器客户端,运行被调试应用程序的系统称为调试器服务端。除了设置并建立远程调试服务器连接,远程调试与本地调试没有太大的区别。

​ 在进行远程调试前,要在远程计算机上启动相应的调试服务器组件,它会处理所有底层执行和调试器操作。在IDA文件目录dbgsrv里提供了服务器组件。

​ 此外,IDA可以与使用gdb server的远程gdb会话进行连接。连接远程gdb服务器的过程与连接远程IDA调试服务器的过程基本相同。因为连接gdb_server时不需要密码,IDA无法获知运行gdb_server的计算机的体系结构,所以,需要为其指定处理器类型(默认为Intel x86),可能还需要指定该处理器的字节序。

三、十六进制工具

​ 常用的十六进制工具有Hex Workshop、WinHex、Hiew等,它们各有特色:Hex Workshop提供了文件比较功能;WinHex可以查看内存映像文件;Hiew可以在汇编状态下修改代码。

四,静态分析技术应用实例

  1. 解密初步

​ 现在的软件一般采取人机对话方式,因此,从提示信息入手很快就能找到要害。运行CrackMe.exe程序,随意输入几个字符,将出现如图所示的提示信息。

image-20240712144052925

​ 用IDA对CrackMe.exe进行反汇编。文件不大,向下翻查,很快就来到了如下代码处。

image-20240712144434829

​ 注意以下几句,它们比较关键

image-20240712145155140

​ 要想让程序接受任何注册码,只要把”je”(不相等就跳转)改成“je”(相等就跳转)或空指令“nop”即可。

image-20240712150147388

​ 然后,单击菜单项“Patch program”一“Apply patches to input file”,将修改保存到文件中。此时,输入任何序列号,CrackMe.exe均提示注册成功。这种跳过算法分析直接修改关键跳转指令使程序注册成功的方法,通常被解密者称为“爆破法”。

​ 此例的算法只是将用户输入的序列号与参照值进行比较,以判断真伪,其核心就是这句比较指令。

image-20240712150341228

​ 这种直接比较的程序,其参照值一般会存储在程序中。在大多数情祝下,编译器会将初始变量放在数据区块(.data区块)中。用十六进制工具打开文件,跳到.data区块处,会发现一个字符串“9981”,这就是正确的密码,如图所示。

image-20240712151917648

​ 所以,在编写注册码验证功能的过程中,不要让正确的注册码直接出现在程序中。另外,不要使用明显的提示信息,以防止信息被解密者利用并快速找到判断的核心。

  1. 逆向工程初步

​ 通常将为了练习逆向工程而特别编写的程序命名为“ReverseMe’”。本例ReverseMe01.exe有如下要求。
​ ● 移去“Okay,for now,mission failed”对话框。
​ ● 显示一个MessageBox对话框,上面显示了用户输入的字符。
​ ● 再显示一个对话框,用于告知用户输入的序列号是正确的还是错误的(“Good”或“Bad serial”)
​ ● 将按钮标题由“Not Reversed”改为“-Reversed-”。
​ ● 使序列号为“pediy”。

移去“Okay,for now,mission failed”对话框:用IDA打开ReverseMe(01.exe并进行反汇编。查看“Strings”窗口,双击“Okay,for now,missionaled!”转到定义此字符串的代码处。双击后面的交叉参考,转到调用此字符串的代码处,

image-20240712152400207

​ 按要求,可以用nop指令代替此处的代码,也可用一句跳转指令跳过此提示窗口部分即可。

将输入的字符显示到对话框中:用于获取编辑框字符的函数有Get WindowText、GetDIgItemText等(可在程序的输人表中查看)。在IDA中,输人、输出等函数显示在“Name”窗口中。在“Name”窗口中双击GetWindowTextA函数,来到调用处,会发现有两处调用了此函数,其中第一处比较可疑,具体如下。

image-20240712160139183

​ 从上面的分析可知,只要将00401211h处的指令nop掉(即将机器码改成“9090”),ReverseMel01就可以执行这段代码了。用Get WindowTextA函数将文本控件中的内容取出,放入缓冲区4030CCh。用MessageBoxA函数从该缓冲区中读取文本并将其显示到消息框中。

​ 至此我们已经基本掌握了静态分析相关工具的使用,但这只是第一步。我们还需要掌握一定的逆向分析技能,才能更好地调试和分析程序,探索软件的最深处。