堆上Off-by-One漏洞深入分析与利用技术
0x00 前言
Off-by-one是一种特殊的堆溢出漏洞,其特点是只能溢出一个字节。尽管看似微小,但通过精心构造,这种漏洞可以导致严重的后果,如内存破坏、任意代码执行等。本文将全面剖析Off-by-one漏洞的原理、利用思路及具体利用技术。
0x01 Off-by-One漏洞原理
基本概念
Off-by-one漏洞是指在向缓冲区写入数据时,由于边界条件验证不严格,导致多写入一个字节,造成缓冲区溢出。这种溢出虽然只多出一个字节,但在堆管理结构中可能产生关键影响。
常见触发场景
-
strcpy函数使用不当
- 当使用strcpy复制长度等于缓冲区最大容量的字符串时
- 复制结束时会额外写入一个'\x00'终止符
- 导致溢出一个NULL字节
-
循环写入控制不当
- 循环次数计算错误导致多写入一字节
-
故意构造的边界条件
- 如
size+1 <= max_content_size这类条件判断
- 如
0x02 Off-by-One漏洞利用思路
主要利用方式
-
Chunk Overlapping
- 通过修改堆块大小字段实现堆块重叠
- 可用于实现Use-After-Free等攻击
-
Unlink攻击
- 利用glibc的unlink机制
- 通过伪造堆块实现任意地址写
特殊变种:Off-by-Null
只能溢出一个NULL字节('\x00')的情况,利用方式相对受限,但仍可通过精心构造实现攻击。
0x03 Off-by-One漏洞利用技术详解
0x031 利用Off-by-One进行Chunk Overlapping
基本原理
通过修改相邻堆块的size字段,改变堆管理器对内存布局的认知,从而将多个堆块合并或分割,实现堆块重叠。
利用场景分类
-
对inuse的fastbin进行extend
add(0x18) #0 add(0x10) #1 add(0x10) #2堆布局:
chunk 0: size 0x21 chunk 1: size 0x21 chunk 2: size 0x21 top chunk利用off-by-one修改chunk 1的size:
edit(0, p64(0)*3 + '\x41') # 将chunk 1的size改为0x41此时chunk 1包含了chunk 2的空间,free后再malloc可以同时控制两个堆块。
-
对inuse的smallbin进行extend
add(0x18) #0 add(0x80) #1 add(0x10) #2 add(0x10) #3 # 防止与top合并修改chunk 1的size为0xb1,使其包含chunk 2,free后进入unsorted bin。
-
后向Overlapping
add(0x18) #0 add(0x10) #1 add(0x10) #2 add(0x10) #3 add(0x10) #4修改chunk 1的size为0x61,使其包含chunk 2和3,free后重新分配可控制多个堆块。
-
前向Overlapping
add(0x80) #0 add(0x10) #1 add(0x18) #2 add(0x80) #3 add(0x10) #4先free chunk 0,然后:
- 修改chunk 3的prev_size为0xb0
- 修改chunk 3的size为0x70(清除PREV_INUSE位)
- free chunk 3时会触发向前合并
Off-by-Null的特殊利用
主要应用于将0x100整数倍大小的堆块的PREV_INUSE位清零,触发unlink操作。
0x032 利用Off-by-One进行Unlink攻击
Unlink机制解析
Unlink是glibc在合并空闲堆块时的操作,主要流程:
- 向后合并:检查前一个chunk是否空闲
- 向前合并:检查后一个chunk是否空闲
- 执行unlink_chunk:将空闲块从双向链表中移除
关键安全检查
-
size与prev_size一致性检查
if (chunksize(p) != prev_size(next_chunk(p))) malloc_printerr("corrupted size vs. prev_size"); -
双向链表完整性检查
if (__builtin_expect(fd->bk != p || bk->fd != p, 0)) malloc_printerr("corrupted double-linked list");
利用步骤
- 构造一个伪造的堆块(fake chunk)
- 通过off-by-one修改相邻堆块的size和prev_size
- 设置fake chunk的fd和bk绕过检查:
- fd = target_addr - 0x18
- bk = target_addr - 0x10
- 触发unlink操作实现任意地址写
示例
# 假设已分配3个chunk: chunk0, chunk1, chunk2
# 在chunk1中伪造一个chunk
fake_chunk = p64(0) + p64(0x20) # prev_size和size
fake_chunk += p64(target-0x18) + p64(target-0x10) # fd和bk
edit(1, fake_chunk)
# 通过chunk0的off-by-one修改chunk1的size
edit(0, p64(0)*3 + '\x00') # 清除PREV_INUSE位
# 设置chunk2的prev_size为fake chunk的大小
edit(2, p64(0x20))
# free chunk2触发unlink
free(2)
0x04 防御与检测
-
编译时防护
- 使用FORTIFY_SOURCE
- 开启堆保护机制(如glibc的完整性检查)
-
运行时检测
- 增加堆操作的安全检查
- 使用AddressSanitizer等工具
-
代码审计
- 严格检查边界条件
- 避免使用不安全的字符串操作函数
0x05 总结
Off-by-one漏洞虽然只能溢出一个字节,但通过精心构造可以:
- 修改堆块元数据实现堆块重叠
- 利用unlink机制实现任意地址写
- 结合其他漏洞实现完整攻击链
理解这些技术需要深入掌握glibc堆管理机制,建议通过实际调试加深理解。