在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位用作标志位fd和bk:空闲chunk的双向链表指针fd_nextsize和bk_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; \
} \
} \
} \
}
关键点:
- 安全检查:
FD->bk != P || BK->fd != P,这是unlink攻击需要绕过的关键检查 - 普通链表操作:
FD->bk = BK; BK->fd = FD; - 对于large bins的额外处理:涉及
fd_nextsize和bk_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分类:
- 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函数分析
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;
}
关键点:
- 首先检查
__malloc_hook,若不为空则调用hook函数 - 获取arena并调用
_int_malloc进行实际分配 - 第一次分配失败会尝试其他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. 安全机制与常见攻击面
- 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中的堆利用打下坚实基础。