对于堆上off-by-one的个人见解
字数 1744 2025-08-07 08:22:23

堆上Off-by-One漏洞深入分析与利用技术

0x00 前言

Off-by-one是一种特殊的堆溢出漏洞,其特点是只能溢出一个字节。尽管看似微小,但通过精心构造,这种漏洞可以导致严重的后果,如内存破坏、任意代码执行等。本文将全面剖析Off-by-one漏洞的原理、利用思路及具体利用技术。

0x01 Off-by-One漏洞原理

基本概念

Off-by-one漏洞是指在向缓冲区写入数据时,由于边界条件验证不严格,导致多写入一个字节,造成缓冲区溢出。这种溢出虽然只多出一个字节,但在堆管理结构中可能产生关键影响。

常见触发场景

  1. strcpy函数使用不当

    • 当使用strcpy复制长度等于缓冲区最大容量的字符串时
    • 复制结束时会额外写入一个'\x00'终止符
    • 导致溢出一个NULL字节
  2. 循环写入控制不当

    • 循环次数计算错误导致多写入一字节
  3. 故意构造的边界条件

    • size+1 <= max_content_size这类条件判断

0x02 Off-by-One漏洞利用思路

主要利用方式

  1. Chunk Overlapping

    • 通过修改堆块大小字段实现堆块重叠
    • 可用于实现Use-After-Free等攻击
  2. Unlink攻击

    • 利用glibc的unlink机制
    • 通过伪造堆块实现任意地址写

特殊变种:Off-by-Null

只能溢出一个NULL字节('\x00')的情况,利用方式相对受限,但仍可通过精心构造实现攻击。

0x03 Off-by-One漏洞利用技术详解

0x031 利用Off-by-One进行Chunk Overlapping

基本原理

通过修改相邻堆块的size字段,改变堆管理器对内存布局的认知,从而将多个堆块合并或分割,实现堆块重叠。

利用场景分类

  1. 对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可以同时控制两个堆块。

  2. 对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。

  3. 后向Overlapping

    add(0x18) #0
    add(0x10) #1
    add(0x10) #2
    add(0x10) #3
    add(0x10) #4
    

    修改chunk 1的size为0x61,使其包含chunk 2和3,free后重新分配可控制多个堆块。

  4. 前向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在合并空闲堆块时的操作,主要流程:

  1. 向后合并:检查前一个chunk是否空闲
  2. 向前合并:检查后一个chunk是否空闲
  3. 执行unlink_chunk:将空闲块从双向链表中移除

关键安全检查

  1. size与prev_size一致性检查

    if (chunksize(p) != prev_size(next_chunk(p)))
        malloc_printerr("corrupted size vs. prev_size");
    
  2. 双向链表完整性检查

    if (__builtin_expect(fd->bk != p || bk->fd != p, 0))
        malloc_printerr("corrupted double-linked list");
    

利用步骤

  1. 构造一个伪造的堆块(fake chunk)
  2. 通过off-by-one修改相邻堆块的size和prev_size
  3. 设置fake chunk的fd和bk绕过检查:
    • fd = target_addr - 0x18
    • bk = target_addr - 0x10
  4. 触发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 防御与检测

  1. 编译时防护

    • 使用FORTIFY_SOURCE
    • 开启堆保护机制(如glibc的完整性检查)
  2. 运行时检测

    • 增加堆操作的安全检查
    • 使用AddressSanitizer等工具
  3. 代码审计

    • 严格检查边界条件
    • 避免使用不安全的字符串操作函数

0x05 总结

Off-by-one漏洞虽然只能溢出一个字节,但通过精心构造可以:

  1. 修改堆块元数据实现堆块重叠
  2. 利用unlink机制实现任意地址写
  3. 结合其他漏洞实现完整攻击链

理解这些技术需要深入掌握glibc堆管理机制,建议通过实际调试加深理解。

堆上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 堆布局: 利用off-by-one修改chunk 1的size: 此时chunk 1包含了chunk 2的空间,free后再malloc可以同时控制两个堆块。 对inuse的smallbin进行extend 修改chunk 1的size为0xb1,使其包含chunk 2,free后进入unsorted bin。 后向Overlapping 修改chunk 1的size为0x61,使其包含chunk 2和3,free后重新分配可控制多个堆块。 前向Overlapping 先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一致性检查 双向链表完整性检查 利用步骤 构造一个伪造的堆块(fake chunk) 通过off-by-one修改相邻堆块的size和prev_ size 设置fake chunk的fd和bk绕过检查: fd = target_ addr - 0x18 bk = target_ addr - 0x10 触发unlink操作实现任意地址写 示例 0x04 防御与检测 编译时防护 使用FORTIFY_ SOURCE 开启堆保护机制(如glibc的完整性检查) 运行时检测 增加堆操作的安全检查 使用AddressSanitizer等工具 代码审计 严格检查边界条件 避免使用不安全的字符串操作函数 0x05 总结 Off-by-one漏洞虽然只能溢出一个字节,但通过精心构造可以: 修改堆块元数据实现堆块重叠 利用unlink机制实现任意地址写 结合其他漏洞实现完整攻击链 理解这些技术需要深入掌握glibc堆管理机制,建议通过实际调试加深理解。