0x01 简介
这里开始熟悉汇编的指令格式
下图为X86版本的指令格式,X64的指令格式和X86差不多后面有需要的话再详细写
0x02 Istruction Prefixes 前缀指令
Istruction Prefixes: 可选项
默认二进制文件是前缀和内容混杂在一起只有机器才能识别区别
人工区分方法:
- 逆向:debug工具打开程序 前面有冒号的就是带前缀的操作指令
前缀指令分组
X86格式图已经给我们提示,有四个组,一次最多使用一个组
1.LOCK 和 REPEAT
指令 | 硬编码 |
---|---|
LOCK | F0 |
REPNE/PEPNZ | F2 |
REP/REPZ | F3 |
2.段前缀指令:
段寄存器 | 硬编码 |
---|---|
ES | 26 |
CS | 2E |
SS | 36 |
DS | 3E |
FS | 64 |
GS | 65 |
段前缀指令的作用:修改使用段寄存器
不加提示默认使用DS
如果有栈寄存器的话会使用SS
3.操作数宽度前缀指令
硬编码 | 效果 |
---|---|
66 | 改变默认寄存器宽度 |
push ebp是设置程序为X86位下的16位模式情况下运行
CPU是如何判断程序有关系:
CS中有一个DB位如果CS中DB位为0则16位1位32
现在默认的CPU模式是32位,所以汇编指令就算是使用epb(16位寄存器)最少也是32位
如何使用16位的
只需要在硬编码前面加上前缀 66:即可
4.地址宽度前缀指令
效果如上面
硬编码 | 效果 |
---|---|
67 | 改变地址宽度 |
当CPU使用32位模式选址方式就会使用32位进行
当DB位为0的时候16位,使用16位寻址方式,当DB为1的时候使用32位寻址方式
0x03 Opcode 修改通用寄存器
决定 硬编码长度是由( Opcode ModR/M SIB)决定。前缀指令只会影响到他自己。
然后,ModR/M 由Opcode决定 SIB由ModR/M决定
定长指令&变长指令
虽然这个是叫做One-byte但是都是从这个表里面展开的
由这个图例查找,55,push rbp/13
然后 0F-3F都有两个操作符,前面的是Opcode,后面跟着是ModR/M
如果Opcode跟着 ModR/M的话就是变长,没有则定长
Zz查表
所有两个操作符的就判定前面的是Opcode,ModR/M
这是要理解这个我们可以查询他相关的问题操作符解释
在Intell白皮书里面有Codes for addressing method
可以查到操作符大写解释
下面的Codes for Operand Type
有操作符小写解释
例如大写E前面很明显的写了A ModR/M byte follows the opcode
注
表中寄存器带个r表示可以随着系统变化而变化,可以是64位的RAX,也可以是32位的EAX,也可以是AX 表中会显示最大的修改位数,带E开头的表示只能修改32位和16位
0x04 Opcode 定长指令之修改ERX
ERX=EAX or ECX or EDX 等通用寄存器
PUSH/POP INC/DEC
硬编码 4开头是 INC/DEC 5开头的是push/pop
0-7是 INC PUSH
8-F是DEC POP
40开头的都两行格式,上面的一行表示32位,下面的是64位的。
mov rb,lb mov ERX ,Iv
在b开头的是mov指令
他的写法又不一样,mov AL\R8L,Ib
这样能够很好的解释定长指令不是只有一个字节的
I是Immedlate立即数
b是bate 字节
v是 word doubleword quadword三种情况之一,他取决于你采用数据宽度
所以:
0-7 是 mov rb,lb
8-F 是 mov ERX,Iv
XCHG EAX,ERX
9开头 就是XCHG指令是两个寄存器之间交换而且都是放到EAX中
例:
91 XCHG EAX,ECX
92 XCHG EAX,EDX
但0是表示EAX 用EAX放到EAX中所以汇编中把
90作为nop 不做任何操作。
我们通常使用Nop的时候是遇到花指令这个下次有时间再写。
90-97是有规则的,但8-F单独说
表格中如果带有
I64 指令表示这个指令在64位下单独表示,例如INC和DEC
o64 只能在64位系统下使用。
d64 这个指令默认是64位,且不支持32位
f64 在64位操作系统下强制使用64位宽度
0x05 Opcode 定长指令之修改EIP
能够修改EIP的指令有JCC,call,ret三类
jcc
从上表中可以查到JCC的指令可以从 0x70~0x7F,八个指令。
上图是JCC指令示例
不论是70-7F中哪一个都会带有一个字节的数据,那个数据被称为偏移地址。
所以JCC普通跳转地址最大位一个字节(FF/2),80以下往下跳转,80以上往上跳转
条件跳转,后面跟一个字节立即数的偏移(有符号),共两个字节
如果条件成立调到当前指令地址+当前指令长度+LB
最大值:向前跳转7F,向后跳转80
而且当你要跳转的字节超过一个字节就会跳转到两个字节,
0F80 者就是两个字节,0F上会让你去看两字节的表同样书中会有这个表格
E
因为E指令有多个指令包含JMP,CALL,LOOP
硬编码 | 汇编解释 | 字节数 |
---|---|---|
E0 | LOOPNE/LOOPNZ lb(Jb) | 2 |
E1 | LOOPE/LOOPZ lb(Jb) | 2 |
E2 | LOOP Ib(Jb) | 2 |
E3 | JrCXZ Ib(Jb) | 2 |
E8 | CALL Id (Jd) | 5 |
E9 | JMP Id(Jd) | 5 |
J是 带偏移地址
上面的是常用的不常用的先不写了
0x06 变长指令 ModR/M
MODR/M 只占一个字节八个位拆开就是下面:
经典的边长指令:
地址 | 汇编指令 | 特殊字符含义 |
---|---|---|
0x88 | MOV Eb,Gb | G:通用寄存器 |
0x89 | MOV Ev,Gv | E:寄存器/内存 |
0x8A | MOV Gb,Eb | b:字节 |
0x8B | MOV Gv,Ev | v:Word doubleWord quadword |
G(寄存器)是由ModR/M 中3,4,5位Reg/Opcode 决定的表格如下
寄存器宽度 | 000 | 001 | 010 | 011 | 100 | 101 | 110 | 111 |
---|---|---|---|---|---|---|---|---|
32 | EAX | ECX | EDX | EBX | ESP | EBP | ESI | EDI |
8 | AL | CL | DL | BL | AH | CH | DH | BH |
E(寄存器)是由ModR/M中的0,1,2,6,7位Mod和R/M共同决定的:
注:
- Disp是(Displacement)地址偏移的意思
- 当查表查到--时,就表示ModR/M不能完全确定寄存器需要再跟一个字节来确定,这个字节就是SIB
现在我们查一下一些不常用的指令的指令
0x80~0x83 首先是Imediate Grp 1^1A
1A的意思是下面的表格
1A就是 3,4,5不再代表通用寄存器,而是通用寄存器
所有的带有这些角标的都可以看扩展
Grp 1是下面的表格
变长指令实例
具体的所有变长指令的表基本介绍完了,我们试试查找一下
81 22 08 FF
81 Ev,Iz
72 拆成字节是
0111 0010
根据ModR/M
01 110 010
结合 81 MOD 和 R/M 三个可以查出
XXX WORD ptr ds:[EDP+disp8],[immediate]
再用 reg查完整的就是
XOR WORD PTR DS:[EDP+disp8],Iz
这里还有两个未知数不知道 就是后面的数据
disp8是08 Iz就是FF
0x07 变长指令 SIB
上面有提过:
当查ModR/M表查到--时,就表示ModR/M不能完全确定寄存器需要再跟一个字节来确定,这个字节就是SIB
例如:
88 84 48
Mov [--][--]+disp32,AL
SIB计算是这下面计算的
然后根据上面的数据可以查出
48
0100 1000
01 001 000
base 000 index 001 Scale 01
MOV BYTE PTR DS:[EAX+ECX*2+disp32],AL
0x08 总结
硬编码整个过程来说,就是简略的通过查表了解CPU将数据转化成汇编指令的过程。