硬编码学习

1.定长指令

由opcode就可以知道这个指令的字节?

32位寄存器及其主要功能

image-20220320151101303
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//32位,一个字节
//PUSH,POP
50 PUSH EAX
51 PUSH ECX
52 PUSH EDX
53 PUSH EBX
54 PUSH ESP
55 PUSH EBP
56 PUSH ESI
57 PUSH EDI
58 POP EAX
59 POP ECX
5A POP EDX
5B POP EBX
5C POP ESP
5D POP EBP
5E POP ESI
5F POP EDI
//INC 操作数加1
40 INC EA:
41 INC ECX
42 INC EDX
43 INC EBX
44 INC ESP
45 INC EBP
46 INC ESI
47 ING ERI
//DEC 操作数减1
48 DEC EAX
49 DEC ECX
4A DEC EDX
4B DEC EBX
4C DEC ESP
4D DEC EBP
4E DEC ESI
4F DEC EDI

B0-B8,91-97

image-20220320151933166
1
2
3
4
5
6
7
8
9
10
B0 立即数 mov AL 立即数
B7 立即数 mov BH 立即数
//b0到b7是把立即数送到8位寄存器里al-bh
B8 立即数 mov EAX 立即数
//b8 - bf 把立即数送到32位寄存器里
91 XCHG EAX ECX
92 XCHG EAX EDX
....
//91-97 XCHG EAX 32位寄存器(ECX-EDI),交换2个寄存器的值,90=NOP

70-7F

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//一般2字节,后面跟立即数 	
70 JO //o标志位为1时跳转
//例,后面跟的数可以是有符号的
70 6A //6A<7F ->地址往后跳6A(以这个数的地址为基础)
70 8B //8B>7F ->往前跳
//如果条件成立,跳转到 当前指令地址 + 当前指令长度 + Ib(1个字节立即数)
//jcc Jb = 要跳转的地址 - 下一条指令的地址 = 要跳转的地址 - (当前指令地址 + 当前指令长度)
0x70 JO OF=1
0x71 JNO OF=0
0x72 JB/JNAE/JC CF=1
0x73 JNB/JAE/JNC CF=0
0x74 JZ/JE ZF=1
0x75 JNZ/JNE ZF=0
0x76 JBE/JNA ZF=1或者CF=1
0x77 JNBE/JA ZF=0或者CF=0
0x78 JS SF=1
0x79 JNS SF=0
0x7A JP/JPE PF=1
0x7B JNP/JPO PF=0
0x7C JL/JNGE SF!=OF
0x7D JNL/JGE SF=OF
0x7E JLE/JNG ZF!=OF 或 ZF=1
0x7F JNLE/JG ZF!OF 或 ZF=0


image-20220320224916256

8B是有符号数,二进制位1000 1011 = -117(十六进制75),所以

image-20220320232939952 image-20220320232651832

image-20220320180029381

0F 80 - 0F 8F

条件跳转,后跟四个字节立即数的偏移(有符号),共五个字节。
如果条件成立,跳转到 当前指令地址 + 当前指令长度 + Id(四字节)
最大值:向前跳7FFFFFFFF,向后跳80000000

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
			

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


image-20220321231116886

E0 - E9

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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 - 1ECX!=0 时跳转到 当前指令地址 + 当前指令长度 + Ib

0XE3 JrCXZ Ib (Jb) (在32位模式中,rCXECX) 共2字节
ECX = 0 时跳转到 当前指令地址 + 当前指令长度 + Ib
(自己控制步长)

0xE8 CALL Id (Jd) 共5字节
CALL指令的下一条指令地址入栈后,跳转到 当前指令地址 + 当前指令长度 + Id

0xE9 JMP Id (Jd) 共5字节
跳转到 当前指令地址 + 当前指令长度 + Id

0xEA JMP Ap (Ap:六字节长度的直接地址) 跨段跳转,共7字节
JMP CS:Id 将Ap中的高2位赋值给CS,低4位直接赋值给EIP, 即跳转

004183D7 > EA 12345678 1B00 JMP FAR 001B:78563412

0xEB JMP Ib (Jb)
跳转到 当前指令地址 + 当前指令长度 + Ib(一个字节)


C

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0xC3 	RET1字节							
EIP出栈

0xC2 RET Iw 共3字节 ret 4 = pop eip esp+4
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

ret -> pop eip
retf -> pop eip, pop cs


2.变长

88 -8B MOV

opcode决定后面的,像下列指令,就肯定有ModR/M字段

image-20220322231038735
1
2
3
4
5
6
7
经典变长指令_ModR/M							

0x88 MOV Eb, Gb G:通用寄存器
0x89 MOV Ev, Gv E:寄存器/内存 看mod的值 000110是内存,11是寄存器
0x8A MOV Gb, Eb b:字节 //这里后面有b说明看的是八位寄存器
0x8B MOV Gv, Ev v:Word, doubleword or quadword //取决当前cpu模式

image-20220322231748759

例如 MOV Eb, Gb 中3,4,5位决定G(通用寄存器),6,7和0-2位决定E

image-20220322232404169

主要是查这个表

image-20220322232833542

mod是100时理论上是esp,ebp,但是实际是没这个指令的,改成别的意义了

练习:

88 84 = MOV Eb Gb 84 = 10 000 100

000 表示G = 1号8位寄存器 = AL

10 = xx+disp32(32位的偏移) xx根据100决定

所以这个指令是 MOV byte ptr ds:[xx+32位偏移] ,al

不过xx是100,情况特殊,这就是接下来的指令

opcode决定modrm,modrm决定后面有没有SIB

所以字节低位是4的就有SIB

SIB字段

image-20220324002710075

当modRM为以上情况时,后续会有SIB字段,8个字节,被分成3个字段,这里disp32指32位的地址偏移,disp8同理

image-20220324003541893

例如, DS:[EAX + ECX*2 + 12345678] 中, Scale描述2^1, Index描述ECX, Base描述EAX,而12345678 由ModR/M字段决定。
所以SIB字段描述的方式为:
Base + Index*2^Scale (Scale描述2^Scale,所以只能为 *1 *2 *4 *8)

分析示例:MOV BYTE PTR DS:[EAX+ECX*2+78563412],AL

首先可以知道是MOV Eb Gb的形式,

然后分析这个指令,原型是0x88肯定有MODR/M字段,然后G是AL那么Reg/Opcode为000

然后观察这个指令形式有多个寄存器,形如下方的[–][–]+disp32,所以能确定MODR/M字段

image-20220324224736647

然后根据EAX+ECX*2+78563412,其中Base对应EAX,**Index对应ECX,***2^1说明Scale为1,可以得出SIB值为

image-20220324225148648

SIB字段的图表如下
1、当Index = 100 时,Index被0替代,此时只有Base有效

2、当Base = 101 时,Base被0替代,此时只用Index有效,最上面那行					
image-20220324225238995

由此可推出,ModR/M整个字段,最长是6个字节

88 84 48

89 84 84

89 84 22

89 AC 15

当Base为101时,解释如下,需要看MOD字段

image-20220324234036589

89 2C 15

MOD Ev Gv

00 101 100

Reg = 101 = EBP (v假设是32位 )

MOD R/M = 00 100 =[…][…]

SIB = 00 010 101

base = 101 看MOD为00 ,所以是index*2^1 + disp32

index =010 = EDX

v = 假设是32位运行 = DWORD

MOV DWORD PTR [EDX+disp32] EBP

3.查表

主表是TABLE A-2,在英特尔白皮书里

image-20220326190802686

例如 50 系列,先看这个表的对应位置,这里显示的是PUSH,但是后面还有个d64

image-20220326191018703

这个d64查另一个表

image-20220326191113328

这里i64说明这个指令在64位无效,o64只在64位有效,d64表示在64位下运行时默认按64位执行

rAX这类字符的原文说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
A.2.3 Register Codes
When an opcode requires a specific register as an operand, the register is identified
by name (for example, AX, CL, or ESI). The name indicates whether the register is
64, 32, 16, or 8 bits wide.
A register identifier of the form eXX or rXX is used when register width depends on
the operand-size attribute. eXX is used when 16 or 32-bit sizes are possible; rXX is
used when 16, 32, or 64-bit sizes are possible. For example: eAX indicates that the
AX register is used when the operand-size attribute is 16 and the EAX register is used
when the operand-size attribute is 32. rAX can indicate AX, EAX or RAX.
When the REX.B bit is used to modify the register specified in the reg field of the
opcode, this fact is indicated by adding “/x” to the register name to indicate the additional possibility. For example, rCX/r9 is used to indicate that the register could either
be rCX or r9. Note that the size of r9 in this case is determined by the operand size
attribute (just as for rCX)
机翻
当一个操作码需要一个特定的寄存器作为操作数时,该寄存器由名称(例如,AX、CL或ESI)标识。该名称指示寄存器是643216还是8位宽。
当寄存器宽度取决于操作数大小属性时,使用eXX或rXX形式的寄存器标识符。当16位或32位大小是可能的时,使用eXX;
16位、32位或64位大小是可能的时,使用rXX。例如:eAX表示当操作数大小属性为16时使用AX寄存器,当操作数大小属性为32时使用eAX寄存器。rAX可以表示AX、EAX或rAX。
当REX.B位用于修改操作码的reg字段中指定的寄存器,这一事实通过在寄存器名称中添加“/x”来表示,以表示额外的可能性。例如,rCX/r9用于指示寄存器可以是rCX或r9。请注意,在这种情况下,r9的大小由操作数大小属性决定(与rCX一样)

总之就是rAX是指多少位代表多少,16位是AX,32位是EAX,64是RAX

eAX只能是AX和EAX

如果是多位的opcode

例如之前学过的JCC指令,0x0f 0x8x,现在看0f处

image-20220326193318842

这就需要查A-3,(这里除了0F处,其他指令都是1字节)

然后查A-3

image-20220326193713065

这里第二位opcode是 38 ,3A的地方,要查A-4,A-5的表,说明opcode长度是3

image-20220326193957195

如果A2里有Grp,查A6

image-20220326194143351

段前缀


段寄存器的作用:早期8086cpu寻址范围小,Inter遍通过段寄存器来拓展内存.即通过段寄存器基址+偏移的方式来寻址。
[]中的地址为有效地址(Effect Address),有效地址+段寄存器基址才是实际地址LA(线性地址 Line Address)。
线性地址 = 段基址 + 有效地址
在后来的80386时,cpu的寻址范围大大提升,这些段寄存器便被用作了其他用途。但是DS:[]类似
这种寻址格式却被保留了下来。
实际上操作码已经决定了寻址时使用哪个段寄存器作为基址,不需要其他字节描述。
1、如果没有特别说明,[]前为DS,即DS:[]
2、PUSH POP指令,以及在[]中使用ESP/EBP的,使用SS段
3、在[Base + Index*2Scale + I]中,以Base为判断条件,没有特别说明,用DS。如果Base为ESP/EBP,则用SS段.
4、串操作指令一般使用ES。MOV ES:[EDI] DS:[ESI]中,目标EDI使用ES段,其他使用DS段.
5、EIP指向当前指令,EIP取指令时使用的是CS段.
6、如果指令加段寄存器前缀,则该条指令一律用这个段,如果加多个段寄存器前缀,默认只看op前的那个.

操作指令前缀 关于之前指令为啥没有16位寄存器

0x66 将操作数改为16字节。例子50为 PUSH EAX, 而66 50则为 PUSH AX

004183DA 50 PUSH EAX
004183DB 66:50 PUSH AX

操作指令前缀:修改默认寻址方式

0x67 将操作数改为16字节。例子50为 PUSH EAX, 而66 50则为 PUSH AX

004183FD 8801 MOV BYTE PTR DS:[ECX],AL
004183FF 67:8801 MOV BYTE PTR DS:[BX+DI],AL


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!,本博客仅用于交流学习,由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。 文章作者拥有对此站文章的修改和解释权。如欲转载此站文章,需取得作者同意,且必须保证此文章的完整性,包括版权声明等全部内容。未经文章作者允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。若造成严重后果,本人将依法追究法律责任。 阅读本站文章则默认遵守此规则。