【编程技术-Ghidra反编译器的设计与实现(2)——分析最小exe文件】此文章归类为:编程技术。
前文介绍了Ghidra反编译器的调试环境搭建,相信看到上篇文章的读者都已经把环境搭建起来了,当然要是没有搭建起来也是可以看本篇文章的,不过少了些动手的乐趣,让我们开始吧。
本文以一个最小exe文件来分析反编译器的流程,IDA载入该exe文件,可以看到在0x140000300的程序开头处是一条比较语句,Ghidra如何处理这条指令呢?

打开安装有Ghidra的Ubuntu虚拟机,用VS Code远程打开Ghidra/Features/Decompiler/src/decompile/cpp目录,F5开始调试。
首先载入要分析的exe文件,指令是
load file /mnt/hgfs/prj/test/tt3.exe
在sleigh.cc文件ParserContext *pos = obtainContext(baseaddr, ParserContext::pcode);
下好断点,然后执行
load addr 0x140000300
不出意外程序会停在下好的断点处。
进入这个函数,可以看到

首先需要分析的是resolve()函数,这个函数完成的是反汇编工作。
在resolve()函数中首先的重点是
ct = root->resolve(walker)
resolve(walker)函数内部执行了很复杂的匹配工作,主要根据cmp eax,3的机器码83 F8 03得到ct,这个匹配很复杂,好在我们可以直接忽略这个函数,不影响我们的分析。
得到ct后会根据特定脚本文件的内容建立内存中的树状结构,这牵涉到Ghidra反汇编器的基本机制。
在Ghidra\Processors\x86\data\languages文件夹下有一个脚本文件ia.sinc,这个文件是x86_64架构cpu指令集的描述文件,之所以是这个文件会被采用是因为我们载入的exe是这个指令集。
对于比较语句“cmp eax,3”首先会对应到ia.sinc的3062行
:CMP rm32,simm8_32 is vexMode=0 & opsize=1 & byte=0x83; rm32 & reg_opcode=7 ...; simm8_32 { local temp:4 = rm32; subflags(temp,simm8_32); local diff = temp - simm8_32; resultflags(diff);
这个脚本首先是
CMP rm32,simm8_32
这代表这这条指令的构成方式,在resolve()函数接下来的程序执行中会解析这个脚本文件,首先得到rm32的信息,这在ia.sinc的1487行
rm32: Rmr32 is mod=3 & Rmr32 { export Rmr32; }rm32又依赖Rmr32,程序继续分析脚本文件会引用ia.sinc的848行内容
Rmr32: r32 is rexBprefix=0 & r32 { export r32; }至此“cmp eax,3”的第一个操作数确定下来了。接下来程序分析第二个操作数simm8_32,这会引用ia.sinc的924行
simm8_32: simm8 is simm8 { export *[const]:4 simm8; }这样“cmp eax,3”这条指令就被转换为了内存中的数状结构以便接下来的程序继续处理。- 反汇编结构就分析到这,接下来是resolveHandles()函数,这个函数中会继续分析“cmp eax,3”汇编语句机器码83 F8 03的后两个字节,对汇编比较熟悉的朋友知道F8字节用于确定cmp指令的第一个操作数是哪个寄存器,在ia.sinc的3062行首先可以看到
byte=0x83
刚好“cmp eax,3”的机器码的第一个字节是0x83,所以“cmp eax,3”确实会绑定到ia.sinc的3062行。继续看脚本的3062行,接下来是
rm32 & reg_opcode=7
搜索ia.sinc可以在536行可以看见
reg_opcode = (3,5)
这代表这reg_opcode表示一个字节中的3,4,5三个比特位。由于reg_opcode=7
所以比较指令“cmp eax,3”的第二字节只能表示为:xx111xxx,x表示不定。
继续查看上面的这条
rm32: Rmr32 is mod=3 & Rmr32 { export Rmr32; }其中mod为3,而mod在ia.sinc的535行
mod = (6,7)
mod为3就表示一个字节中的第6,7比特位全为1,所以比较指令“cmp eax,3”的第二字节只能表示为:11111xxx,x表示不定。现在只差最低位的三个比特没有确定了,继续看上面的这条
Rmr32: r32 is rexBprefix=0 & r32 { export r32; }这条中的r32在ia.sinc的560行
r32 = (0,2)
这代表r32是一个字节中的最低3位。比较指令“cmp eax,3”中的不同寄存器就被编码为不同的这3个比特,由于只有3个比特所以最多可以表示8个不同的寄存器。看ia.sinc的690行attach variables [ r32 reg32 base index ] [ EAX ECX EDX EBX ESP EBP ESI EDI ];
这就确定了r32的值对应的寄存器,对于“cmp eax,3”,由于是寄存器eax,所以比特位为000,比较指令的第二字节为11111000,刚好是0xF8。分析完了“cmp eax,3”的机器码83 F8 03的第二个字节,resolveHandles()函数接下来会将exe文件中的0x140000300处的第三个字节03提取出来,以便后用。
好了,不早了,明天继续分析“cmp eax,3”如何对应到中间代码表示,Ghidra又是如何支持多种CPU指令集,明天见。
更多【编程技术-Ghidra反编译器的设计与实现(2)——分析最小exe文件】相关视频教程:www.yxfzedu.com