A05 —— use-after-free:释放后再访问的问题

金句:释放一块内存后继续使用它,就像在一块已经卖掉的地皮上继续建房子——迟早会有人来拆。


什么是 use-after-free

use-after-free(UAF)是指访问已经 free 掉的内存——指针还在,但指向的内容已经被回收并可能被重新分配。

Bash
UAF 的典型例子:

char *buf = malloc(100);
strcpy(buf, "hello");
free(buf);          // 释放内存

// buf 指针仍然有效(指向原来的地址)
// 但这块内存已经归还给系统

printf("%sn", buf);  // 危险!可能读到垃圾数据
strcpy(buf, "world"); // 更危险!可能覆盖其他程序的数据

现代 malloc 的行为:
  - free 后,指针不变
  - 内存归还 bin,可能被后续 malloc 分配给其他代码
  - UAF 会导致数据泄漏或覆写

UAF 的危害

Bash
UAF 的严重后果:

1. 数据泄漏:
   buf = malloc(100);
   strcpy(buf, "secret password");
   free(buf);

   // 另一个程序 malloc,可能分配到同一块内存
   other_buf = malloc(100);
   // 如果旧的 "secret" 还没被清零,可能被读出

2. 任意代码执行(最严重):
   class A { virtual void foo() {} };
   A *obj = new A();
   delete obj;  // 释放对象

   // 指针还在,指向已释放的内存
   obj->foo();  // 调用虚函数表

   // 如果攻击者在释放的内存上布置假的虚函数表
   // 可以控制程序执行流程

3. 崩溃:
   - 内存可能被其他代码覆写
   - 访问时可能触发 crash

UAF 的利用(Exploitation)

Bash
UAF 的利用步骤(简化):

1. 分配对象 A(包含函数指针/虚表)
   obj = malloc(sizeof(A));  // 假设 64 bytes

2. 释放对象 A
   free(obj);
   // obj 指针仍在,指向已释放的内存

3. 攻击者分配同等大小的块,写入恶意数据
   fake_obj = malloc(64);
   // 写入假的虚函数表地址

4. 程序使用 obj(不知道已经释放)
   obj->virtual_method();
   // → 调用 fake_obj 中的假函数表
   // → 执行任意代码

现代缓解措施:
  - Heap hardening:free 后写入保护字节(canary)
  - 安全删除:释放后清零指针
  - Heap integrity check:tcache/fastbin 检查 double-free

检测 UAF 的工具

Bash
1. valgrind --track-origins=yes:

valgrind --track-origins=yes ./program

输出:
  ==12345== Invalid read of size 8
  ==12345==    at 0x...: use_after_free (prog.c:20)
  ==12345==    by 0x...: main (prog.c:30)

2. AddressSanitizer(ASan):

gcc -fsanitize=address -g program.c
./program

ASan 在每次 access 时检查:
  - 内存是否在 quarantine 中(已释放但未重用)
  - 是否越界

ASan 的 quarantine:
  - free 后,内存不会立即归还 buddy
  - 延迟一段时间(re分配更困难)
  - 检测 UAF 更容易

3. GDB 的 heap analysis:

GDB 可以设置 watchpoint:
  (gdb) watch *0x12345678
  (gdb) run

  当地址被写入时,触发 watchpoint

防止 UAF 的方法

Bash
1. 释放后置 NULL:

char *buf = malloc(100);
free(buf);
buf = NULL;  // 释放后立即置 NULL

// 使用前检查
if (buf != NULL) {
    // 使用 buf
}

问题:代码中所有使用 buf 的地方都要检查

2. 作用域管理(RAII):

std::vector<char> buf(100);
// 作用域结束,自动释放
// 不存在 UAF 问题

3. 智能指针:

auto buf = std::make_unique<char[]>(100);
// 离开作用域,自动 free
// 不存在 UAF 问题

4. 工具:clang 的 -fuse-ld=gold + sanitize:

$ clang -fsanitize=address -g program.c
$ ./program
  → ASan 会在运行时检测所有 UAF

5. 安全的删除接口(自定义):

template<typename T>
void safe_delete(T*& p) {
    delete p;
    p = nullptr;
}

总结

  • UAF:use-after-free,访问已 free 的内存
  • 危害:数据泄漏(读到别人的数据)/ 任意代码执行(覆写虚函数表)
  • 利用:释放对象 → 攻击者分配同大小块写入假数据 → 程序使用原指针 → 执行攻击者代码
  • 检测:valgrind / ASan(ASan 有 quarantine 延迟重用,更容易检测)
  • 防止:释放后置 NULL / RAII / 智能指针 / safe_delete

下篇预告(A06):C++ 的 new/delete 和 malloc/free 有什么区别?new 的构造和 delete 的析构,以及 operator new 的底层实现。


关注公众号「AI不着急」,回复”资料”获取内存学习路线图。

最后修改: 2024年10月26日

作者

评论

发表评论

您的邮箱地址不会被公开。