硬编码学习笔记(二)—— 经典变长指令
前言
本次学习仅基于intel x86模式
指令结构
描述:对于任何一条指令,都由以下几部分组成,但不是每部分都必须存在
长度:最短1个字节,最长15个字节
Instruction Prefixes:指令前缀
Opcode: 主操作码
ModR/M:在内存中引用一个操作数的许多指令都有一个寻址方式的指定符字节(称为ModR/M字节)跟随在主操作码之后
SIB:ModR/M字节的某些编码需要第二个寻址字节(SIB字节)
Displacement:一些寻址方式包含紧跟ModR/M字节(或者SIB字节,如果有的话)的位移
Immediate:立即数
符号说明
寻址符号
符号 | 意义 |
---|---|
A | 直接寻址 |
C | 控制寄存器 |
D | 调试寄存器 |
E | 寄存器/内存 |
F | EFLAGS/RFLAGS寄存器 |
G | 通用寄存器 |
I | 立即数 |
J | 要添加到指令指针寄存器的相对偏移量 |
M | ModR/M字节可能仅指向内存。 |
N | MMX 技术寄存器 |
O | 该指令没有ModR/M字节。操作数的偏移量在指令中被编码为一个字或双字(取决于地址大小属性)。 |
P | ModR/M字节的reg字段选择一个打包的四字MMX技术寄存器 |
Q | ModR/M字节跟随操作码并指定操作数。该操作数要么是MMX技术寄存器,要么是内存地址。 |
R | ModR/M字节的R/M字段可能仅指一个通用寄存器 |
S | ModR/M字节的reg字段选择一个段寄存器 |
U | ModR/M字节的R/M字段选择一个128位的XMM寄存器。 |
V | ModR/M字节的reg字段选择一个128位的XMM寄存器。 |
W | ModR/M字节跟随操作码并指定操作数。操作数要么是一个128位的XMM寄存器要么是一个内存地址。 |
X | 由DS:rSI寄存器对寻址的内存 |
Y | 由ES:rDI寄存器对寻址的内存 |
操作数符号
符号 | 意义 |
---|---|
a | 内存中的两个单字操作数或内存中的两个双字操作数,具体取决于操作数大小属性(仅由BOUND指令使用) |
b | 字节 |
c | 字节/字 |
d | 双字 |
dq | 四字 |
p | 32位/48位/80位指针 |
pd | 128位封装的双精度浮点数据 |
pi | 双四字,MMX技术寄存器(例如:mm0) |
ps | 128位封装的单精度浮点数据 |
q | 四字 |
s | 6字节或10字节的伪描述符 |
ss | 128位打包的单精度浮点数据的标量元素 |
si | 双字整数寄存器(例如:eax) |
v | 字、双字或四字(取决于当前CPU的模式) |
w | 字 |
z | 16位操作数大小的字或32位或64位操作数大小的双字 |
上标符号
符号 | 意义 |
---|---|
1A | ModR/M字节的第5、4和3位用作操作码扩展 |
1B | 使用0F0B操作码(UD2指令)或0FB9H操作码时,故意尝试生成无效的操作码异常(#UD) |
1C | 在Pentium III处理器中添加的一些指令可以使用相同的双字节操作码。如果指令有变化,或者操作码代表不同的指令,则使用ModR/M字节来区分指令。 |
i64 | 指令在64位模式下无效 |
o64 | 指令仅在64位模式下有效 |
d64 | 当处于64位模式时,指令默认为64位操作数大小,并且不能对32位操作数大小进行编码。 |
f64 | 在64位模式下,操作数大小被强制为64位操作数大小(在64位模式下,该指令会忽略改变操作数大小的前缀)。 |
One-Byte Opcode Map
变长指令
描述:当操作系统遇到诸如0x88这样的Opcode时,其指令序列为MOV Eb, Gb
,目的是将一个8位的寄存器存储到一个8位的寄存器或内存中,当表中出现E或G这样的符号时,即存在ModR/M字段
思考:CPU如何知道要把值放在哪个寄存器中?
答案:使用ModR/M字段
ModR/M
描述:指令在引用一个不确定操作数时使用该字段进行定位
结构:
Reg/Opcode:用来确定寄存器或操作码是什么
Mod+R/M:这两个字段拼在一起确定是哪个内存或寄存器
例:0x88
指令格式:MOV Eb, Gb
例:
88 00
Mod:00 Reg/Opcode:000 //使用0号寄存器,是EAX还是AL由操作码决定 R/M:000 //Mod与R/M拼在一起指向[EAX]
因此字节码
88 00
对应的指令为:MOV [EAX], AL
注意:
- 当
Mod为00,R/M为100
时,使用另一种指令格式 - 当
Mod为00,R/M为101
时,向后再取4字节立即数作为Gb的值88 15 00 10 40 00 MOV [0x401000],DL
例:0x89
指令格式:MOV Ev, Gv
例:
89 49 10
Mod:01 Reg/Opcode:001 //使用1号寄存器 R/M:001 //Mod与R/M拼在一起指向[ECX]+disp8
因此字节码89 49 10
对应的指令为:MOV [ECX+0x10], ECX
例:0x8A
指令格式:MOV Gb, Eb
例:
8A 92 78 56 34 12
Mod:10 Reg/Opcode:010 //使用2号寄存器 R/M:010 //Mod与R/M拼在一起指向[EDX]+disp32
因此字节码8A 92 78 56 34 12
对应的指令为:MOV DL, [EDX+0x12345678]
例:0x8B
指令格式:MOV Gv, Ev
例:
8B DB
Mod:11 Reg/Opcode:011 //使用3号寄存器 R/M:011 //Mod与R/M拼在一起指向EBX
因此字节码8B DB
对应的指令为:MOV EBX, EBX
SIB
描述:
- Opcode决定了后边是否存在ModR/M字段,而ModR/M决定了后面是否存在SIB字段
- 当ModR/M字段低3位为100时,存在SIB字段
结构:
Scale: 2的几次方
Index:下标
Base:确定是哪个寄存器
例:DS:[EAX+ECX2+0x12345678]
scale表示2¹
Index表示ECX
Base表示EAX
0x12345678由ModR/M字段决定
即:SIB=Base+Index2的scale次方
[*]:取决于ModR/M中MOD字段的值
00 [scaled index] + disp32
01 [scaled index] + disp8 + [EBP]
10 [scaled index] + disp32 + [EBP]
例:88 84 48 78 56 34 12
Opcode:88
指令格式:MOV Eb, Gb
ModR/M:84
Mod:10
Reg/Opcode:000 //使用0号寄存器AL
R/M:100 //使用SIB字段
[--][--]
:查询SIB字段
SIB:48
Scale:01
Index:001 //Scale与Index结合指向[ECX*2]
Base:000 //使用0号寄存器EAX
因此,字节码
88 84 48
对应的指令为MOV BYTE PTR DS:[EAX+ECX*2+0x12345678], AL
例:89 84 84 78 56 34 12
Opcode:89
指令格式:MOV Ev, Gv
ModR/M:84
Mod:10
Reg/Opcode:000 //使用0号寄存器AL
R/M:100 //使用SIB字段
[--][--]
:查询SIB字段
SIB:84
Scale:10
Index:001 //Scale与Index结合指向[ECX*2]
Base:000 //使用0号寄存器EAX
因此,字节码88 84 48
对应的指令为MOV DWORD PTR DS:[ESP+EAX*4+0x12345678], EAX
Opcode Extension Tables
描述:当操作系统遇到诸如0x80这样的Opcode时,其指令序列为Eb, Ib
,并无指明Opcode
思考:CPU如何知道Opcode是什么?
答案:查询Opcode Extension Tables
例:80 65 08 FF
ModR/M:65
Mod:01
Reg/Opcode:100 //此时不再指向寄存器,用这个值去查Opcode Extension Tables
R/M:101 //Mod与R/M结合指向[EBP]+disp8
因此,字节码80 65 08 FF
对应的指令为AND BYTE PTR SS:[EBP+0x8]
, 0xFF
Instruction Prefixs
描述:指令前缀
段前缀
描述:
1)在早期8086CPU寻址范围较小,Intel采用段寄存器*16+偏移的方式寻址
2)后来80386CPU扩大了寻址范围,段寄存器便被用作了其它用途,不参与寻址
3)但是类似DS:[]这种格式被保留了下来
4)实际上操作码已经决定了寻址时使用哪个段寄存器作为基址,不需要其他字节描述
注意:
1)如果没有特别说明,[]前为DS,即DS:[]
2)若是像PUSH和POP指令,以及其它在[]中使用ESP/EBP的指令,默认前缀为SS
3)在[Base+Index*2Scale+I]中,以Base作为判断条件,没有特别说明,默认前缀为DS
4)如果Base为ESP/EBP,默认前缀为SS
5)EIP取指令时默认前缀为CS
6)如果指令加段寄存器前缀,则该条指令一律用这个段; 如果加多个段寄存器前缀,默认只看Opcode前面那个
默认值
CS:2E
SS:36
DS:3E
ES:26
FS:64
GS:65
操作指令前缀:修改地址默认长度
描述:在无指令前缀的Opcode中,B0表示MOV AL, Ib,B8表示MOV EAX, Id,但却不存在MOV AX, I这样的Opcode,这是因为intel使用指令前缀完成这件事情
例:0x66
作用:将操作数改为16位模式
50 PUSH EAX
66:50 PUSH AX
操作指令前缀:修改默认寻址方式
例:0x67
作用:将操作数改为16位模式
88 01 MOV BYTE PTR DS:[ECX], AL
67:8801 MOV BYTE PTR DS:[BX+DI], AL