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 通常写成 源, 目的,寄存器带 %,立即数带 $。例如:
学习路线¶
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 编译,比较优化后的控制流和数据流。
一个适合实验的命令序列是:
阅读汇编的核心方法¶
面对一段陌生代码,可以按固定顺序分析:
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 学习中坚持授权、最小影响和防御性分析 | ||
| 下一篇从二进制程序最基础的数据表示开始:架构、数据与数值表示。 |