Memory Safety 小结

本文回顾笔者在UCB学习的CS161 的 Memory Safety(内存安全),笔者觉得这一章很有必要单独拉出来整理一下,在一些设计API流程中都有启发意义。
文中的配图均来自课程官方 textbook: https://textbook.cs161.org/

Concepts

Memory Safety(内存安全)关心的是:程序是否会读/写本不该访问的内存。一旦出现违规访问,就可能导致 information disclosure(信息泄露)logic bypass(逻辑绕过)、甚至 control-flow hijacking(控制流劫持)

CS161 通常用两类性质来组织这部分内容:

  • spatial safety(空间安全):不发生 out-of-bounds(越界)访问,例如数组越界读写。
  • temporal safety(时间安全):不发生 use-after-lifetime(生命周期之后仍访问),例如 use-after-free(释放后使用)double free(重复释放)

Address Space

一个进程的虚拟地址空间可粗略分成四段:

  • code:指令
  • static:全局/静态变量
  • heap(堆):动态分配(malloc/free
  • stack(栈):函数调用栈帧(locals、saved registers、return address)

常用的结构是:

  • heap 向高地址增长
  • stack 向低地址增长

Memory sections

这张图的重要性在于:很多漏洞本质是“写穿边界”,覆盖到相邻对象。相邻对象如果是 flag、function pointer、返回地址等关键数据,后果就会变得非常严重。

Endianness

x86 采用 little-endian(小端序):多字节整数的 least significant byte(最低有效字节) 放在最低地址。 下面的图是表示0x44332211这个数字的存储。

Little endian

这在构造 payload(攻击载荷)时很关键:比如要把某个 32-bit 地址写进 return address,输入字节顺序需要按小端序排列。

Call Stack

理解 stack-based 漏洞,首先要建立 stack frame(栈帧) 的结构直觉。以 32-bit x86 为例,通常关注三个寄存器:

  • EIP:instruction pointer(控制流位置)
  • EBP:frame pointer(栈帧基址)
  • ESP:stack pointer(栈顶)

一个典型栈帧(概念上)从高到低大致是:

  • arguments(参数)
  • return address(返回地址),常见位置 EBP + 4
  • saved frame pointer(保存的帧指针 / SFP),常见位置 EBP
  • locals(局部变量),常见位置 EBP - ...

此外常用偏移记忆是:第一个参数通常在 EBP + 8

Stack after pushing args

这类布局直接解释了:如果 local buffer(局部缓冲区)发生 overflow 并向高地址写穿,通常会先碰到 SFP,再碰到 return address。

Overflows

buffer overflow(缓冲区溢出) 指程序向 buffer 写入超过容量的数据,而 C/C++ 默认不做 bounds check,就会导致越界写。

最直接的危害是覆盖相邻数据,例如把 is_admin 之类的 flag 覆盖成非零,从而实现 logic bypass(逻辑绕过)

Overflow layout

Overflow overwriting data

Stack Smashing

stack smashing(栈溢出控制流劫持) 的目标是控制数据而不是普通变量。

当 overflow 足够长时,它可以覆盖到:

  1. saved frame pointer(SFP)
  2. return address(RIP/EIP)

一旦 return address 被改写,函数执行 ret 时就会跳到攻击者指定的位置,实现 control-flow hijacking(控制流劫持)

Smash to control RIP

经典攻击是把 shellcode(注入代码) 放进 buffer,并把 return address 改成指向 buffer 的地址。现代系统通常有 NX/DEP 等防护来阻断“写入即执行”,但“覆盖 return address”仍然是理解后续 ROP 的基础。

Format Strings

format string vulnerability(格式化字符串漏洞) 通常来自把用户输入当作 printf 的 format string,例如:

printf(user_input);        // vulnerable
printf("%s", user_input);  // safe pattern

由于 printf 是 variadic function(可变参数函数),format string 决定了它会从栈上“取多少参数、按什么类型解释”。当 format string 与真实参数不匹配时,printf 会把栈上其它内容当作参数读取,从而产生:

  • %x/%p:stack scanning(扫栈)→ information disclosure(信息泄露)
  • %s:把栈值当指针解引用 → arbitrary read(任意读)
  • %n:把“已输出字符数”写回某地址 → arbitrary write(任意写)

printf stack diagram

Integer Conversions

integer conversion vulnerability(整数转换漏洞) 常见于 signed/unsigned 混用、隐式类型转换、整数溢出(wraparound)。

高频模式包括:

  • signed-to-unsigned cast bypass:负数 int 传给 size_t 参数后变成巨大正数,导致复制长度爆炸(例如 memcpy)。
  • wraparound allocation bug:长度计算(如 len + k)在 32-bit 下溢出为很小的数,分配很小的 buffer,却执行很大的 copy/read。

核心原则是:检查要在与最终操作一致的类型域里完成,并对可能的 overflow 做显式处理。

Off-by-One

off-by-one(差一错误) 指只越界写 1 byte,但依然可能可利用。
在栈上,如果这个 1 byte 刚好覆盖到 saved frame pointer 的最低有效字节,可能触发 stack pivot(栈迁移)

  1. 利用 1-byte overwrite(单字节覆盖)改变 SFP,使其指向攻击者布置的“假栈帧”
  2. 函数 epilogue(收尾)时从错误位置恢复栈帧
  3. ret 从假栈帧取到伪造 return address,转移控制流

Off-by-one setup

Off-by-one pivot

Mitigations

Mitigations(缓解措施)的目标不是“消灭 bug”,而是降低 exploitability(可利用性),提高攻击成本,形成 defense in depth(纵深防御)

Safe APIs

优先使用带长度的 API,减少“无边界写”的机会:

  • fgets 替代 gets
  • snprintf 替代 sprintf
  • 使用 memcpy/memmove 时严格验证长度

Non-executable Pages

NX / DEP(不可执行页) 把 writable 与 executable 分离,阻断“把 shellcode 写进栈/堆然后跳过去执行”的经典路径。

对应的常见攻击演化是 code reuse(代码复用)

  • return-to-libc:跳转到现成 libc 函数
  • ROP(Return-Oriented Programming):用 gadgets(以 ret 结尾的短指令序列)拼链执行

Stack Canaries

stack canary(栈金丝雀) 在 locals 与控制数据之间插入随机值,返回前检查是否被改写;若被改写则终止程序。它对“连续写穿到 return address”的攻击非常有效。

Canary

ASLR

ASLR(地址空间布局随机化) 随机化 stack/heap/libs 等基址,阻止写死地址的 ret2libc/ROP。很多情况下,攻击者需要先获得 address leak(地址泄露)才能稳定利用。

Pointer Authentication

pointer authentication(指针认证)(如 PAC 思路)为指针/返回地址附加认证信息,阻止攻击者伪造指针值。即便内存可被改写,未通过认证的指针也难以被 CPU 接受。

Defense in Depth

典型组合是:

  • NX 阻止注入代码直接执行
  • canary 阻止大量 stack-return overwrite
  • ASLR 提高定位 gadget/libc 的难度
  • pointer authentication 进一步保护控制数据完整性

在现实攻击中,往往需要“漏洞 + 信息泄露 + 代码复用 + 绕过多层防护”才能成功。

Engineering

Memory-safe languages

采用 memory-safe language(内存安全语言)(如 Java、Go、Python,或 Rust 的 safe subset)可以从语言层面避免大类 memory safety bug(bounds check、lifetime rules 等)。

Analysis and testing

在必须使用 C/C++ 的系统里,工程上常见的降低风险方式包括:

  • static analysis(静态分析)
  • dynamic analysis(动态分析,如 sanitizers)
  • fuzzing(模糊测试)
  • 依赖更新与补丁管理

Checklist

  • Bounds:所有 copy/read/write 都有明确的上限与验证。
  • Types:避免 silent signed/unsigned conversion;对 size_t 与 wraparound 做显式保护。
  • Formats:不允许 printf(user_input);format string 必须受控;避免 %n 暴露。
  • Build:开启 NX、stack canary、ASLR;平台支持时启用更强的硬件/ABI 防护。
  • Process:sanitizers + fuzzing 纳入 CI;依赖保持更新。


📢 转载须知



本文作者:Tianci Hou


本文标题:《Memory Safety 小结》


本文链接:https://blog.tiancihou.me/memory-safety-%e5%b0%8f%e7%bb%93/




CC BY-NC-SA


版权声明:本文采用 CC BY-NC-SA 4.0 许可协议。转载请务必保留以上署名及原始链接。



暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇