一步一步PWN路由器之uClibc中malloc&&free分析
字数 1021 2025-08-22 12:22:24

uClibc中malloc与free机制深入分析

前言

本文详细分析uClibc中的内存管理机制,重点剖析malloc和free的实现原理,为嵌入式设备(如路由器)的堆溢出漏洞利用奠定基础。uClibc是glibc的精简版,主要用于资源受限的嵌入式环境。

uClibc的malloc实现

uClibc提供三种malloc实现:

  1. malloc-standard:从glibc移植的dlmalloc
  2. malloc:uClibc最初的自实现版本
  3. 另一种实现(文中未详细说明)

本文重点分析第二种实现(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;
}

分配策略:

  1. 如果剩余空间不足最小空闲区大小(HEAP_MIN_FREE_AREA_SIZE),则整个分配
  2. 否则仅调整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;
}

安全分析

可能的攻击面

  1. unlink攻击:需要覆盖空闲块的heap_free_area指针

    • 元数据在块末尾,需精确控制溢出
    • 需要满足特定条件才能触发unlink
  2. 前向合并漏洞

    fa_size += prev_fa->size;
    __heap_link_free_area_after(heap, fa, prev_fa->prev);
    
    • 若可伪造prev_fa->prev,可实现任意地址写入fa值

与glibc的差异

  1. 元数据位置相反(uClibc在末尾,glibc在开头)
  2. 空闲管理结构不同(uClibc使用heap_free_area)
  3. 合并逻辑实现差异

实践建议

  1. 交叉编译分析

    • 编译为ARM/x86架构便于IDA分析
    • 使用Docker部署交叉编译环境
  2. 调试技巧

    • 静态分析结合动态调试
    • 重点关注heap_free_area结构操作
  3. 漏洞利用

    • 需要精确控制堆布局
    • 利用合并操作实现内存写

总结

uClibc的内存管理机制与glibc有显著差异,理解这些差异对嵌入式设备漏洞利用至关重要。虽然文中提到的攻击面有限,但在实际漏洞利用中,精确控制堆布局和利用合并操作仍是可能的攻击向量。

uClibc中malloc与free机制深入分析 前言 本文详细分析uClibc中的内存管理机制,重点剖析malloc和free的实现原理,为嵌入式设备(如路由器)的堆溢出漏洞利用奠定基础。uClibc是glibc的精简版,主要用于资源受限的嵌入式环境。 uClibc的malloc实现 uClibc提供三种malloc实现: malloc-standard:从glibc移植的dlmalloc malloc:uClibc最初的自实现版本 另一种实现(文中未详细说明) 本文重点分析第二种实现(malloc目录下的原始实现)。 malloc核心流程 实际分配工作由 malloc_from_heap 完成: 内存块结构 uClibc的内存块结构特殊: 返回地址是ADDR SIZE包含整个块大小(包括头部) 元数据存储在块 末尾 (与glibc相反) 空闲区域管理 使用 heap_free_area 结构管理空闲内存: 内存布局: __ heap_ alloc实现 分配策略: 如果剩余空间不足最小空闲区大小(HEAP_ MIN_ FREE_ AREA_ SIZE),则整个分配 否则仅调整heap_ free_ area的size字段 free机制分析 free核心流程 实际释放由 __free_to_heap 完成: __ heap_ free实现 关键合并逻辑: 链表操作函数 安全分析 可能的攻击面 unlink攻击 :需要覆盖空闲块的heap_ free_ area指针 元数据在块末尾,需精确控制溢出 需要满足特定条件才能触发unlink 前向合并漏洞 : 若可伪造prev_ fa->prev,可实现任意地址写入fa值 与glibc的差异 元数据位置相反(uClibc在末尾,glibc在开头) 空闲管理结构不同(uClibc使用heap_ free_ area) 合并逻辑实现差异 实践建议 交叉编译分析 : 编译为ARM/x86架构便于IDA分析 使用Docker部署交叉编译环境 调试技巧 : 静态分析结合动态调试 重点关注heap_ free_ area结构操作 漏洞利用 : 需要精确控制堆布局 利用合并操作实现内存写 总结 uClibc的内存管理机制与glibc有显著差异,理解这些差异对嵌入式设备漏洞利用至关重要。虽然文中提到的攻击面有限,但在实际漏洞利用中,精确控制堆布局和利用合并操作仍是可能的攻击向量。