硬编码逆向分析——定长指令与变长指令

写在前面,助记清单

帮助记忆:
1、要成为武林(50)盟主,必须拼命push,直到最后放下武器(57)!==》50~57表示push。
2、58同城搞了一个集五福(5F)活动,说是邀请了上面的50盟主,要和他对着干(push--》pop)。==》58~5F表示pop。

1、许仕林(40)因为鼓舞了士气(47),最后考取状元加分(inc)了!成功夺魁。==》40~47对应inc
2、DEC公司是你爸(48)还是佛(4f)?你竟然这样向着他!==》48~4F对应DEC

1、他是一个2b(B2),必须立即(立即数)移走(move),然后切成牛肉(BF)==》B0~BF表示move立即数。

一个90后的97年出生的程序员,为了叫eax的女生,交换了自己的青春年华去搬砖。==》90~97对应XCHG EAX

70后的要跳槽(jmp)太难了!处处被人欺负(7f)==》70~7f对应跳转类jmp指令,只能跳转0~255。

80后的程序员,因为了CFO(OF)的buffer(8f)加持,所以可以跳槽的空间大些。==》0f 80~0f8f对应jmp四字节。

1、你这小子是个爸宝男,每次都依你爸(E8)的意见,一出事就打电话(call)给他!==》E8 call
2、在泰坦尼克号里,让男主再选择一次,他依旧跳了(E9 jmp)!==》E9 jmp

你乘坐的国航(CA打头)CCC(c3)航班已经返航(RET)==》C3,CA对应ret。

爸爸和芭比(8b)娃娃参加89薛超,最后都移动(mov)用AK干掉了!==》88~8B对应mov

定长指令与变长指令

如下图是硬编码的结构,第二部分的Opcode是整个指令的灵魂,硬编码结构中的任何部分都可以没有,但是Opcode是必须要有的。

images/download/attachments/21791281/image2021-10-25_22-43-36.png

Opcode最少1个字节,最多3个字节;如下图我们可以看见硬编码排列是不整齐的,有的一行是1个字节,有的则是2个、5个字节,Opcode、ModR/M、SIB这三个组合在一块就可以决定一行指令的宽度(抛开前缀指令),后面的Displacement、Immediate就是配角,当前面的三个确定了,这两个也就确定了。

images/download/attachments/21791281/image2021-10-25_23-11-57.png

Opcode、ModR/M、SIB之间的关系是这样的:Opcode决定有没有ModR/M,ModR/M决定有没有SIB。==》其实很容易理解,就是贪心解析的思路。

什么是定长、变长指令

如下图所示50、52之类的硬编码实际上就是定长指令但并不表示定长指令就只有一个字节;同样,如00D4、0034C3之类的硬编码就是变长指令

images/download/attachments/21791281/image2021-10-31_12-57-34.png

简而言之,定长指令可以直接通过Opcode确定硬编码长度变长指令就无法通过Opcode确定硬编码长度。

如何区分指令是定长或变长

如何区分指令是定长或变长,这需要去根据官方的文档来看:

images/rest/documentConversion/latest/conversion/thumbnail/21792842/1.jpg

如下图所示展开到「A.3 ONE, TWO, AND THREE-BYTE OPCODE MAPS」,向下拉就可以看到一张表,这张表是1个字节的Opcode的对应表,但实际上其他字节的Opcode就是通过这张表进行扩展的,所以这张表就是主表,也是所有x86硬编码中最重要的一张表(这张图中的表是不完整的,向下拉还有一张表,两张表拼在一块才是完整的):

images/download/attachments/21791281/image2021-10-31_13-7-46.png

通过这张表,我们可以直接看到之前举例的定长指令50就正是对应着PUSH EAX,举一反三,51就是PUSH ECX...,图中的rAX表示着这里可以是64位的RAX(64位模式下才有)、32位的EAX、16位的AX,而eAX则表示这里可以是32位的EAX、16位的AX(默认取决于你的CPU运行模式)

images/download/attachments/21791281/image2021-10-31_13-13-59.png

但是我们之前举例的变长指令00却让有点让人摸不着头脑:

images/download/attachments/21791281/image2021-10-31_13-15-28.png

这里我们知道对应的汇编代码是ADD,但是表格中的Eb,Gb却不清楚是什么;实际上这是Intel定义的一种Zz表示法,第一个字母为大写,第二个字母为小写。

在文档的「A.2.1 Codes for Addressing Method」、「A.2.2 Codes for Operand Type」中有解释每个字母的含义,

images/download/attachments/21791281/image2021-10-31_13-28-54.png

结合内容,再根据之前知道的Opcode决定有没有ModR/M,返过来一推,Opcode后面有ModR/M则表示这是一个变长指令,没有则是一个定长指令,也就是说操作数只要存在Ex或Gx的就为定长指令,没有的则为定长指令。

经典定长指令

经典定长指令,就是我们以后会经常见到、使用的定长指令;注意以下都是以x86环境去讲解,在实际的硬编码对应的汇编指令中其他环境对应的指令并不是这些

修改ERX

标题中的ERX就表示EAX、ECX、EDX等等32位的寄存器。

PUSH/POP

PUSH:压入栈;POP:推出堆。

定长指令

汇编代码

0x50

PUSH EAX

0x51

PUSH ECX

0x52

PUSH EDX

0x53

PUSH EBX

0x54

PUSH ESP

0x55

PUSH EBP

0x56

PUSH ESI

0x57

PUSH EDI

0x58

POP EAX

0x59

POP ECX

0x5A

POP EDX

0x5B

POP EBX

0x5C

POP ESP

0x5D

POP EBP

0x5E

POP ESI

0x5F

POP EDI

帮助记忆:

1、要成为武林(50)盟主,必须拼命push,直到最后放下武器(57)!==》50~57表示push。

2、58同城搞了一个集五福(5F)活动,说是邀请了上面的50盟主,要和他对着干(push--》pop)。==》58~5F表示pop。

 

INC/DEC

INC:加1;DEC:减1。

定长指令

汇编代码

0x40

INC EAX

0x41

INC ECX

0x42

INC EDX

0x43

INC EBX

0x44

INC ESP

0x45

INC EBP

0x46

INC ESI

0x47

INC EDI

0x48

DEC EAX

0x49

DEC ECX

0x4A

DEC EDX

0x4B

DEC EBX

0x4C

DEC ESP

0x4D

DEC EBP

0x4E

DEC ESI

0x4F

DEC EDI

帮助记忆:

1、许仕林(40)因为鼓舞了士气(47),最后考取状元加分(inc)了!成功夺魁。==》40~47对应inc

2、DEC公司是你爸(48)还是佛(4f)?你竟然这样向着他!==》48~4F对应DEC

 

MOV Rb, Ib

MOV:数据传送。

定长指令

汇编代码

0xB0

MOV AL, Ib

0xB1

MOV CL, Ib

0xB2

MOV DL, Ib

0xB3

MOV BL, Ib

0xB4

MOV AH, Ib

0xB5

MOV CH, Ib

0xB6

MOV DH, Ib

0xB7

MOV BH, Ib

 

 

标题里的Rb表示着8位寄存器,Ib表示着是8位立即数,这些都可以通过之前的PDF文档查阅得知(1字节等于8位)==》人家那写的是大写的i,表示immiditely立即数。

images/download/attachments/21791281/image2021-10-31_22-15-11.png

在官方的表格中也可以很直观的看见:

images/download/attachments/21791281/image2021-10-31_22-17-6.png MOV ERX, Id

MOV:数据传送。

定长指令

汇编代码

0xB8

MOV EAX, Id

0xB9

MOV ECX, Id

0xBA

MOV EDX, Id

0xBB

MOV EBX, Id

0xBC

MOV ESP, Id

0xBD

MOV EBP, Id

0xBE

MOV ESI, Id

0xBF

MOV EDI, Id

 

实验:

助记:

1、他是一个2b(B2),必须立即(立即数)移走(move),然后切成牛肉(BF)==》B0~BF表示move立即数。

 

标题里的Id,I我们都知道是立即数了,再来看一下官方文档的d:

images/download/attachments/21791281/image2021-10-31_22-24-34.png

也就表示这里的Id是32位的立即数,其实你也可以不用看官方的释义,我们可以这样推出:首先这是一个定长指令,长度是固定的,其次这里的ERX就表示着32位寄存器,由此可以得出后面的立即数是必须是固定的长度,所以只能是32位的立即数。

需要注意的是我们当前环境是x86的所以用Id来代替立即数的表示,在实际表格中立即数是由Iv来表示的:

images/download/attachments/21791281/image2021-10-31_22-34-4.png

这是由于寄存器是rAX,在64位模式下有三种表达方式,所以Iv表示的立即数的大小是取决于操作数的属性的:

images/download/attachments/21791281/image2021-10-31_22-37-51.png

 

XCHG EAX, ERX

XCHG:内容交换。

定长指令

汇编代码

0x90

XCHG EAX, EAX = NOP

0x91

XCHG EAX, ECX

0x92

XCHG EAX, EDX

0x93

XCHG EAX, EBX

0x94

XCHG EAX, ESP

0x95

XCHG EAX, EBP

0x96

XCHG EAX, ESI

0x97

XCHG EAX, EDI

XCHG是用来做内容交换的,0x90对应着XCHG EAX, EAX,这就没有任何意义了,所以Intel给其定义了一个新的指令叫NOP,这个我们称之为无效指令,也就表示这个指令是没有任何意义的。

 

助记:

一个90后的97年出生的程序员,为了叫eax的女生,交换了自己的青春年华去搬砖。

 

修改EIP

我们在学习会变的时候都知道无法通过MOV、ADD之类的指令去修改EIP,所以要修改EIP需要借助JCC、CALL、JMP之类的指令进行,接下来我们学习的硬编码就跟这些指令有关的。

0x70 - 0x7F(近跳)

条件跳转,后跟一个字节立即数的偏移(有符号),共两个字节。 如果条件成立,跳转到当前指令地址 + 当前指令长度 + Ib向下跳的范围是0x0 - 0x7f,向上跳的范围是0x80 - 0xFF

定长指令

汇编代码

0x70

JO

0x71

JNO

0x72

JB/JNAE/JC

0x73

JNB/JAE/JNC

0x74

JZ/JE

0x75

JNZ/JNE

0x76

JBE/JNA

0x77

JNBE/JA

0x78

JS

0x79

JNS

0x7A

JP/JPE

0x7B

JNP/JPO

0x7C

JL/JNGE

0x7D

JNL/JGE

0x7E

JLE/JNG

0x7F

JNLE/JG

 

助记:

70后的要跳槽(jmp)太难了!处处被人欺负(7f)==》70~7f对应跳转类指令,只能跳转0~255。

 

0x0F 0x80 - 0x0F 0x8F(远跳)

条件跳转,后跟四个字节立即数的偏移(有符号),共五个字节。如果条件成立,跳转到当前指令地址 + 当前指令长度 + Id向下跳的范围是0x0 - 0x7FFFFFFFF,向上跳的范围是:0x80000000 - 0xFFFFFFFF

定长指令

汇编代码

0x0F 0x80

JO

0x0F 0x81

JNO

0x0F 0x82

JB/JNAE/JC

0x0F 0x83

JNB/JAE/JNC

0x0F 0x84

JZ/JE

0x0F 0x85

JNZ/JNE

0x0F 0x86

JBE/JNA

0x0F 0x87

JNBE/JA

0x0F 0x88

JS

0x0F 0x89

JNS

0x0F 0x8A

JP/JPE

0x0F 0x8B

JNP/JPO

0x0F 0x8C

JL/JNGE

0x0F 0x8D

JNL/JGE

0x0F 0x8E

JLE/JNG

0x0F 0x8F

JNLE/JG

 助记:

80后的程序员,因为了CFO(OF)的buffer(8f)加持,所以可以跳槽的空间大些。==》0f 80~0f8f对应jmp四字节。

 

0xE0 - 0xE9

如下表格中的J就表示偏移量,宽度根据后面b或者d决定(具体可以看文档释义)。

定长指令

汇编代码

宽度

作用

0xE0

LOOPNE/LOOPNZ Ib (Jb)

共2字节

先进行 ECX = ECX - 1 当 ZF = 0 && ECX!=0 时跳转到当前指令地址 + 当前指令长度 + Ib

0XE1

LOOPE/LOOPZ Ib (Jb)

共2字节

先进行 ECX = ECX - 1 当 ZF = 1 && ECX != 0 时跳转到当前指令地址 + 当前指令长度 + Ib

0XE2

LOOP Ib (Jb)

共2字节

先进行 ECX = ECX - 1 当 ECX!=0 时跳转到当前指令地址 + 当前指令长度 + Ib

0XE3

JrCXZ Ib (Jb) (在32位模式中rCX为ECX)

共2字节

当 ECX = 0 时跳转到当前指令地址 + 当前指令长度 + Ib(自己控制步长)

0xE8

CALL Id (Jd)

共5字节

CALL指令的下一条指令地址入栈后,跳转到当前指令地址 + 当前指令长度 + Id

0xE9

JMP Id (Jd)

共5字节

跳转到当前指令地址 + 当前指令长度 + Id

助记:

1、你这小子是个爸宝男,每次都依你爸(E8)的意见,一出事就打电话(call)给他!==》E8 call

2、在泰坦尼克号里,让男主再选择一次,他依旧跳了(E9 jmp)!==》E9 jmp

 

其他指令

定长指令

汇编代码

宽度

作用

0xEA

JMP Ap (Ap:六字节长度的直接地址)

共7字节

JMP CS:Id 将Ap中的高2位赋值给CS,低4位直接赋值给EIP, 即跳转

0xEB

JMP Ib (Jb)

共1字节

跳转到当前指令地址 + 当前指令长度 + Ib

0xC3

RET

共1字节

EIP出栈

0xC2

RET Iw

共3字节

EIP出栈后,进行 ESP = ESP + Iw

0XCB

RETF (return far)

共1字节

出栈8个字节,低4个字节赋值给EIP,高4个字节中低2位赋值给CS

0xCA

RETF Iw

共3字节

出栈8个字节,低4个字节赋值给EIP,高4个字节中低2位赋值给CS后,ESP = ESP + Iw

 

助记:

你乘坐的国航(CA打头)CCC(c3)航班已经返航(RET)==》C3,CA对应ret。

 

经典变长指令

ModR/M

当指令中出现内存操作对象的时候,就需要在操作码后面附加一个字节来进行补充说明,这个字节被称为ModR/M,其只有一个字节宽度,但如果你看PDF官方文档中那个表的话就会发现其有两个参数,这也正是它复杂的地方。

如下所示就几个经典的变长指令:

变长指令

汇编代码

0x88

MOV Eb, Gb

0x89

MOV Ev, Gv

0x8A

MOV Gb, Eb

0x8B

MOV Gv, Ev

助记:

爸爸和芭比(8b)娃娃参加89薛超,最后都移动(mov)用AK干掉了!==》88~8B对应mov

 

指令中参数的释义如下所示:

G:通用寄存器

E:寄存器/内存

b:字节

v:字、双字或四字

理解ModR/M

ModR/M这个字节的8个位被拆分成了3个部分:

images/download/attachments/21791281/image2021-11-1_21-36-32.png

其中,Reg/Opcode(第3、4、5位,共3个位)描述指令中的G部分,即寄存器,如下就是这三个位对应寄存器的表示:

images/download/attachments/21791281/image2021-11-1_21-43-22.png

Mod(第6、7位,共2个位)和R/M(第0、1、2位,共3个位)共同描述指令中的E部分,即寄存器/内存。

那么,这8个位具体是如何工作的呢,Intel操作手册给出了一张表(Table 2-2):

images/download/attachments/21791281/image2021-11-1_21-53-38.png

 

 

手动解析指令

现在有一个指令为:0x88 0x84 0x48,其对应的Opcode、ModR/M、SIB如下:

Opcode

ModR/M

SIB

0x88 - MOV Eb, Gb

0x84

0x48

ModR/M转为二进制:1000 0100,拆分如下:

Mod

Reg/Opcode

R/M

1

0

0

0

0

1

0

0

Mod与R/M字段查Table 2-2得到对应的结构:[--][--]+disp32,这就表示需要SIB来进行补充。

SIP转为二进制:0100 1000,拆分如下:

Scale

Index

Base

0

1

0

0

1

0

0

0

接着查Table 2-3,Base对应着EAX,Base和Index就是[ECX*2],最终得到[EAX + ECX*2]。

最终指令就是:MOV [EAX + ECX * 2 + disp32], AL

 

posted @ 2023-04-17 10:51  bonelee  阅读(201)  评论(0编辑  收藏  举报