硬编码

硬编码

硬编码简介

硬编码就是指令编码格式

image-20210517110614296

x64比x86主要是多了一组指令前缀,还有比如地址偏移和立即数可以允许8个字节(x86只允许最多4个)

这里学习的主要是x86

无论是x86还是x64的指令格式,都是最短一个字节,最长15个字节

硬编码转换为汇编语言的过程叫反汇编

哪些人需要学习硬编码

  1. 病毒与反病毒
  2. 加密与破解
  3. 外挂与反外挂

所有与计算机底层相关的行业都需要学习

汇编指令有六部分构成

  1. Prefix—-指令前缀(可选项)
  2. Opcode—-操作指令(必选项)
  3. Mod R/M—操作数辅助说明(可选项)
  4. SIB—-Mod R/M辅助说明(可选项,但是出现Mod R/M 这个必须有)
  5. Displacement—操作数作为内存地址时用来表示位移(可选项)
  6. Immediate —-表示操作数为立即数(可选项)

前缀指令(Instruction Prefixes)

该指令是可选的,最多可以在每组中选一个,最少可以什么都不写。并且前缀指令和顺序没关系

在od中会把前缀指令和后面的指令中间加上冒号:方便用户识别

前缀指令分组

  1. LOCK和REPEAT前缀指令

    LOCK===F0 REPNE/REPNZ===F2 REP/REPZ===F3

  2. 段前缀指令

    CS(2E) SS(36) DS(3E) ES(26) FS(64) GS(65)

  3. 操作数宽度前缀指令

    66

  4. 地址宽度前缀指令

    67

LOCK和REPEAT前缀指令

LOCK

锁地址总线,在多核下才有意义,使同一时刻只有一个核可以读这个地址。(单核下,该指令没有意义)

REPNE/REPNZ

重复执行(zero flag为0的时候执行)

REP/REPZ

重复执行(zero flag为1的时候执行)

段前缀指令

CS(2E) SS(36) DS(3E) ES(26) FS(64) GS(65)

明确地告诉cpu要使用的是哪个段

不写段前缀指令的情况,如果访问的是全局地址,则默认访问DS;若访问的是ebp,esp,则默认是访问SS段

操作数宽度前缀指令

66

用来改变操作数宽度的前缀指令(默认32位,加66就改成16位;默认16位,加66就改成32位)

1
2
3
4
55
//PUSH EBP
66 55
//PUSH BP

地址宽度前缀指令

67

用来改变寻址方式的(默认32位,加66就改成16位;默认16位,加66就改成32位)

1
2
3
4
8965 E8
//MOV DWORD PTR SS:[EBP-18],ESP
67 8965 E8
//MOV DWORD PTR DS:[DI+FFE8],ESP

定长指令和变长指令

整个硬编码的长度取决于Opcode(operand code操作码),ModR/M和SIB这三个字段

Opcode决定了后面有没有ModR/M(没有ModR/M就是定长)

ModR/M决定了后面有没有SIB

Opcode可以决定整个指令是否定长还是变长(此处的定长是不包含前缀的)

变长指令:仅仅通过Opcode没办法确定长度的指令

image-20210517121504731

上图中Opcode为50的那行指令是定长指令。

Opcode为00的那三行指令是变长指令。

一个字节的操作码查表(下图只是一个字节的操作码,还有两个和三个字节的操作码在因特尔白皮书中)

(intel白皮书1737页)

image-20210517124255812

image-20210517122807231

查表方式:

1502566-20181215195735075-1632848315

图中的Eb是因特尔的Zz表示法,即一个大写带一个小写的字母表示,对应的含义也需要查表

(intel白皮书1731页)

  • Codes for Addressing Method(大写字母)
  • Codes for Operand Type(小写字母)

p.s.图中大写E和大写G表示的一定是后面接了ModR/M的(即变长指令)

image-20210601180927470

image-20210601180946471

1735页

image-20210601181145342

部分表中标志含义解读:

  • i64 invalid,在64位系统64位操作数无效,只能32位操作数
  • o64 only,在64位系统只有在64位模式有效
  • d64 default,在64位系统默认操作数宽度64,不能编码宽度32位的操作数,意思是其他位可以
  • f64 forced,在64位系统强制64位操作数,哪怕前面加了操作数宽度前缀指令。

经典定长指令_修改寄存器

PUSH/POP

  • 0x50~0x57为push
  • 0x58~0x5F为pop
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

INC/DEC(i64)

  • 0x40~0x47为INC
  • 0x48~0x4F为DEC

MOV Rb,lb(定长2个字节)

0xb0~0xb7

1
2
3
4
//B0是操作码,A4是操作数
0xB0 0xA4 MOV AL,0A4
//什么叫定长,只要opCode确定,那么这行指令的长度就确定了
//如上,只要B0确定了,那么这个指令一定占两个字节

MOV ERX,ld(定长5个字节)

0xb8~0xbF

1
B8 8DA42400    MOV EAX,24A48D

XCHG EAX,ERX

0x90~0x97 XCHG EAX,ERX

1
2
3
4
5
6
90   NOP  //XCHG EAX,EAX没有任何意义,所以给90赋予新的意义,NOP,CPU遇到他不做任何事情直接跳过他,他唯一的作用就是用来对齐其他指令
91 XCHG EAX,ECX//交换EAX和ECX里面的值
92 XCHG EAX,EDX
93 XCHG EAX,EBX
94 XCHG EAX,ESP
...

可通过NOP指令构造花指令使得调试器的汇编混乱

image-20210601161252391

假如上图中框选的部分为我们要跳过来77DE01E8执行的代码段,这是没使用花指令的情况下。

我们把77DE01E7的90改成B0

image-20210601161559344

由于B0是定长2个字节的操作码,因此会吃掉后面一个字节的数据,所以汇编的过程中就显示出错了,但是实际上执行的时候,程序还是跳转到77DE01E8正常执行代码。

如何去除这种花指令,从上面执行下来的时候,跳转到的应该是77DE01E8,但在上图中,左列地址列却没有显示77DE01E8的地址,很显然该地址被花指令了

经典定长指令_修改EIP

0x70~0x7F短跳(定长两个字节)

  • 条件跳转,后跟一个字节立即数的偏移(有符号),共两个字节。
  • 如果条件成立,跳转到当前指令地址+当前指令长度+lb
  • 向前跳07f(向大地址跳),向后跳FF80

向前跳:
$$
目标地址-(当前命令地址+当前命令长度)的原码
$$
大地址-小地址

向后跳:
$$
目标地址-(当前命令地址+当前命令长度)的原码舍弃溢出位
$$
小地址-大地址(舍弃溢出位)

也可以理解成
$$
(当前命令地址+当前命令长度)-目标地址的补码
$$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
//e.g.
004183DD--0x73 0x80 JNB SHORT 0041835F

0x0F 0x80~0x0F 0x8F长跳(定长6个字节)

和短跳的语法完全一样

向前跳0000 00007FFF FFFF(向大地址跳),向后跳FFFF FFFF8000 0000

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
0x0F 0x70   JO
0x0F 0x71 JNO
0x0F 0x72 JB/JNAE/JC
0x0F 0x73 JNB/JAE/JNC
0x0F 0x74 JZ/JE
0x0F 0x75 JNZ/JNE
0x0F 0x76 JBE/JNA
0x0F 0x77 JNBE/JA
0x0F 0x78 JS
0x0F 0x79 JNS
0x0F 0x7A JP/JPE
0x0F 0x7B JNP/JPO
0x0F 0x7C JL/JNGE
0x0F 0x7D JNL/JGE
0x0F 0x7E JLE/JNG
0x0F 0x7F JNLE/JG
//e.g.
004183E9--0F83 00606489 JNB 89A5D3EF

其他指令

image-20210601180602207

Jb:J表示间接寻址,即操作数是相对偏移;b表示操作数一个字节

下面的暂时不讲(没进0环之前下面指令基本用不到)

image-20210601181544726

Ap:A表示直接寻址,p表示当前操作数宽度是16位,p就表示32位,操作数32位,p表示48位,操作数64位,p表示80位

1
EA 410064A1 0000   JMP FAR 0000:A1640041//跨段跳转,讲调用门的时候会用到

EB是一个字节内的跳转,E9是超越一个字节的跳转,EA是跨段跳转

C2的return用于内平衡

EA,CB,CA都常用于0环

经典变长指令_ModR/M

Mod R/M 大小为一个字节,由三部分组成,分别为 Mod(字节前两位),Reg(字节中间三位),R/M(字节后三位)。Mod R/M的主要功能就是说明操作数的寻址方式,包括寄存器选择,内存操作数的偏移等等。例如:

1
66:81FE 4746   CMP SI,474

因特尔白皮书中指令查表中,E和G开头的表示变长指令。

经典变长指令_ModR/M

1
2
3
4
0x88   MOV Eb,Gb	G:通用寄存器
0x89 MOV Ev,Gv E:通用寄存器或者内存
0x8A MOV Gb,Eb b:字节
0x8B MOV Gv,Ev v:Word,doubleword or quadword

这里应该注意的是,E中已经说明了要使用通用寄存器或者内存操作数,并且要使用Mod R/M来进行辅助说明,所以在操作码89后的C1就是ModR/M,Ev统一起来就是可以使用双子寄存器操作数或者双子内存操作数,具体的要使用ModR/M字段来辅助说明。

image-20210603151613245

ModR/M的目的就是告诉我们G和E到底是谁

这8个位究竟是如何工作的,Inter操作手册给出了一张表

1502566-20181215195902895-1924133618

image-20210603162026207

image-20210603162045657

因为操作指令是88,所以MOV Eb,Gb,由于b所以无论E和G都是一个字节,所以上图说EAX还是AL是由操作码决定的

经典变长指令_RegOpcode

Reg/Opcode除了可以确定G,还有时候作为opcode的拓展位来确定操作指令

image-20210603163024666

举例说明0x80 0x65 0x08 0xFF查表

image-20210603164816516

DIS8表示8位偏移

image-20210603165041786

1A可以查表看意思:image-20210603165406263

1A标明了ModR/M中的3,4,5位作为opcode的扩展

80代表的Opcode未标明操作指令,凡是写了Grp的,均参见TableA-6(1748页)

上图红线处Grp和上标1A中间的1代表第1组,可以在下表TableA-6中的Group中对应第1组

image-20210603165308505

总结一下0x80 0x65 0x08 0xFF查表流程

  1. 首先看到了0x80作为opcode查到了格式为xxx Eb,Ib(此时不确定操作指令是什么),通过1A知道操作指令取决于opcode和ModR/M的3,4,5位,并且知道了查表找操作指令的时候要查的是第一组

  2. 通过ModR/M的1,2,6,7,8,位:0x65=01 100 101(参考上一章知识)确定Eb是[EBP+DIS8],该指令当前为xxx byte ptr ds:[EBP+DIS8],Ib

  3. 通过ModR/M的3,4,5位:image-20210603171014362

    确定操作指令为AND,当前的指令为AND byte ptr ds:[EBP+DIS8],Ib

  4. 完整编码的后半部分0x08和0xFF按硬编码结构顺序填入DIS8(8位偏移)和Ib(一个字节的立即数),由此得到完整指令:AND byte ptr ds:[EBP+0x08],0xFF

经典变长指令_SIB

SIB—-Mod R/M辅助说明(可选项)

阶段性总结

image-20210603172025574

Opcode决定了后面是否有ModR/M,ModR/M决定了后面是否存在SIB,Displacement和immediate

image-20210603172713610

查ModR/M表的时候如果ModR/M1,2,6,7,8位查到上图红线[–]这种形式,标明ModR/M后面需要再跟一个字节,就是SIB来确定[–]里面的信息

image-20210603173312603

sib用到的表

image-20210603173524173

比如0x88 0x84 0x48查表确定到MOV Eb,Gb

MOV [–][–]+DISP32,AL

SIB为0x48 拆分01 001 000

000的base部分查到EAX(下图红线)

image-20210603173832029

01的Scale部分决定了拿个SS

image-20210603174113527

001的Index部分

(此处缺了一张图)

得出结论SIB== [EAX+ECX*2]

所以结论为MOV byte ptr DS:[EAX+ECX*2+DISP32],AL

这也意味着0x88 0x84 0x48后面还会吃掉4个字节来作为DISP32.

1
888448 6AFF6818		MOV BYTE PTR DS:[EAX+ECX*2+1868FF6A],AL

32位二进制中含操作数的汇编代码总结汇总

汇编指令 汇编二进制形式 大小(字节)
retn 0~0xFFFF C2 FFFF 3
直接call 0x1 E8 (4字节)目标地址-call地址-5 5
间接call [0x12345678] FF 15 78 56 34 12 6
短jmp EB (1字节)目标地址-jmp地址-2 2
长jmp E9 (4字节)目标地址-jmp地址-5 5
间接jmp [0x12345678] FF 25 78 56 34 12 6
直接push 0x12345678 68 78 56 34 12 5
间接push [0x12345678] FF 35 78 56 34 12 6
直接add eax 0x100 05 00 01 00 00 5
间接add eax [0x12345678] 03 05 00 01 00 00 6
等等等等(非常多)

汇编大全参考