跳转至

x86-64 汇编、逆向与 Pwn 基础

汇编语言是机器指令的人类可读表示。它不负责隐藏硬件细节,而是直接描述数据位于哪里、CPU 做了什么,以及下一条指令从哪里执行。 本章以 x86-64、Intel 语法、Linux ELF 与 System V AMD64 ABI 为主线,目标不是让读者背诵指令表,而是建立一套能用于阅读编译器输出、调试程序和理解二进制安全问题的模型。

适用范围

  • 初次接触寄存器、内存和机器指令的读者
  • 希望把 C 代码与反汇编对应起来的读者
  • 准备学习 Linux Reverse 或 Pwn 基础的读者
  • 需要辨认 Windows x64 反汇编差异的读者 文中的安全内容用于授权环境、课程实验和 CTF。重点是识别缺陷、理解保护与修复程序,不提供可直接用于真实目标的武器化 payload。

默认约定

除非特别说明,示例遵守以下约定: | 项目 | 默认值 | |---|---| | 指令集 | x86-64,也称 AMD64 或 Intel 64 | | 汇编格式 | Intel:目的操作数, 源操作数 | | 操作系统 | Linux | | 可执行格式 | ELF64 | | C 调用约定 | System V AMD64 ABI | | 栈增长方向 | 从高地址向低地址 | | 字节序 | 小端序 | 不同反汇编器可能显示 AT&T 语法。AT&T 通常写成 源, 目的,寄存器带 %,立即数带 $。例如:

; Intel
mov eax, 1

# AT&T
movl $1, %eax
不要把语法显示差异误认为指令行为不同。

学习路线

1. 架构、数据与数值表示

架构、数据与数值表示 解释 CPU、ISA、机器码、十六进制、补码、小端序,以及整数和浮点数在二进制中的基本表示。 读完后应能回答: - 0xff 在 8 位有符号和无符号语境中各代表什么? - 为什么内存中的 0x12345678 显示为 78 56 34 12? - 汇编器、编译器、链接器和加载器分别做什么?

2. 寄存器、内存与栈

寄存器、内存与栈 建立运行时状态模型,覆盖通用寄存器、子寄存器、虚拟地址空间、内存权限、栈帧和对齐。 读完后应能回答: - 写 eax 为什么会清零 rax 的高 32 位? - rsp、rbp、rip 分别有什么作用? - 局部变量、返回地址和堆对象通常在哪里?

3. 指令、寻址与控制流

指令、寻址与控制流 介绍数据传送、算术、位运算、有效地址、标志位、条件跳转、循环和间接控制流。 读完后应能回答: - [rbx+rcx*4+8] 如何计算? - cmp 为什么不保存减法结果? - 有符号比较和无符号比较为什么使用不同跳转条件?

4. 函数与调用约定

函数与调用约定 解释 call、ret、参数、返回值、保存责任、栈对齐、可变参数和 Windows x64 差异。 读完后应能回答: - Linux x86-64 的前六个整数参数位于哪些寄存器? - caller-saved 与 callee-saved 有什么区别? - Windows x64 的 shadow space 是什么?

5. 工具、反汇编与调试

工具、反汇编与调试 给出 GCC、binutils、GDB、IDA 和 Ghidra 的基础工作流,并说明如何验证静态分析结论。 读完后应能回答: - 如何查看 ELF 的节、符号、导入和反汇编? - 如何在 GDB 中检查寄存器、内存和调用栈? - 为什么反编译伪代码不能等同于原始源码?

6. 面向 Reverse 与 Pwn 的汇编

面向 Reverse 与 Pwn 的汇编 将前述知识用于控制流还原、输入边界分析、栈破坏识别、ROP 概念和防护机制判断。 读完后应能回答: - 如何从汇编中恢复 if、循环和 switch? - 栈溢出为什么可能破坏控制数据? - NX、ASLR、PIE、Canary 和 RELRO 分别降低什么风险?

建议的练习方式

学习汇编最有效的方法是让源码、反汇编和运行状态互相印证: 1. 写一个只包含少量表达式或分支的 C 函数。 2. 使用 gcc -O0 -g 编译,保留清晰的调试信息。 3. 使用 objdump -d -M intel 查看反汇编。 4. 在 GDB 中逐条执行并观察寄存器和内存。 5. 再用 -O2 编译,比较优化后的控制流和数据流。 一个适合实验的命令序列是:

gcc -O0 -g -Wall -Wextra demo.c -o demo
objdump -d -M intel demo
gdb -q ./demo
不要为了让练习“更像漏洞题”而关闭系统保护。初学阶段应先在默认安全配置下理解程序行为;若课程要求比较保护差异,也应只对本地、可丢弃的实验程序操作。

阅读汇编的核心方法

面对一段陌生代码,可以按固定顺序分析: 1. 确定架构、位数、语法和 ABI。 2. 标记函数入口、出口、调用和跳转目标。 3. 记录参数寄存器、返回寄存器和关键内存地址。 4. 把 cmp/test 与紧随其后的条件跳转配对。 5. 为栈槽和寄存器赋予暂时的语义名称。 6. 识别循环、不变量、数组步长和结构体偏移。 7. 用调试器验证关键输入下的实际值。 汇编中的寄存器只是存储位置。rax 一会儿可以是整数,一会儿可以是指针;真正的“类型”来自指令宽度、数据来源、使用方式和上下文。

常见误区

  • mov 通常是复制,不是把源位置清空。
  • lea 计算地址表达式,通常不读取该地址指向的内存。
  • 栈不是特殊硬件内存,而是由 rsp 约定管理的一段普通虚拟内存。
  • 反汇编能恢复指令,不能可靠恢复原变量名、注释和全部类型。
  • 反编译器输出是推测出的等价伪代码,不是原始源码。
  • je 与 jz 编码和条件相同,只是名称强调的语境不同。
  • jg 与 ja 不等价:前者用于有符号大于,后者用于无符号大于。
  • x86-64 Linux 与 Windows x64 使用相同核心指令集,但 ABI 和文件格式不同。
  • 现代编译器经常省略 rbp 栈帧,因此不能只靠固定序言识别函数。

Linux 与 Windows x64 快速对照

项目 Linux System V Windows x64
文件格式 ELF PE/COFF
前四个整数参数 rdi, rsi, rdx, rcx rcx, rdx, r8, r9
第五、六个整数参数 r8, r9 栈
整数返回值 rax rax
调用前栈对齐 16 字节规则 16 字节规则
调用者预留区 无 shadow space 32 字节 shadow space
red zone 有,叶函数可用 128 字节 无
常见调试器 GDB WinDbg
完整细节应以对应平台 ABI 文档为准,不能只凭寄存器名称猜测参数。
## 学习目标检查
完成七篇内容后,你应能:
- 准确解释常见 x86-64 指令对寄存器、内存和标志位的影响
- 依据 System V ABI 跟踪普通 C 函数的参数与返回值
- 在 GDB 中设置断点、单步、查看状态并验证假设
- 从反汇编中恢复常见表达式、分支、循环和函数边界
- 识别不安全内存操作可能影响的数据范围
- 解释常见二进制保护的作用与局限
- 在 Reverse/Pwn 学习中坚持授权、最小影响和防御性分析
下一篇从二进制程序最基础的数据表示开始:架构、数据与数值表示。