从零开发一个反汇编引擎,手工分析Opcode???

很久以前就想着自己开发一个反汇编引擎了,不久前又对这方面有兴趣,所有就研究了一下,其实也就那么一回事,不算太难的东西,语言基础比较好的兄弟可以去读一下微软的官方文档《64-ia-32-architectures-software-developer-instruction-set-reference-manual-3253》里面的2.2 IA-32E MODE和附录A,其它可以读一下《ArtofDisassembly》。

开始前请先下载一些表,这些表用来解析Opcode的,下面每一处都用到这些表,当然自己有的话就不需要了,下载传送门Opcode解析表下载

我们现在开始,先了解Opcode的基本结构。如图所示:

一个Opcode是由前缀(prefix),主操作码(code),ModR/M,SIB,偏移量(offset),立即数(Immediate)构成的,下面分别介绍。

1. 前缀(prefix)

指令前缀可以分成位四组,不是必须的,也就是说指令中可以不存在前缀的。

前缀最多可以有四个。

如图所示:

上图也有一些各个前缀的介绍了,这里在进行补充, 0FH-Lock前缀在多处理器环境下强制执行独占共享内存操作,这个没怎么研究,感觉没啥用处啊。

而哪两个F2H,F3H重复前缀是MOVS,CMPS,SCAS,LODS,STOS,INS,OUTS等字符串操作或I/O指令才能使用这些前缀,其它指令也用这个前缀会导致出现不可预知的行为。当然右边框框里面的介绍看到没有,一些双字节的指令或者三字节的指令是需要F2H,F3H指令前缀的。

其它的指令前缀没啥需要注意的了,直接举一些例子吧。

比如说66H操作数大小重载指令前缀,允许在16位和32位操作数之间转化

正常的情况下下的指令 32位

但是如果加了66H前缀下,将会得到 16位

这就是因为66H前缀将操作数的大小变小了

详细指令前缀传送门:指令前缀

2. 主操作码(code)

主操作码是必须的,也就是说每一条指令都是必须要用到主操作码的,最少有一个,最多有三个。

一字节主操作码例子:60H,对应的反汇编也就是PUSHA

两个节主操作码例子:0F06H,对应的反汇编也就是CLTS

三字节主操作码例子:0F3800H,对应的反汇编也就是PSHUFB Pq Qq(这个还有其它情况)

所有主操作码有三个表(实际上是4个)

第一个就是A-2单字节表

第二个就是A-3双字节表

第三个就是A-4三字节表 其实还有A-5三字节

3. ModR/M

这个不是必须的,最多只有一个字节而已,是紧挨这主操作码的,进行辅助的。

可以分为三个域 Mod,Reg,R/M 每个域都有不同的意思,后面介绍。

不需要的ModR/M的例子:60H

将所有的寄存器入栈,不需要源操作数的目的操作数的

需要ModR/M的例子:A110000000H

这里的EAX和[0x10]就是依据ModR/M得到的。

4. SIB

这个不是必须的,最多只有一个字节而且,是在ModR/M后面的,进行辅助。

也是可以分为三个域:scale(比例因子),index(索引寄存器号),base(基址寄存器号)

5. 偏移量和立即数

这个也不是必须的,可有可无的,也就是直接在指令里面的,不需要什么解析。

这里也给一个例子:D555H

前面的55也就是后面的0x55.也就是立即数

 

总的来说,指令的解析也就是不断的查表查表再查表,主操作码要查表,ModR/M要查表,SIB要查表,差不多有二十个表,如图所示:

多是多了点,但是常用的也就两三个表。我们继续。

接下来就要说一下怎么看这些表,第一张表 A-2 单字节表

表大了点这里截取一部分而已,操作码表由十六进制数组织,高低4位表示一个操作码字节.单字节操作码通过高4位索引行,低4位索引列,比如说 主操作码是13H的话,那对应的汇编指令那就是 ADC Gv,Ev ,那OK,ADC后面的Gv和Ev是什么鬼东西??那就是寻址方式和操作码大小,大写的字母是代表寻址方式,小写的字符也就是代表操作码大小啦,什么意思?

寻址方式决定了后面地ModR/M和寄存器,而操作码大小就决定了单字节,双字节,四字节这些。比如大写的G代表了ModR/M为通用寄存器,由ModR/M的Reg域提供寻址,这样子通过对应的Reg域也就得到了对应的寄存器,决定是EAX还是EDX。而小写的v就代表着字大小,双字大小,四字大小这三个大小,但是究竟是用哪一个呢,还是要依据操作数进行决定。哈哈哈是不是有一点头晕,我当初也是,当初我一个人摸索来摸索去的....

如果主操作码是80H,对应的是???Grap是什么指令啊??其实这里又引出了另外一张表A-6 Gruop表,这张表里面需要依据ModR/M的域信息判断是哪一个汇编指令了哈哈哈

Gruop表是这样子的:

如果主操作码是80H,那么去找Grap 1,那也就是第一行咯,后面就有ADD,OR,ADC,SBB...可选项,那究竟是哪一个呢?那就要根据ModR/M的Reg域了,域位000那就是选择ADD,域位001那就选择OR,以此类推,。

其它的主操作码表A-3 A-4 A-5也差不多一个样的。

得了,我们现在开始手工分析一下opcode,直接选user32.dll

直接拖进OD来,得到

我们先随便拿一些指令来分析分析练练手,其它那一些特殊的指令再说吧。

一开始我们就看到8BH,我们就可以知道它是主操作码,因为它不是前缀指令啊,前缀指令只有F0 F2 F3 2E 36 3E 26 64 65 66 67,指令前缀后面就是主操作码了,所以我们得到8BH就是主操作码。

拿到主操作码8BH,我们就先要查看A-2单字节表,如图所示

我们看到8BH对应的就是 MOV Gv,Ev,那我们就找到这是一条MOV指令,对比一下OD的反汇编代码,OK,一样的哈哈哈。MOV指令都是有两个操作数的吧,Gv对应的就是目标操作数,Ev对应的也就是源操作数。G代表着主操作码后面一定有着ModR/M,目标操作数是一个通用寄存器,而且ModR/M的Reg域为目标操作数提供寻址,如图所示

那E代表着源操作数可以是一个寄存器或者是一个内存地址,有ModR/M的RM域提供寻址,如图所示

v代表着大小,可以是字节 双字节 四字节,如图所示

好,根据主操作码我们就得到了这么多信息,接下来我们看看紧跟其后的ModR/M,也就是FFH,我们查看ModR/M表,如图所示

FFH进行解析 Mod域为11 Reg域为111 R/M域为11,而Reg的111对应的是EDI寄存器,所以我们可以得到MOV EDI,Ev了,再由R/M也是111,也是对应EDI寄存器,所以得到完整的反汇编指令MOV EDI,EDI,对比一下OD,一模一样啊。

再接下来,看到55H,判断也不是指令前缀,直接查找A-2单字节表,如图所示

因为没有操作数大小重载前缀66H,所以直接就得到了指令 PUSH EBP,如果有操作数大小重载前缀66H的话,那就得到PUSH BP。

再继续看,也是一条8BH,得了,和上边一模一样的解析手法,这里就不解析了,留读者自己解析,再参考OD的看看对不对。

再接下来看,看到83H,判断也不是指令前缀,所以我们查看单字节表。看到的居然是....如图所示

好,看到了Grap,我们就知道要去查找Gruop表了,查Grap 1,有Gruop就一定会有ModR/M的,如图所示Gruop表

我们可以找到,从主操作码80H到83H,全部都是Gruop 1,所以我们就要看后面了,究竟是选择哪一个呢???选哪一个还是要看后面的ModR/M的Reg域呢,我们看一下ModR/M是什么,是7DH,可以分析Mod域为01 Reg域为111 R/M域为101。如果不会分析各个域的话,那就用计算器,7DH如图所示

点几下就找到各个域了对不对?好了现在我们继续,Reg域得到111,Gruop 1的111对应的就是CMP指令啊,所以我们得到CMP Ev,Ib。E分析过了,v分析过了,我们分析I,大写I就代表着立即数。如图所示

而小写的b呢,那就代表着大小是byte的,也就是一个字节的大小咯,如图

上面ModR/M各个域分析出来了吧,E是R/M域进行寻址的吧,R/M也就是101,也就是对应[EBP+disp8],那disp8也就是代表8位,也就是一个字节,当然disp32也就是4个字节大小咯。那disp8去哪里得到呢???那就直接在十六进制指令中的,也就是ModR/M后一个咯,也就是0CH。OK,我们现在可以得到指令CMP [EBP+0CH],Ib了,接下来我们继续,立即数也是直接在十六进制中的,而且b说明了大小,也就是byte大小,所以我们继续取得0CH后面的一个byte,也就是01H,OK,现在完整的反汇编代码出来了,那也就是CMP [EBP+0XH],01H,当然不能忘记还要说明大小的dword ptr,那为什么OD里面显示的不是这个???只是OD把[EBP+0CH]转化为符号了,反汇编代码的一般函数开头不都是push ebp 然后再mov ebp esp创建新的栈帧么,OD识别到[EBP+0CH]也就是第二个函数参数的意思,将[EBP+0CH]转化为Arg.2而已,不信的话可以双击该指令就可以看到如图

这里我也有一处地方不明白,

也就是807D0C01对应CMP BYTE PTR SS:[EBP+CH],1H

66837D0C01对应CMP WORD PTR SS:[EBP+CH],1H

837D0C01对应CMP DWORD PTR SS:[EBP+CH],1H

v代表可以是字节大小,双字节大小,四字节大小,这里的DWORD PTR是依据哪里得来的呢?难道是依据EBP么?将EBP修改为BP,得到【对不起,不支持16位地址】。

【这里先留着,今天就先不写先。。。。待续】

再说说反汇编引擎吧,我看过几个不同的反汇编引擎(只是粗略的看了一下),我觉得还是自己写一个吧,我觉得别人写的太多分支语句了,不是一大堆的switch就是一大堆的if..else..,感觉蛮乱的,我的思路就是将不同的表按照某种格式保存好。比如拿单字节主操作码来说,传入某个函数33H,就得到XOR Gv,Ev,然后再将寻址方式和操作数大小再传入某个函数得到相应的解析,当然某个函数里面也不会是一大堆的判断语句(只是说避免大堆的判断而不是说不用判断语句)....写写又不大想写了,因为感觉再继续写下去会整个代码偏离我的预想了,所以现在暂停了下来,反正现在也不需要这个东西,留给以后吧,这个半成品代码和我看过的反汇编引擎我都放在Github,有兴趣的读者可以去看看。

猜你喜欢

转载自blog.csdn.net/zzy1448331580/article/details/93354682