House Of Kiwi
适用于 glibc2.23-glibc2.35 (漏洞所需要的malloc_assert函数在2.36中被修改,而在2.37中则完全被删除)
House of Kiwi
在 glibc 2.29 之后,当题目开启了沙箱,利用思路一般有两种,一种是劫持 __free_hook,然后利用 gadgets 进行栈迁移,另一种是劫持 __malloc_hook 为 setcontext + 61 利用 SetContext 攻击并劫持 IO_list_all 链,之后调用 exit 函数,让 _IO_cleanup 函数进行缓冲区的刷新去读取 flag。
但并非程序中都存在 exit 或是通过 syscall 调用的 exit,那么我们也就没有机会去调用 _IO_cleanup 刷新缓冲区。在 glibc2.34 之后又禁用了大部分的 hook 以保证安全性,那么我们就没办法利用上述方法。那么接下来就需要运用 House of Kiwi 了。
利用原理:
由于题目限制,我们无法返回 main 函数,也无法调用 exit 函数,那么我们就只能触发 malloc_assert 刷新 IO 流
触发 malloc_assert
在常规的 FSOP 链中有一个 _int_malloc 函数, 当所有的 Bins 都没有合适的空闲内存,且 Top Chunk 的大小不足以满足当前用户请求分配的内存大小时,_int_malloc 就会放弃内部搜索,调用 sysmalloc(nb, av) 向操作系统申请扩容堆空间。 当进入 sysmalloc 后,glibc 并不直接去扩容,而是会先对旧的Top Chunk 进行安全校验:
1 | assert ((old_top == initial_top (av) && old_size == 0) || |
此时会对该 Top Chunk 进行合法判断,满足下列条件之一:通过该判断就可以成功触发 malloc_assert 函数,进行之后的攻击
- 它是刚初始化的堆(old_size == 0)。
- 它是一个正常使用中的 Top Chunk,且必须同时满足:
- old_size >= MINSIZE (通常是 0x20 字节)
- prev_inuse(old_top) 标志位必须为 1 (Top chunk 之前的块总是被视为已使用的)
- old_end 必须是页对齐的(4KB 对齐)。
malloc_assert
1 | static void |
- __fxprintf 打印错误信息:一般来说其输出格式相当严谨 [程序名]: [文件名]:[行号]: [函数名]: Assertion [断言内容] failed \n
- fflush(stderr) 刷新标准错误流:它强制清空 stderr(标准错误流)的缓冲区,确保刚刚生成的错误信息能够立刻显示在终端或写入日志文件,避免因为程序突然死亡而导致报错信息丢失。
- about()异常终止程序:调用 abort() 触发 SIGABRT 信号,程序被操作系统被终止。
从源码当中我们可以看到函数调用了 fflush(stderr)函数,该函数调用后会调用 _IO_file_jumps 中的 sync 指针
这个指针在_IO_file_jumps偏移为0x60的位置,那么将这个指针进行劫持,就能达到我们想要的目的,如果题目禁用了execve的话,可以考虑通过 setcontext 来实现栈迁移
我们看一下 SetContext 函数:
1 | text:0000000000053030 ; __unwind { |
kiwi利用方法总结
1.拿shell
1 | 能够达成任意已知地址写,想办法控制_IO_file_jumps中的__sync指针,修改__sync指针为one_gadget |
2.打ORW
1 | 能够达成任意已知地址写,来想办法控制_IO_file_jumps中的__sync指针为setcontext+61 |
参考资料:https://xz.aliyun.com/news/12380
更新: 2026-04-29 09:40:36
原文: https://www.yuque.com/idcm/wnemg9/fpphb84no4i9uyzs