缓冲区溢出与防御
基于通用操作系统概念 · 核于 2026-08
速查
- 缓冲区溢出(Buffer Overflow):向定长缓冲区写入超过其容量的数据,多出的字节覆盖相邻内存——若覆盖到栈上的返回地址,攻击者就能让程序跳转到自己植入的 shellcode 执行,从而劫持控制流。
- 栈溢出攻击原理:函数返回时 CPU 从栈上取返回地址(旧 PC)跳回调用者;攻击者用精心构造的超长输入把返回地址改写为 shellcode 的地址,函数一
ret就跳去执行攻击代码——这是经典的 stack smashing。 - Shellcode:攻击者植入的一段精简机器码(通常用来
exec("/bin/sh")开 shell),通过覆盖返回地址跳转过去执行。 - ASLR(地址空间布局随机化):每次运行随机化栈、堆、共享库的加载基址——攻击者猜不准 shellcode 地址,盲跳大概率崩溃。是"提高攻击成本"的缓解,非根治。
- DEP / NX(数据执行保护 / No-eXecute):利用 CPU 页表的 NX 位,把数据段(栈/堆)标记为不可执行——即便 shellcode 注入成功,CPU 执行到那里也触发异常。逼迫攻击者改用 ROP/JOP。
- Stack Canary(栈金丝雀):函数序言在返回地址旁插一个随机值(canary),返回前检查它是否被改动——若溢出覆盖了返回地址,必先踩过 canary,检测到不匹配就
__stack_chk_fail终止进程。名字源于矿工带金丝雀下井测毒气。 - CFI(控制流完整性):限制间接跳转/调用的目标只能在合法集合内——即便攻击者改了函数指针或返回地址,也只能跳到"预计算的合法目标",无法跳到任意 shellcode/ROP gadget。
- ROP / Return-Oriented Programming:DEP 让注入代码不可执行后,攻击者转而复用程序已有的代码片段(gadget),用一连串 gadget 地址串在栈上,靠
ret依次弹出执行——绕过 DEP。CFI 专门针对它。 - 最小权限:即便缓冲区溢出被利用,若服务以普通用户而非 root 运行,攻击者拿到的也只是普通权限——纵深防御的最后兜底。
- Meltdown / Spectre(2018):推测执行(speculative execution)的侧信道漏洞。CPU 为提速会推测执行未授权分支(如用户态读内核内存),虽架构上结果被丢弃,但推测时改动了缓存,攻击者用计时探测缓存即可侧信道泄露数据。Meltdown 破内核/用户隔离(缓解:KPTI),Spectre 破进程间隔离(缓解难,性能损失大)。
- 缓解叠加深意:单层皆可被绕过(ASLR 可被信息泄露破、DEP 可被 ROP 绕、Canary 可被爆破/泄露)——纵深防御叠加后才可靠,配合最小权限兜底。
- 进阶:理解攻击与防御后,看参考(防御机制对比表、易错点)。
一、缓冲区溢出:最经典的内存攻击
C/C 程序常见这样的漏洞代码:
c
void vuln(char *input) {
char buf[64];
strcpy(buf, input); // 不检查长度!input 超 64 字节就溢出
}strcpy 不检查源串长度,若 input 来自不可信输入(网络/用户),攻击者塞入 64 字节以上的数据,多出的字节就溢出 buf 覆盖相邻内存。栈上 buf 旁边恰好是保存的返回地址(旧 PC),于是攻击者可以:
栈布局(从高地址到低地址):
┌──────────────┐
│ 返回地址(旧PC) │ ← 攻击目标:改写为 shellcode 地址
├──────────────┤
│ 保存的寄存器 │
├──────────────┤
│ buf[64] │ ← 溢出从这里开始,向后(高地址)覆盖
└──────────────┘- 攻击流程:①攻击者构造超长输入,前 64 字节填垃圾,接下来的若干字节精确填入 shellcode 地址;②
strcpy把返回地址覆盖成 shellcode 地址;③函数ret时 CPU 取栈上的"返回地址"(已是 shellcode 地址)跳过去执行攻击代码。 - 后果:被攻击进程的权限决定了危害上限——若进程以 root 运行,攻击者拿到 root;以普通用户运行,只能拿到普通权限(最小权限兜底)。
- 历史:1988 年 Morris 蠕虫用
fingerd缓冲区溢出感染了当时 10% 联网机器,是溢出攻击的"成名战"。此后这类漏洞(如微软 MS03-026 RPC 漏洞催生冲击波蠕虫)反复肆虐。
二、四大缓解机制:纵深防御
现代 OS 用多层缓解对抗缓冲区溢出,单层可被绕过,叠加后才可靠:
ASLR(地址空间布局随机化)
每次程序运行,OS 随机化栈、堆、共享库、可执行文件的加载基址——攻击者无法预知 shellcode 或 gadget 的确切地址。
- 作用:让攻击者盲跳大概率失败(跳到无效地址触发段错误,进程崩溃而非被劫持)。
- 局限:①非位置无关代码(未开 PIE 的可执行文件)基址固定;②信息泄露(如格式化字符串漏洞泄露栈地址)会绕过 ASLR——一旦泄露一个地址,就能推算其他。
- 代表:Linux 默认开(
/proc/sys/kernel/randomize_va_space=2)。
DEP / NX(数据执行保护)
利用 CPU 页表的 NX(No-eXecute)位,把数据所在的页(栈/堆)标记为不可执行。
- 作用:即便 shellcode 注入到栈/堆成功,CPU 执行到那里立即触发异常(数据区不能当代码跑)。
- 局限:逼迫攻击者放弃"注入新代码",转而复用程序已有的代码片段(gadget)——即 ROP(Return-Oriented Programming),靠一连串以
ret结尾的 gadget 拼出任意逻辑,绕过 DEP。 - 代表:Windows 的 DEP、Linux 的 NX(
gcc -z noexecstack)。
Stack Canary(栈金丝雀)
编译器在函数序言把一个随机值(canary)压在返回地址旁,函数返回前检查它是否被改动——若溢出要覆盖返回地址,必先踩过 canary,检测到不匹配就调用 __stack_chk_fail 终止进程。
c
void vuln(char *input) {
char buf[64];
uintptr_t canary = __stack_chk_guard; // 序言:插入金丝雀
strcpy(buf, input);
if (canary != __stack_chk_guard) // 返回前:检查金丝雀
__stack_chk_fail(); // 被改动 → 终止
}- 作用:检测栈溢出而非阻止——溢出一旦发生就主动终止进程(崩溃好过被劫持)。
- 局限:①canary 可被信息泄露泄露出来(如格式化字符串漏洞);②针对 canary 的爆破(子进程 fork 后与父进程同 canary,可逐字节爆破);③堆溢出不踩栈 canary。
- 代表:GCC
-fstack-protector、Clang-fstack-protector-strong。
CFI(控制流完整性)
编译器为每个间接跳转/调用计算"合法目标集合",运行时校验跳转目标在集合内——即便攻击者改了返回地址或函数指针,也只能跳到预计算的合法目标,无法跳到任意 shellcode 或任意 ROP gadget。
- 作用:从根上限制控制流,让 ROP/JOP 失去"任意跳"的能力——这是最接近"根治"的缓解。
- 局限:①性能开销(每个间接跳转都要校验);②部分实现(如粗粒度 CFI)仍可被绕过;③部署不普遍。
- 代表:LLVM CFI、Microsoft CFG/XFG、Linux 的 kCFI。
四机制协同
| 缓解 | 防什么 | 被什么绕过 |
|---|---|---|
| ASLR | 猜不准地址 | 信息泄露地址 |
| DEP/NX | 注入代码不可执行 | ROP 复用已有代码 |
| Stack Canary | 检测栈返回地址被覆盖 | 泄露/爆破 canary、堆溢出 |
| CFI | 限制间接跳转目标 | 粗粒度实现的盲区 |
纵深防御的意义正在于此:四层相互补位,攻击者需同时绕过全部才能成功,难度指数级上升。
三、最小权限:最后的兜底
即便上述缓解全部被攻破,最小权限原则仍能限制危害范围:
- 一个 Web 服务器若以 root 运行,缓冲区溢出被利用 → 攻击者拿到整个系统最高权限。
- 同一服务器若降权以专用普通用户(如
www-data)运行 + 用 chroot/容器/沙箱隔离,溢出被利用 → 攻击者只拿到www-data那点受限权限,不能读 /etc/shadow、不能装后门、不能动其他服务。 - 这就是为什么生产环境绝不以 root 跑服务——它是纵深防御里"权限控制"那一层的兜底。
四、Meltdown 与 Spectre:推测执行侧信道
2018 年爆出的两个硬件级安全漏洞,重新定义了"隔离"的边界——它们攻击的不是软件 bug,而是 CPU 为性能做的推测执行:
- 推测执行(Speculative Execution):CPU 为填补流水线空泡,会提前猜测分支走向并执行那条路径。若猜错,架构上结果被丢弃——但缓存已被推测执行改动了。
- Meltdown(破内核/用户隔离):用户态进程通过精心构造的推测执行路径,推测读取内核内存的数据,再用缓存侧信道(用计时探测哪个缓存行被加载)把数据"侧漏"出来——直接打破"用户态不能读内核内存"的铁律。缓解:KPTI(内核页表隔离)——内核态与用户态用不同页表,用户态页表里干脆不映射内核地址(代价:syscall 多切一次页表,开销涨 5-30%)。
- Spectre(破进程间隔离):诱骗别的进程/虚拟机的 CPU 推测执行越界读取,再用缓存侧信道泄露数据——比 Meltdown 更通用更难修,无法纯靠软件根治,需微码更新 + 重新编译(retpoline 等缓解),性能损失大。
- 教训:安全机制(页表隔离)可能被底层硬件的优化(推测执行 + 缓存)侧信道绕过——性能优化的副作用有时会侵蚀安全边界。这催生了"安全不仅是软件问题,更是硬件问题"的认知。
下一步
掌握了攻击与防御机制后,看参考——汇总 ACL vs capability、四大防御机制、Unix 权限速查与易错点,作为复习与速查手册。