PWN系列堆利用之off-by-one
字数 1465 2025-08-15 21:33:50
Off-by-One漏洞利用技术详解
1. Off-by-One漏洞原理
Off-by-one(单字节溢出)是指程序向缓冲区中写入时,写入的字节数超过了缓冲区本身所申请的字节数并且只越界了一个字节。这种漏洞看似微小,但在堆利用中可能造成严重后果。
关键特征:
- 只溢出1个字节
- 通常是null字节溢出('\x00')
- 可以破坏堆块元数据或改变关键指针
2. 示例题目分析:Asis CTF 2016 b00ks
2.1 环境配置
# 配置libc的版本
cp /glibc/2.23/64/lib/ld-2.23.so /tmp/ld-2.23.so
patchelf --set-interpreter /tmp/ld-2.23.so ./b00ks
LD_PRELOAD=/glibc/2.23/64/lib/libc.so.6
# 转发输入输出流到10001端口
socat tcp-listen:10001,reuseaddr,fork EXEC:./b00ks,pty,raw,echo=0
2.2 程序功能分析
这是一个图书管理系统,提供以下功能:
- 创建图书
- 删除图书
- 编辑图书
- 打印图书详情
- 更改当前作者名
- 退出
2.3 关键数据结构
图书结构体:
struct book {
int id;
char *name; // 指向名称缓冲区的指针
char *description; // 指向描述缓冲区的指针
int size; // 描述的大小
};
2.4 漏洞点
MyRead函数存在null byte off-by-one漏洞:
signed __int64 __fastcall MyRead(char *a_string, int str_size) {
int i;
char *buf;
if ( str_size <= 0 )
return 0LL;
buf = a_string;
for ( i = 0; ; ++i ) {
if ( (unsigned int)read(0, buf, 1uLL) != 1 )
return 1LL;
if ( *buf == 10 )
break;
++buf;
if ( i == str_size )
break;
}
*buf = 0; // 这里可能写入超出边界1字节的null
return 0LL;
}
3. 利用思路详解
3.1 利用步骤概述
- 填充满author缓冲区
- 创建堆块1,覆盖author结尾的\x00
- 创建堆块2(较大尺寸,为后续泄露libc做准备)
- 泄露堆块1地址
- 利用off-by-one修改book结构体指针
- 构造伪造的book结构体实现任意地址读写
- 泄露libc地址
- 覆盖__free_hook为one_gadget
- 触发free获取shell
3.2 详细利用过程
步骤1:填充author缓冲区
createname(b"A"*32) # 填充32字节的author名
这会覆盖author缓冲区并在末尾添加一个\x00,共33字节写入32字节空间。
步骤2:创建堆块1
创建一个小型图书,其name缓冲区将紧邻author缓冲区,覆盖author末尾的\x00。
步骤3:创建堆块2
创建一个较大的图书(如0x21000大小),这样它的描述缓冲区将通过mmap分配,与libc有固定偏移。
步骤4:泄露堆块1地址
由于堆块1覆盖了author的null终止符,打印author名时会连带打印出堆块1的地址。
步骤5:利用off-by-one修改指针
通过修改author名,利用off-by-one覆盖book1指针的最低字节:
# 将book1指针从0x55...30修改为0x55...00
edit_author(b"A"*32) # 再次触发off-by-one
步骤6:构造伪造的book结构体
在堆块1的描述中构造一个伪造的book结构体:
payload = b'A'*0x50 + p64(1) + p64(book1_addr+0x38) + p64(book1_addr+0x40) + p64(0xffff)
editbook(book_id_1, payload)
这个伪造的结构体:
- id = 1
- name指针指向堆块2的name
- description指针指向堆块2的description
- size = 0xffff
步骤7:实现任意地址读写
现在通过编辑book1实际上是编辑伪造的结构体,可以:
- 修改book2的name/description指针指向任意地址
- 通过编辑book2实现任意地址写
步骤8:泄露libc地址
通过mmap分配的堆块与libc的固定偏移,计算出libc基址。
步骤9:覆盖__free_hook
# 将free_hook地址写入book2的description指针
editbook(1, p64(free_hook))
# 将one_gadget写入free_hook
editbook(2, p64(one_gadget))
步骤10:触发free
删除任意图书触发free,从而调用one_gadget获取shell。
4. 调试技巧
- 使用IDA远程调试比pwntools+GDB更高效
- 重点关注:
- 堆布局变化
- 关键指针的修改
- 内存中的伪造结构体
- 多次尝试确定正确的偏移量
5. 防御措施
- 严格检查输入长度
- 使用安全的字符串处理函数
- 启用堆保护机制(如GNU的FORTIFY_SOURCE)
- 使用现代堆分配器(如glibc>=2.32的新保护机制)
6. 总结
Off-by-one漏洞虽然只溢出1字节,但结合堆利用技巧可以实现强大的攻击效果。关键点在于:
- 利用null字节溢出破坏堆结构
- 构造伪造的数据结构
- 实现任意地址读写
- 结合其他利用技术(如hook覆盖)
理解这类漏洞需要深入掌握堆分配机制和内存布局,通过实践调试加深理解。