在pwn中malloc扮演的角色(1)
字数 1477 2025-08-29 08:30:05

malloc在Pwn中的角色与内部机制分析

1. chunk结构分析

glibc中的内存块(chunk)是malloc管理内存的基本单位,其结构如下:

struct malloc_chunk {
  INTERNAL_SIZE_T      prev_size;  /* 前一个chunk的大小(如果前一个chunk是空闲的) */
  INTERNAL_SIZE_T      size;       /* 当前chunk的大小,包括头部开销 */
  struct malloc_chunk* fd;         /* 仅空闲时使用 - 前向指针 */
  struct malloc_chunk* bk;         /* 仅空闲时使用 - 后向指针 */
  /* 仅大块使用: 指向下一个更大尺寸块的指针 */
  struct malloc_chunk* fd_nextsize; 
  struct malloc_chunk* bk_nextsize;
};

关键点:

  • prev_size:前一个chunk空闲时存储其大小,否则可能被前一个chunk使用
  • size:当前chunk大小,包含头部开销,最低3位用作标志位
  • fdbk:空闲chunk的双向链表指针
  • fd_nextsizebk_nextsize:仅用于large bins的特殊指针

2. 基础概念和宏定义

2.1 关键宏定义

#define chunk2mem(p)   ((void*)((char*)(p) + 2*SIZE_SZ))     // 从chunk指针转为用户内存指针
#define mem2chunk(mem) ((mchunkptr)((char*)(mem) - 2*SIZE_SZ)) // 从用户内存指针转为chunk指针

#define MIN_CHUNK_SIZE (offsetof(struct malloc_chunk, fd_nextsize)) // 最小chunk大小(64位0x20,32位0x10)

#define PREV_INUSE 0x1        // size字段的最低位,表示前一个chunk是否在使用中
#define IS_MMAPPED 0x2        // size字段的倒数第二位,表示chunk是否由mmap分配
#define NON_MAIN_ARENA 0x4    // size字段的倒数第三位,表示chunk是否不属于主arena

#define SIZE_BITS (PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) // 0x7 (0111)

2.2 重要操作宏

// 获取chunk的实际大小(清除标志位)
#define chunksize(p) ((p)->size & ~(SIZE_BITS))

// 获取物理相邻的下一个chunk
#define next_chunk(p) ((mchunkptr)(((char*)(p)) + chunksize(p)))

// 获取物理相邻的前一个chunk
#define prev_chunk(p) ((mchunkptr)(((char*)(p)) - ((p)->prev_size)))

// 将请求大小转换为实际分配大小
#define request2size(req) \
  (((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE) ? MINSIZE : \
   ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)

3. unlink操作分析

unlink是malloc中最重要的操作之一,用于从双向链表中移除一个chunk:

#define unlink(AV, P, BK, FD) {                                 \
    FD = P->fd;                                                 \
    BK = P->bk;                                                 \
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))       \
      malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
    else {                                                      \
        FD->bk = BK;                                            \
        BK->fd = FD;                                            \
        if (!in_smallbin_range (P->size)                        \
            && __builtin_expect (P->fd_nextsize != NULL, 0)) {  \
            if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) \
            || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \
              malloc_printerr (check_action,                     \
                   "corrupted double-linked list (not small)", P, AV); \
            if (FD->fd_nextsize == NULL) {                      \
                if (P->fd_nextsize == P)                        \
                  FD->fd_nextsize = FD->bk_nextsize = FD;       \
                else {                                          \
                    FD->fd_nextsize = P->fd_nextsize;           \
                    FD->bk_nextsize = P->bk_nextsize;           \
                    P->fd_nextsize->bk_nextsize = FD;           \
                    P->bk_nextsize->fd_nextsize = FD;           \
                }                                               \
            } else {                                            \
                P->fd_nextsize->bk_nextsize = P->bk_nextsize;   \
                P->bk_nextsize->fd_nextsize = P->fd_nextsize;   \
            }                                                   \
        }                                                       \
    }                                                           \
}

关键点:

  1. 安全检查:FD->bk != P || BK->fd != P,这是unlink攻击需要绕过的关键检查
  2. 普通链表操作:FD->bk = BK; BK->fd = FD;
  3. 对于large bins的额外处理:涉及fd_nextsizebk_nextsize指针

4. bins的分类与管理

glibc使用多种bins来管理不同大小的空闲chunk:

#define NBINS             128     // 总bin数量
#define NSMALLBINS         64     // small bins数量
#define SMALLBIN_WIDTH    MALLOC_ALIGNMENT  // small bin宽度(通常8字节)
#define MIN_LARGE_SIZE    ((NSMALLBINS - SMALLBIN_CORRECTION) * SMALLBIN_WIDTH)

bin分类:

  1. Fast bins:管理小尺寸chunk(默认<64字节),单链表,LIFO顺序
  2. Small bins:管理<512字节的chunk,双向链表,FIFO顺序
  3. Large bins:管理≥512字节的chunk,双向链表,按大小排序
  4. Unsorted bin:临时存放刚释放的chunk,双向链表

bin大小分布:

  • 64个8字节间隔的small bins
  • 32个64字节间隔的bins
  • 16个512字节间隔的bins
  • 8个4096字节间隔的bins
  • 4个32768字节间隔的bins
  • 2个262144字节间隔的bins
  • 1个剩余大小的bin

5. __libc_malloc函数分析

void *__libc_malloc (size_t bytes) {
  mstate ar_ptr;
  void *victim;
  void *(*hook) (size_t, const void *) = atomic_forced_read (__malloc_hook);
  
  // 检查malloc_hook,这是malloc hook攻击的关键点
  if (__builtin_expect (hook != NULL, 0))
    return (*hook)(bytes, RETURN_ADDRESS (0));
  
  arena_get (ar_ptr, bytes);
  victim = _int_malloc (ar_ptr, bytes);
  
  // 如果第一次分配失败,尝试其他arena
  if (!victim && ar_ptr != NULL) {
    LIBC_PROBE (memory_malloc_retry, 1, bytes);
    ar_ptr = arena_get_retry (ar_ptr, bytes);
    victim = _int_malloc (ar_ptr, bytes);
  }
  
  if (ar_ptr != NULL)
    (void) mutex_unlock (&ar_ptr->mutex);
  
  assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
          ar_ptr == arena_for_chunk (mem2chunk (victim)));
  return victim;
}

关键点:

  1. 首先检查__malloc_hook,若不为空则调用hook函数
  2. 获取arena并调用_int_malloc进行实际分配
  3. 第一次分配失败会尝试其他arena

6. _int_malloc函数分析

_int_malloc是malloc的核心实现,主要流程:

6.1 Fast bins处理

if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ())) {
    idx = fastbin_index (nb);
    mfastbinptr *fb = &fastbin (av, idx);
    mchunkptr pp = *fb;
    
    // 从fast bin中取出chunk
    do {
      victim = pp;
      if (victim == NULL)
        break;
    } while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim))
             != victim);
    
    if (victim != 0) {
      // 安全检查:确保取出的chunk大小与bin匹配
      if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0)) {
        errstr = "malloc(): memory corruption (fast)";
        goto errout;
      }
      check_remalloced_chunk (av, victim, nb);
      void *p = chunk2mem (victim);
      alloc_perturb (p, bytes);
      return p;
    }
}

6.2 Small bins处理

if (in_smallbin_range (nb)) {
    idx = smallbin_index (nb);
    bin = bin_at (av, idx);
    
    if ((victim = last (bin)) != bin) {
      if (victim == 0) /* initialization check */
        malloc_consolidate (av);
      else {
        bck = victim->bk;
        // 安全检查:检查small bin链表完整性
        if (__glibc_unlikely (bck->fd != victim)) {
          errstr = "malloc(): smallbin double linked list corrupted";
          goto errout;
        }
        set_inuse_bit_at_offset (victim, nb);
        bin->bk = bck;
        bck->fd = bin;
        
        if (av != &main_arena)
          victim->size |= NON_MAIN_ARENA;
        check_malloced_chunk (av, victim, nb);
        void *p = chunk2mem (victim);
        alloc_perturb (p, bytes);
        return p;
      }
    }
}

6.3 Large bins处理

else {
    idx = largebin_index (nb);
    if (have_fastchunks (av))
      malloc_consolidate (av);
}

对于large bins,会先合并fast bins中的chunk到unsorted bin,然后进行更复杂的查找和分割操作。

7. 安全机制与常见攻击面

  1. Unlink攻击:需要绕过FD->bk == P && BK->fd == P检查
  2. Fastbin Attack:利用fast bin的单链表特性进行攻击
  3. Unsorted bin Attack:通过修改unsorted bin中的chunk实现任意地址写
  4. House of系列攻击:如House of Spirit、House of Force等
  5. malloc_hook劫持:通过覆盖__malloc_hook控制程序流

8. 总结

理解malloc的内部机制对于二进制安全研究至关重要,特别是在堆漏洞利用方面。关键点包括:

  • chunk结构及其元数据含义
  • 各种bins的管理方式和分配策略
  • unlink操作及其安全检查
  • malloc的核心分配流程
  • 各种安全机制和对应的绕过方法

通过深入分析malloc源码,可以更好地理解堆漏洞的成因和利用方式,为Pwn中的堆利用打下坚实基础。

malloc在Pwn中的角色与内部机制分析 1. chunk结构分析 glibc中的内存块(chunk)是malloc管理内存的基本单位,其结构如下: 关键点: prev_size :前一个chunk空闲时存储其大小,否则可能被前一个chunk使用 size :当前chunk大小,包含头部开销,最低3位用作标志位 fd 和 bk :空闲chunk的双向链表指针 fd_nextsize 和 bk_nextsize :仅用于large bins的特殊指针 2. 基础概念和宏定义 2.1 关键宏定义 2.2 重要操作宏 3. unlink操作分析 unlink是malloc中最重要的操作之一,用于从双向链表中移除一个chunk: 关键点: 安全检查: FD->bk != P || BK->fd != P ,这是unlink攻击需要绕过的关键检查 普通链表操作: FD->bk = BK; BK->fd = FD; 对于large bins的额外处理:涉及 fd_nextsize 和 bk_nextsize 指针 4. bins的分类与管理 glibc使用多种bins来管理不同大小的空闲chunk: bin分类: Fast bins :管理小尺寸chunk(默认 <64字节),单链表,LIFO顺序 Small bins :管理 <512字节的chunk,双向链表,FIFO顺序 Large bins :管理≥512字节的chunk,双向链表,按大小排序 Unsorted bin :临时存放刚释放的chunk,双向链表 bin大小分布: 64个8字节间隔的small bins 32个64字节间隔的bins 16个512字节间隔的bins 8个4096字节间隔的bins 4个32768字节间隔的bins 2个262144字节间隔的bins 1个剩余大小的bin 5. __ libc_ malloc函数分析 关键点: 首先检查 __malloc_hook ,若不为空则调用hook函数 获取arena并调用 _int_malloc 进行实际分配 第一次分配失败会尝试其他arena 6. _ int_ malloc函数分析 _int_malloc 是malloc的核心实现,主要流程: 6.1 Fast bins处理 6.2 Small bins处理 6.3 Large bins处理 对于large bins,会先合并fast bins中的chunk到unsorted bin,然后进行更复杂的查找和分割操作。 7. 安全机制与常见攻击面 Unlink攻击 :需要绕过 FD->bk == P && BK->fd == P 检查 Fastbin Attack :利用fast bin的单链表特性进行攻击 Unsorted bin Attack :通过修改unsorted bin中的chunk实现任意地址写 House of系列攻击 :如House of Spirit、House of Force等 malloc_ hook劫持 :通过覆盖 __malloc_hook 控制程序流 8. 总结 理解malloc的内部机制对于二进制安全研究至关重要,特别是在堆漏洞利用方面。关键点包括: chunk结构及其元数据含义 各种bins的管理方式和分配策略 unlink操作及其安全检查 malloc的核心分配流程 各种安全机制和对应的绕过方法 通过深入分析malloc源码,可以更好地理解堆漏洞的成因和利用方式,为Pwn中的堆利用打下坚实基础。