### 讲讲 Linux 平台的漏洞缓解机制

1. 堆栈保护（Stack Smashing Protection, SSP）

这是最基础，也是最重要的堆栈溢出缓解机制

- 原理： 在函数调用时，编译器会在栈上的局部变量和返回地址之间插入一个随机的“金丝雀值”（Canary Value）
- 工作方式：
  - 函数进入时，金丝雀值被推入栈中
  - 函数返回前，程序会检查这个金丝雀值是否被改变
  - 如果金丝雀值被修改，说明发生了缓冲区溢出，程序会立即终止（通常会调用 `__stack_chk_fail` 函数），而不是让攻击者控制程序流
- 局限性： 攻击者可以通过覆盖低地址的变量或利用其他漏洞（如格式化字符串漏洞）来泄露金丝雀值，从而绕过此保护

2. 地址空间布局随机化（Address Space Layout Randomization, ASLR）

ASLR 是一个非常有效的漏洞缓解机制，它让攻击者难以预测内存中关键数据的位置

- 原理： 每次程序启动时，ASLR 会将程序的主要内存区域（如可执行文件基址、堆、栈和共享库（DLL/SO））加载到随机的地址上
- 工作方式： 攻击者在利用漏洞时，通常需要知道某个函数（例如 `system` 函数）或某个数据（例如返回地址）的精确内存地址。ASLR 打破了这种确定性，使得攻击者无法在不知道这些地址的情况下构造 ROP 链（Return-Oriented Programming）
- 局限性：
  - 熵不足： 早期版本的 ASLR 随机化范围有限，攻击者可以通过暴力破解或多次尝试来绕过
  - 信息泄露： 如果程序存在信息泄露漏洞（如格式化字符串漏洞），攻击者可以泄露出某个模块的基址，从而推算出其他所有函数的地址，绕过 ASLR

3. 不可执行内存（Non-Executable Memory）/ NX 位（No-eXecute）

这是为了防止攻击者将恶意代码注入数据段（如堆或栈）并执行而设计的

- 原理： CPU 的 MMU（内存管理单元）会根据内存页的权限来决定是否允许执行该页中的代码。NX 位被设置在页表项中，如果该位为 1，则该页不可执行
- 工作方式： 操作系统会将堆、栈等数据段标记为不可执行。当攻击者利用缓冲区溢出将 Shellcode（恶意代码）写入栈上并尝试执行时，CPU 会抛出异常，阻止代码的执行
- 局限性：
  - ROP 攻击： ROP（Return-Oriented Programming）是一种绕过 NX 位的方法。攻击者不注入代码，而是通过利用程序本身已有的代码片段（称为“gadgets”），并精心构造返回地址链来执行恶意逻辑
  - JIT（即时编译）代码： 对于一些需要动态生成和执行代码的应用程序（如 Java 或 JavaScript 引擎），它们需要创建可执行的内存区域，这可能会被攻击者利用

4. 只读重定位（Read-Only Relocations, RELRO）

RELRO 旨在保护程序在加载后不被修改，特别是全局偏移表（GOT）和过程链接表（PLT）

- 原理：
  - GOT（Global Offset Table）： 存储了外部共享库函数的实际地址
  - PLT（Procedure Linkage Table）： 负责将函数调用重定向到 GOT
- 工作方式：
  - 部分 RELRO： 在程序加载时，`.got` 段是可写的，因为加载器需要填充外部函数的地址。加载后，`.got` 段变为只读
  - 完全 RELRO： 将 GOT 表完全设置为只读，所有重定位都在程序加载前完成
- 局限性： 攻击者无法再利用 GOT 覆写漏洞来劫持程序流。然而，它并不能防御所有类型的攻击

5. 控制流完整性（Control Flow Integrity, CFI）

CFI 是一种更高级的保护机制，它旨在确保程序执行的控制流不会被攻击者劫持

- 原理： CFI 在编译和链接阶段为每个间接调用（如函数指针调用）和返回指令创建元数据，并在运行时检查这些元数据，确保控制流的跳转是合法的、预期的
- 工作方式：
  - 向前边沿 CFI（Forward-Edge）： 保护间接函数调用，确保函数指针只能跳转到其类型兼容的函数
  - 向后边沿 CFI（Backward-Edge）： 保护函数返回，确保返回地址不会被篡改
- 局限性： CFI 的实现较为复杂，并且可能引入性能开销。虽然能防御 ROP 攻击，但并不能防御所有类型的攻击，且可以被绕过