从零开始的 Pwn 之旅 - x86 汇编基础

汇编语言风格

AT&TIntel
寄存器前缀 %寄存器无前缀
立即数前缀 $立即数无前缀
16 进制数前缀 0x16 进制数后缀 h
源操作数在前,目标操作数在后源操作数在后,目标操作数在前
间接寻址使用 (%reg)间接寻址使用 [reg]
间接寻址格式为 (%reg, %reg, scale)间接寻址格式为 [reg + reg * scale]
操作数大小后缀 bwl操作数大小后缀 byte ptrword ptrdword ptr

寄存器

寄存器是 CPU 内部的高速存储器,用于存储临时数据和指令执行状态。

如下图所示,以 r 开头的寄存器为 64 位寄存器,e 开头的寄存器为 32 位寄存器,ax、bx 等为 16 位寄存器。 在 64 位模式下,寄存器的高 32 位可以通过 e 前缀访问,例如 eaxrax 的低 32 位。 同样,寄存器的低 16 位可以通过 x 后缀访问,例如 axeax 的低 16 位。

通用寄存器示意图

特殊寄存器

  • 段寄存器:用于存储内存段的基地址,常用的段寄存器有 cs(代码段)、ds(数据段)、ss(堆栈段)、esfsgs

  • 标志寄存器:用于存储 CPU 的状态信息,包括零标志位、符号标志位、进位标志位等。

  • 指令指针寄存器:eip(32 位模式)或 rip(64 位模式),用于存储下一条要执行的指令的地址。

  • 堆栈指针寄存器:esp(32 位模式)或 rsp(64 位模式),用于指向当前堆栈的顶部。

  • 基址寄存器:ebp(32 位模式)或 rbp(64 位模式),用于指向当前函数的堆栈帧基址。

常见指令

  • MOV:数据传送指令,用于将数据从一个位置移动到另一个位置。

![NOTE] BYTE、WORD 和 DWORD 分别表示 1 字节、2 字节和 4 字节的数据类型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
mov eax, ebx  ; 将 ebx 的值复制到 eax

mov [eax], ebx  ; 将 ebx 的值存储到 eax 指向的内存地址

mov eax, 0x1234  ; 将立即数 0x1234 传送到 eax

mov eax, [ebx + 4]  ; 将 ebx 指向的内存地址加 4 后的值加载到 eax

.data
  testArrayB BYTE 99h, 98h, 97h  ; 定义一个字节数组
  testArrayW WORD 0x1234, 0x5678  ; 定义一个字
  testArrayD DWORD 0x12345678, 0x9ABCDEF0  ; 定义一个双字数组

.code
  mov al, [testArray]  ; 将 testArray 数组的第一个字节加载到 al 寄存器
  mov [testArray + 1], al  ; 将 al 寄存器的值存储到 testArray 数组的第二个字节
  mov [testArray + 2], 0x00  ; 将 0x00 存储到 testArray 数组的第三个字节

  mov bx, [testArrayW]  ; 将 testArrayW 数组的第一个字加载到 bx 寄存器
  mov [testArrayW + 2], bx  ; 将 bx 寄存器的值存储到 testArrayW 数组的第二个字

  mov [testArrayD], 0x12345678  ; 将 0x12345678 存储到 testArrayD 数组的第一个双字
  mov [testArrayD + 4], 0x9ABCDEF0  ; 将 0x9ABCDEF0 存储到 testArrayD 数组的第二个双字
  • LEA:加载有效地址指令,用于将内存地址加载到寄存器中。
lea 指令中的 [] 不用于取值,表示一个内存地址计算表达式
1
2
3
4
lea eax, [ebx + 4]  ; 将 ebx 指向的内存地址加 4 后的地址加载到 eax
; 若 ebx = 0x1000,则 eax = 0x1004

lea eax, [ebx + 4*ecx]  ; 将 ebx 指向的内存地址加 4 倍的 ecx 后的地址加载到 eax
  • INC:自增指令,用于将寄存器或内存中的值加 1。
1
2
inc eax  ; 将 eax 的值加 1
inc [ebx]  ; 将 ebx 指向的内存地址中的值加 1
  • DEC:自减指令,用于将寄存器或内存中的值减 1。
1
2
dec eax  ; 将 eax 的值减 1
dec [ebx]  ; 将 ebx 指向的内存地址中的值减 1
  • ADD:加法指令,用于将两个操作数相加。
1
2
3
add eax, ebx  ; 将 ebx 的值加到 eax 中
add [ebx], ecx  ; 将 ecx 的值加到 ebx 指向的内存地址中的值
add eax, 0x1234  ; 将立即数 0x1234 加到 eax 中
  • SUB:减法指令,用于将一个操作数从另一个操作数中减去。
1
2
3
sub eax, ebx  ; 将 ebx 的值从 eax 中减去
sub [ebx], ecx  ; 将 ecx 的值从 ebx 指向的内存地址中的值中减去
sub eax, 0x1234  ; 将立即数 0x1234 从 eax 中减去
  • NEG:取反指令,用于将寄存器或内存中的值取反。
1
2
neg eax  ; 将 eax 的值取反
neg [ebx]  ; 将 ebx 指向的内存地址中的值取反
  • XOR:按位异或指令,用于将两个操作数按位异或。
1
2
3
4
xor eax, ebx  ; 将 eax 和 ebx 的值按位异或
xor [ebx], ecx  ; 将 ebx 指向的内存地址中的值和 ecx 的值按位异或
xor eax, 0x1234  ; 将立即数 0x1234 和 eax 的值按位异或
xor eax, eax  ; 将 eax 的值清零(等同于 mov eax, 0)
  • XCHG:交换指令,用于交换两个操作数的值。
1
2
xchg eax, ebx  ; 交换 eax 和 ebx 的值
xchg [ebx], ecx  ; 交换 ebx 指向的内存地址中的值和 ecx 的值
  • CMP:比较指令,用于比较两个操作数的值。
CMP 指令不会修改操作数的值,只会设置标志寄存器中的标志位。
1
2
3
4
5
cmp eax, ebx  ; 比较 eax 和 ebx 的值
jmp label  ; 如果比较结果为零,则跳转到 label

cmp [ebx], ecx  ; 比较 ebx 指向的内存地址中的值和 ecx 的值
jmp label  ; 如果比较结果为零,则跳转到 label
  • TEST:按位与指令,用于将两个操作数按位与,并设置标志寄存器中的标志位。
1
2
test eax, ebx  ; 将 eax 和 ebx 的值按位与,并设置标志寄存器中的标志位
jmp label  ; 如果结果为零,则跳转到 label
  • JZ/JNZ:条件跳转指令,用于根据标志寄存器中的零标志位进行跳转。
1
2
cmp eax, ebx  ; 比较 eax 和 ebx 的值
jz label  ; 如果比较结果为零,则跳转到 label
指令英文全称跳转条件(标志位)对应关系(易懂)常见用途(有/无符号)
JEJump EqualZF=1等于时跳转有符号/无符号
JZJump ZeroZF=1等于时跳转有符号/无符号
JNEJump Not EqualZF=0不等于时跳转有符号/无符号
JNZJump Not ZeroZF=0不等于时跳转有符号/无符号
JAJump AboveCF=0 且 ZF=0(无符号)大于时跳转无符号
JNBEJump Not Below or EqualCF=0 且 ZF=0(无符号)大于时跳转无符号(同 JA)
JAEJump Above or EqualCF=0(无符号)大于等于时跳转无符号
JNBJump Not BelowCF=0(无符号)大于等于时跳转无符号(同 JAE)
JBJump BelowCF=1(无符号)小于时跳转无符号
JNAEJump Not Above or EqualCF=1(无符号)小于时跳转无符号(同 JB)
JBEJump Below or EqualCF=1 或 ZF=1(无符号)小于等于时跳转无符号
JNAJump Not AboveCF=1 或 ZF=1(无符号)小于等于时跳转无符号(同 JBE)
JGJump GreaterZF=0 且 SF=OF(有符号)大于时跳转有符号
JNLEJump Not Less or EqualZF=0 且 SF=OF(有符号)大于时跳转有符号(同 JG)
JGEJump Greater or EqualSF=OF(有符号)大于等于时跳转有符号
JNLJump Not LessSF=OF(有符号)大于等于时跳转有符号(同 JGE)
JLJump LessSF≠OF(有符号)小于时跳转有符号
JNGEJump Not Greater or EqualSF≠OF(有符号)小于时跳转有符号(同 JL)
JLEJump Less or EqualZF=1 或 SF≠OF(有符号)小于等于时跳转有符号
JNGJump Not GreaterZF=1 或 SF≠OF(有符号)小于等于时跳转有符号(同 JLE)
JCJump CarryCF=1有进位时跳转无符号运算
JNCJump No CarryCF=0无进位时跳转无符号运算
JOJump OverflowOF=1溢出时跳转
JNOJump No OverflowOF=0无溢出时跳转
JSJump SignSF=1结果为负时跳转
JNSJump No SignSF=0结果为正时跳转
JPJump ParityPF=1奇偶标志为1时跳转(偶数)
JPEJump Parity EvenPF=1奇偶标志为1时跳转(偶数)
JNPJump No ParityPF=0奇偶标志为0时跳转(奇数)
JPOJump Parity OddPF=0奇偶标志为0时跳转(奇数)
JMPJump无条件直接跳转
JMPFJump Far无条件跳转到指定段/内存地址
  • CALL:调用指令,用于调用子程序或函数。
1
2
3
4
5
6
7
call function_name  ; 调用名为 function_name 的函数

call [ebx]  ; 调用 ebx 指向的内存地址中的函数

call 0x12345678  ; 调用指定地址的函数

call eax  ; 调用 eax 寄存器中存储的函数地址

call 指令可以拆解为 push 和 jmp 两条指令的组合:

1
2
push eip  ; 将当前指令指针压入堆栈 (eip 不允许被直接修改, 此条指令不存在)
jmp function_name  ; 跳转到函数的入口地址
  • PUSH:压栈指令,用于将数据压入堆栈。
1
2
3
4
push eax  ; 将 eax 的值压入堆栈
push [ebx]  ; 将 ebx 指向的内存地址中的值压入堆栈
push 0x1234  ; 将立即数 0x1234 压入堆栈
push dword ptr [ebx]  ; 将 ebx 指向的内存地址中的双字值压入堆栈
  • POP:弹栈指令,用于从堆栈中弹出数据。
1
2
3
pop eax  ; 从堆栈中弹出一个值到 eax
pop [ebx]  ; 从堆栈中弹出一个值到 ebx 指向的内存地址
pop dword ptr [ebx]  ; 从堆栈中弹出一个双字值到 ebx 指向的内存地址
  • RET:返回指令,用于从子程序或函数中返回。
1
ret  ; 返回到栈顶的地址

等价于 pop eip (eip 不允许被直接修改, 此条指令不存在)

  • LEAVE:离开指令,用于从子程序或函数中返回,并清理堆栈。

一般配合 ret 使用

1
2
leave  ; 等价于 mov esp, ebp; pop ebp
ret  ; 返回到栈顶的地址
  • INT:中断指令,用于触发软件中断。
1
int 0x80  ; 触发中断 0x80,通常用于系统调用
  • SYSCALL:系统调用指令,用于调用操作系统提供的服务。(64 位模式)
1
syscall  ; 调用操作系统的系统调用
  • LOOP:加载操作数指令,用于将数据从内存加载到寄存器。
1
2
loop label  ; 循环执行,直到 CX 寄存器的值为 0
; loop 指令会自动将 CX 寄存器的值减 1, jmp 到 label

标志位

  • TODO: 标志位的详细介绍