一步一步PWN路由器之uClibc中malloc&&free分析
字数 1021 2025-08-22 12:22:24
uClibc中malloc与free机制深入分析
前言
本文详细分析uClibc中的内存管理机制,重点剖析malloc和free的实现原理,为嵌入式设备(如路由器)的堆溢出漏洞利用奠定基础。uClibc是glibc的精简版,主要用于资源受限的嵌入式环境。
uClibc的malloc实现
uClibc提供三种malloc实现:
- malloc-standard:从glibc移植的dlmalloc
- malloc:uClibc最初的自实现版本
- 另一种实现(文中未详细说明)
本文重点分析第二种实现(malloc目录下的原始实现)。
malloc核心流程
void *malloc(size_t size) {
void *mem;
if (unlikely(size == 0)) goto oom;
mem = malloc_from_heap(size, &__malloc_heap, &__malloc_heap_lock);
return mem;
}
实际分配工作由malloc_from_heap完成:
static void *__malloc_from_heap(size_t size, struct heap_free_area **heap) {
size += MALLOC_HEADER_SIZE; // 添加头部大小
__heap_lock(heap_lock);
mem = __heap_alloc(heap, &size); // 尝试从heap分配
__heap_unlock(heap_lock);
// 分配失败处理(省略)
}
内存块结构
uClibc的内存块结构特殊:
SIZE | (unused) | allocation
BASE ^ ADDR ^ ADDR - MALLOC_ALIGN
- 返回地址是ADDR
- SIZE包含整个块大小(包括头部)
- 元数据存储在块末尾(与glibc相反)
空闲区域管理
使用heap_free_area结构管理空闲内存:
struct heap_free_area {
size_t size; // 空闲区大小
struct heap_free_area *next, *prev; // 双向链表指针
};
内存布局:
heap_free_area结构体
空闲空间
实际可用空间 = size - sizeof(struct heap_free_area)
__heap_alloc实现
void *__heap_alloc(struct heap_free_area **heap, size_t *size) {
// 大小对齐处理
_size = HEAP_ADJUST_SIZE(_size);
if (_size < sizeof(struct heap_free_area))
_size = HEAP_ADJUST_SIZE(sizeof(struct heap_free_area));
// 遍历寻找合适大小的空闲区
for (fa = *heap; fa; fa = fa->next)
if (fa->size >= _size) {
mem = HEAP_FREE_AREA_START(fa);
*size = __heap_free_area_alloc(heap, fa, _size);
break;
}
return mem;
}
分配策略:
- 如果剩余空间不足最小空闲区大小(HEAP_MIN_FREE_AREA_SIZE),则整个分配
- 否则仅调整heap_free_area的size字段
free机制分析
free核心流程
void free(void *mem) {
free_to_heap(mem, &__malloc_heap, &__malloc_heap_lock);
}
实际释放由__free_to_heap完成:
static void __free_to_heap(void *mem, struct heap_free_area **heap) {
size_t size = MALLOC_SIZE(mem); // 获取实际大小
mem = MALLOC_BASE(mem); // 获取基地址
__heap_lock(heap_lock);
fa = __heap_free(heap, mem, size); // 核心释放逻辑
// 内存回收处理(省略)
}
__heap_free实现
关键合并逻辑:
struct heap_free_area *__heap_free(struct heap_free_area **heap, void *mem, size_t size) {
void *end = (char *)mem + size;
// 查找插入位置(地址有序排列)
for (prev_fa = 0, fa = *heap; fa; prev_fa = fa, fa = fa->next)
if (unlikely(HEAP_FREE_AREA_END(fa) >= mem)) break;
if (fa && HEAP_FREE_AREA_START(fa) <= end) {
// 存在相邻情况
size_t fa_size = fa->size + size;
if (HEAP_FREE_AREA_START(fa) == end) {
// 前向合并
if (prev_fa && mem == HEAP_FREE_AREA_END(prev_fa)) {
fa_size += prev_fa->size;
__heap_link_free_area_after(heap, fa, prev_fa->prev);
}
} else {
// 后向合并
if (next_fa && end == HEAP_FREE_AREA_START(next_fa)) {
fa_size += next_fa->size;
__heap_link_free_area_after(heap, next_fa, prev_fa);
fa = next_fa;
} else {
// 无法合并,移动描述符到块尾部
fa = (struct heap_free_area *)((char *)fa + size);
__heap_link_free_area(heap, fa, prev_fa, next_fa);
}
}
fa->size = fa_size;
} else {
// 简单插入空闲链表
fa = __heap_add_free_area(heap, mem, size, prev_fa, fa);
}
return fa;
}
链表操作函数
static __inline__ void __heap_link_free_area(
struct heap_free_area **heap,
struct heap_free_area *fa,
struct heap_free_area *prev,
struct heap_free_area *next) {
fa->next = next;
fa->prev = prev;
if (prev) prev->next = fa;
else *heap = fa;
if (next) next->prev = fa;
}
安全分析
可能的攻击面
-
unlink攻击:需要覆盖空闲块的heap_free_area指针
- 元数据在块末尾,需精确控制溢出
- 需要满足特定条件才能触发unlink
-
前向合并漏洞:
fa_size += prev_fa->size; __heap_link_free_area_after(heap, fa, prev_fa->prev);- 若可伪造prev_fa->prev,可实现任意地址写入fa值
与glibc的差异
- 元数据位置相反(uClibc在末尾,glibc在开头)
- 空闲管理结构不同(uClibc使用heap_free_area)
- 合并逻辑实现差异
实践建议
-
交叉编译分析:
- 编译为ARM/x86架构便于IDA分析
- 使用Docker部署交叉编译环境
-
调试技巧:
- 静态分析结合动态调试
- 重点关注heap_free_area结构操作
-
漏洞利用:
- 需要精确控制堆布局
- 利用合并操作实现内存写
总结
uClibc的内存管理机制与glibc有显著差异,理解这些差异对嵌入式设备漏洞利用至关重要。虽然文中提到的攻击面有限,但在实际漏洞利用中,精确控制堆布局和利用合并操作仍是可能的攻击向量。