首页
社区
课程
招聘
[原创]32位反汇编引擎开发笔记:Step.4_变长指令复习1
发表于: 2020-10-9 15:59 5344

[原创]32位反汇编引擎开发笔记:Step.4_变长指令复习1

2020-10-9 15:59
5344

Step.1_引擎大纲

Step.2_框架搭建

Step.3_经典定长指令的解析

Step.4_变长指令复习1

Step.5_变长指令复习2 - 指令前缀

Step.6_引擎核心->函数封装

上一章我们对经典定长指令进行了解析,引擎已经能完成对定长指令的解析,但是硬编码的世界里绝大部分都是变长指令,那么在解析变长指令之前,我们需要复习一下指令的相关知识

对于一条指令来说,我们可见的就是一个或多个字节组成,这些字节我们称为硬编码

但是指令真的就像表面上的几个字节那么简单吗?为了了解指令结构,我们打开Intel白皮书第1643页

上面清晰的为我们展示描述了一条指令的组成部分:前缀,Opcode指令码,ModRM,SIB,偏移,立即数

例如C2 1234->RETN 0X3412,则是由一个Opcode->C2加上一个立即数1234组成

既然Opcode 偏移 立即数我们都已经接触到了,那么其余3部分在哪里呢?

这就是变长指令所用到的部分

首先我们要明白ModRM的结构,它是由8位二进制数组成,也就是一个字节.这一个字节的数据并不是表面上看起来那样是一个整体,而是由三部分组成:0~2 -> R/M,3~5 -> Reg/Opcode,6~7 -> Mod,上图中也清晰的描述了这种结构.

既然前面说到了变长指令所用的部分为前缀 MODRM SIB,那么MODRM在其中起到了什么作用呢?什么时候才会起作用呢?

白皮书中依然为我们清晰的标明了什么时候MODRM字段会发挥它的作用,打开1475页,观察Table A-2

我们会发现上面有一部分是我们熟悉的汇编指令,另外一部分却是类似Eb,Gb Ev,Gv一类的东西.当一个硬编码对应这种结构,那么就说明这个字节的硬编码将无法确定当前指令的长度,其后紧接的一个字节将变为ModRM吞入当前指令,这就是我们所说的变长指令,而整个指令的长度将由接下来的ModRM字节继续进行确定

既然了解了ModRM的结构,作用,何时起效.那么接下来我们就要继续探寻ModRM字段的具体作用,打开白皮书第41页 -> Table 2-2,上面依然为我们清晰的列出了ModRM字段的拆解说明

想要解析2-2,我们需要先回到A-2中弄清楚Ev,Gv之类的东西是个啥

上面我们说了变长指令是怎么形成的,而变长指令的最终样子则遵守Ev,Gv这种格式,在这个格式中,每个字母都有其不同的含义:

E: 寄存器/内存

G: 通用寄存器

b: 字节

v: 字,双字,四字 最终V是什么取决于当前程序的运行环境,若是8位则是字.32位则是双字,64位则是四字

也就是说,EvGv格式决定了当前指令操作的寄存器 内存是什么.那么操作的指令的决定因素之一则是ModR/M

决定变长指令最终样子的因素不仅仅是ModRM,还需要Opcode + SIB,此处我们先只讨论Opcode+ModRM的情况

下面我们将举两个个具体的例子说明:

如有一段编码为88 01,我们首先在A-2表中找到88,其对应的指令为MOV Eb,Gb,这并不是我们熟知的汇编指令,这是一个变长指令,如果我们想要确定这条指令,就需要像上文提到的:取出后面一个字节01,吞入当前指令,将该字节作为ModR/M进行解析

我们已经知道了ModRM是由8位 3部分组成,那么我们首先将其转换为二进制,并分成三部分

01 -> 0000 0001 -> Mod:00 Reg:000 R/M:001

接下来就到了我们解析ModRM的时候了!全部操作严格按照Table 2-2进行解析

首先我们解析Mod部分00,对照2-2,我们发现当Mod = 00时,对应的指令有8种格式:

[EAX] ,[ECX] ,[EDX] ,[EBX] ,[--][--] ,disp32 ,[ESI] ,[EDI]

接下来我们需要确定是这8种格式中的哪一个,那么我们就需要用到R/M字段001

查看2-2,我们发现R/M = 001时,指令为[ECX],很好!我们已经锁定了指令的一部分为[ECX]!

还剩一个Reg没有解析,Reg在哪里呢?我们观察2-2最上面一行,发现Reg = 000时,有6种情况:

AL AX EAX MM0 XMM0 0 那么是哪一个呢?

我们上文说到Eb,Gb中的b代表了字节的意思,这说明了当前指令操作数据的宽度为:1字节,因此,我们需要选用1字节宽度的寄存器,也就是AL

至此,我们已经解析出来的结果:指令格式为MOV Eb,Gb 一部分是[ECX] 一部分是AL

那么最终是什么样子的呢? 上文中也说到了: E代表寄存器/内存 G代表通用寄存器

因此我们对号入座:

[ECX]为一款内存 因此放在Eb处 变成了MOV byte ptr ds:[ECX],Gb

AL为一个8位通用寄存器 因此放在Gb处 变成了 MOV byte ptr ds:[ECX],AL

至此,整条指令解析结束 88 01最终变成了两字节的变长指令 MOV byte ptr ds:[ECX],AL

接下来我们解析另一条编码:88 2D 12 34 56 78

上文已经详细描述了查表的步骤,这里不再赘述

88 -> MOV Eb,Gb 引出ModR/M = 2D 2D->0010 1101->Mod:00 Reg:101 R/M:101

解析Mod 与 R/M得到disp32

解析Reg得到CH

带入公式得到: MOV disp32,CH

我们发现这里出现了没见过的东西disp32,对照2-2其他部分,这里本应该是[EBP],为什么会变成disp32?

知识点来了!

我们在学习基础,画堆栈图时应该记得这样一个知识点 : call调用时系统会提升堆栈,将新堆栈给这个call使用,在提升后,[EBP+4]为返回地址 [EBP+8]为第一个参数 [EBP-4]为第一个局部变量 [EBP]则为原始堆栈的栈底

而intel认为,任何对于原始栈底赋值是无意义的!这会破坏堆栈的结构所以intel废弃了这种指令MOV [EBP],XX,而将其扩展为disp32 : 一个32位的内存地址

知道了这个知识点,我们再会过来看这条指令MOV disp32,CH,既然已经变成了32位内存地址,那么就说明此条指令在ModR/M之后,应该再吞4个字节,存放至disp32位置处!

所以 88 2D 12 34 56 78最终会变为 6字节的变长指令 MOV byte ptr ds:[0x78563412],CH

上文我们学习了ModR/M的解析,接下来我们要学习SIB的解析

在上面进行解析ModR/M时,细心的小伙伴应该发现了,2-2表中,不光有disp32这种奇葩,还有一个[--][--]

这是个什么玩意?

观察后我们发现只有在R/M = 100时才会出现,根据2-2表中寄存器的顺序推理,R/M =100时理应为[ESP]之类的东西,为何又变成了[--][--]?

知识点又来了!

没错,intel又想到了! ESP的值随时发生变化,任何通过ESP进行寻址的操作会变得不好维护!,于是intel将其扩展,引出了下一个字节: SIB

SIB的来源我们简单描述了一下,接下来我们看一下SIB的解析

SIB一字节 8个二进制位,其同ModR/M相同,也是分成了三部分,分别为:Scale:6~7 Index:3~5 Base:0~2

白皮书中第1643页,表Table 2-3为我们展示了SIB各部分的含义:

多说无益,我们将通过两个例子来学习SIB的解析与作用

88 84 48 12 34 56 78

ModR/M的解析大家应该已经了解了,我就快速带过了

当我们看到[--][--]时,我们就该知道,需要解析SIB

取出ModR/M后面的一字节48作为SIB进行解析:

SIB = 48 -> 0100 1000 -> Scale:01 Index:001 Base:000

查询2-3,我们发现当Scale = 01时,存在8种情况:

[EAX*2] [ECX*2] [EDX*2] [EBX*2] NONE [EBP*2] [ESI*2] [EDI*2]

Index = 001时,锁定为八种情况中的 [ECX*2]

观察2-3第一行,我们得出当Base = 000时得到EAX

这时我们已经得到了EAX ,[ECX*2],接下来我们需要记住一个公式:

SIB的寻址结构公式: [Base + Index*2^Scale]

将结果带入寻址结构公式为 : [EAX + ECX*2^1]也就是[EAX + ECX*2]

至此,我们SIB的解析已经结束了,将解析结果带入最开始的公式:

MOV [--][--]+disp32,AL -> MOV [EAX + ECX*2] +disp32,AL

这里不知道是不是intel书里写错了,但是各位记住就行,disp32是在括号里的,不要看他写在外面你也写在外面

所以在吞入4字节存放至disp32后,整行指令变成了:

MOV byte ptr ds:[EAX + ECX*2 + 0x78563412],AL

虽然我觉得应该没有人会不知道,但是我还是说一下吧...

^ 的意思是次方,比如 2^3 代表2的3次方

88 84 A4 12 34 56 78

我们再来解析一条指令 : 88 84 A4 12 34 56 78

看到了[--][--]我们取出下一个字节进行解析 :

SIB = A4 -> 1010 0100 -> Scale:10 Index:100 Base:100

对照2-3得出结果为: none ESP

我们又看到了一个不认识的东西

none : 这个我暂时不知道intel为啥舍弃了,各位知道的可以评论区说下. 这个none代表此处不填值,可以相当于没有这个结果,在进行公式带入的时候跳过即可

带入寻址结构公式[Base + Index*2^Scale]变为[ESP + none],none舍弃,结果就是[ESP]

带入第一个公式 ,变为 MOV [ESP]+disp32,AL

吞入4字节作为偏移结果为: MOV byte ptr ds:[ESP + 0X78563412],AL

这就结束了吗?

当然不是

各位应该听说过段寄存器,指令6部分中的第一部分前缀则是描述段寄存器的,当无前缀时,指令有一个默认的段寄存器,这里面有一些默认规则,这里我们先说其中一条

当寻址公式里出现 ESP EBP时,默认段寄存器为 SS

所以,我们的汇编语句不能使用DS作为段寄存器,而应该是SS,所以最终结果为:

8884 12345678 ->MOV byte ptr ss:[ESP + 0X78563412],AL

88 84 85 12 34 56 78

我们最后再来解析一条:88 84 85 12 34 56 78

看到了[--][--]我们取出下一个字节进行解析 :

SIB = 85 -> 1000 0101 -> Scale:10 Index:000 Base:101

查询2-3得出两个结果:

Scale + Index -> [EAX*4]Base -> [*]


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2020-10-13 17:43 被SSH山水画编辑 ,原因:
收藏
免费 3
支持
分享
最新回复 (8)
雪    币: 40
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
支援
2020-10-9 16:03
0
雪    币: 1475
活跃值: (14652)
能力值: ( LV12,RANK:380 )
在线值:
发帖
回帖
粉丝
3
门徒_L 支援
到位!
2020-10-9 16:39
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
牛肉面吃吗?三块钱一碗
2021-3-16 08:23
0
雪    币: 231
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
请问楼主是直接看Intel白皮书来理解反汇编的吗?我寻思有没有其他参考资料,最近写毕设论文可能会用到。
2021-4-23 14:49
0
雪    币: 231
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
SIB为一字节 8个二进制位,其同ModR/M相同,也是分成了三部分,分别为:Scale:6~7 Index:3~5 Base:0~2

按照后文的解释(SIB = 48 -> 0100 1000 -> Scale:01 Index:001 Base:000)
这三个部分对应的位数应该为:Scale:0~1,Index:2~5,Base:6~7

还是说这里的 “Scale:6~7 Index:3~5 Base:0~2” 指的是其他意思?
2021-4-23 15:51
0
雪    币: 1475
活跃值: (14652)
能力值: ( LV12,RANK:380 )
在线值:
发帖
回帖
粉丝
7
JZen SIB为一字节 8个二进制位,其同ModR/M相同,也是分成了三部分,分别为:Scale:6~7 Index:3~5 Base:0~2 按照后文的解释(SIB = 48 -> 0100 1 ...
48  高位是4低位是8  0是最低位  7是最高位
2021-4-23 18:04
0
雪    币: 1475
活跃值: (14652)
能力值: ( LV12,RANK:380 )
在线值:
发帖
回帖
粉丝
8
JZen 请问楼主是直接看Intel白皮书来理解反汇编的吗?我寻思有没有其他参考资料,最近写毕设论文可能会用到。
白皮书和滴水三期初级教程硬编码部分
2021-4-23 18:05
0
雪    币: 231
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
9
SSH山水画 48 高位是4低位是8 0是最低位 7是最高位
啊,明白了,犯了个低级错误,感谢解惑!
2021-4-24 17:01
0
游客
登录 | 注册 方可回帖
返回
//