LitCTF2024 heap-2.35
在 glibc 2.34 及以后的版本中,诸如 __free_hook 和 __malloc_hook 这类极易被劫持的全局函数指针被彻底移除了。因此,安全研究人员将目光转向了 FSOP (File Stream Oriented Programming)。你提供的这段 EXP 正是利用了 Large Bin Attack 结合 House of Apple 2 技术来实现任意代码执行的。
House of Apple 2 是一种基于 _IO_FILE 的利用链。它的终极目标是:在不劫持 vtable 指针(通常受到 CFI/vtable 检查保护)的情况下,利用合法的 _IO_wfile_jumps vtable 内部的回调机制来执行 system 函数。
当程序异常终止或调用 exit() 时,glibc 会遍历全局链表 _IO_list_all 并刷新所有流(调用 _IO_flush_all_lockp)。如果我们将 _IO_list_all 指向我们伪造的 _IO_FILE 结构,并将该结构体的 vtable 设为合法的 _IO_wfile_jumps,就会顺次发生以下调用:
- 调用
_IO_OVERFLOW,即_IO_wfile_overflow。 - 在
_IO_wfile_overflow中,如果满足一定条件,会调用_IO_wdoallocbuf。 _IO_wdoallocbuf会调用宽字符虚表(_wide_vtable)中的doallocate函数:fp->_wide_data->_wide_vtable->doallocate(fp)。
在 House of Apple 2 中,攻击者可以完全伪造 _wide_data 和 _wide_vtable,将 doallocate 替换为 system 的地址。由于调用时传入的参数 fp 就是我们伪造的结构体本身的地址,只要在结构体开头写入 /bin/sh 或 ;sh,就能完美执行 system(“;sh”)。
参考资料:https://c-lby.top/2024/2024-litctf-wp/#%E7%89%88%E6%9C%AC%E7%89%B9%E6%80%A7-3
题目分析:
main&menu
1 | int __fastcall main(int argc, const char **argv, const char **envp) |
1 | int menu() |
create
1 | __int64 create() |
设置了最多 16 个堆,不过并没有对堆的 size 进行限制。他的内容和大小是分开放的,都在 .bss 段。
delete
1 | void delete() |
这里一眼 UAF,free 后没有置 0
show
1 | int show() |
用printf来打印chunk内容,有一点需要注意就是\x00会被截断。
exit
1 | void __noreturn Exit() |
EXP 思路:
封装函数:
1 | def cmd(choice): |
泄露 Libc 与 Heap 基地址:
1 | add(8, 0x18) # 辅助 Chunk,后面用于覆盖 Chunk 0 的头部 |
在第一个断点处
我们先释放 chunk2 让他进入 UnSortedbin,在利用一次申请大块让 free chunk 放入 Largebin:
1 | pwndbg> heap |
在第二个断点处:
1 | Free chunk (largebins) | PREV_INUSE |
- 移入 Large Bin:当我们释放 Chunk 2 后,它在 Unsorted Bin 里。紧接着我们申请一个更大的 Chunk 4 (0x530)。glibc 在分配时,会遍历 Unsorted Bin 寻找合适的块,发现 Chunk 2 太小不满足要求,就会把它整理进对应的 Large Bin 中。
- 指针生成:一旦进入 Large Bin,Chunk 2 的头部就不只有 fd 和 bk 了,还会多出两个指针:fd_nextsize 和 bk_nextsize。
- fd / bk 指向 main_arena 内部,所以 show(2) 可以泄露 libc 基址。
- fd_nextsize / bk_nextsize 用于连接同等大小的块。因为当前 Large Bin 里只有它自己,这两个指针会指向它自身的堆地址。
这样当你去 show(2)的时候就可以打印出 largebin 的地址
计算关键 _IO_FILE 文件流
1 | large = u64(r.recv(6).ljust(8, b'\0')) # 其实是main_arena+0x490 |
泄露 largebin 的起始地址
我们可以想办法泄露 largebin 的地址,从而使栈上所有的堆地址都是地址可知且可控的。
1 | edit(2, b'A' * 0x10) |
printf 也就是 show 函数调用的打印函数遇到 \x00 会停止打印,如果我们不覆盖 fd 和 bk 指针,打印出的内容在碰到第一个 NULL 字节时就会中断。
当我们把 fd 和 bk 指针全部用 a 填满,就会继续打印出后面的 fd_next 和 bk_next 指针。
当 largebins 上只有一个 bin 的时候,他的 fd_nextsize 和 bk_nextsize 都会指向该 bin 的起始位置。
Large Bin Attack 劫持 _IO_list_all
这是接管程序控制流的最关键一步。我们的目标是把 _IO_list_all 这个全局变量的值,
篡改为我们可控的 Chunk 0 的地址。
1 | delete(0) # 将 Chunk 0 放入 Unsorted Bin |
我们利用 UAF 漏洞修改了 Chunk 2 的指针。我们将它的 bk_nextsize 修改为了 _IO_list_all - 0x20。
当我们 add(5, 0x550) 时,glibc 发现 Unsorted Bin 里的 Chunk 0 (0x510) 不满足需求,于是把它也移入 Large Bin。
在将 Chunk 0 插入 Large Bin 且排在 Chunk 2 后面时,glibc 源码中会执行类似这样的解链逻辑:
1
victim->bk_nextsize->fd_nextsize = victim
victim 就是正在被插入的 Chunk 0,而 victim->bk_nextsize 被我们伪造为了 _IO_list_all - 0x20。
fd_nextsize在结构体中的偏移恰好是0x20。所以,这行代码等价于:
(_IO_list_all - 0x20 + 0x20) = Chunk0_addr。结果导致
_IO_list_all指向了 Chunk 0 的头部
House Of Apple2 准备开始
在 _IO_list_all 指向了 Chunk0,glibc 把它当成了一个 _IO_FILE 结构体。我们需要在 Chunk 0 里布置假数据
1 | chunk_addr = heap - 0x560 # Chunk 0 的 Header 地址 |
再复习一下 _IO_FILE 文件结构:
1 | pwndbg> p *_IO_list_all |
- _IO_list_all 指向的是 Chunk0 的Header,而 _IO_FILE 的第一个成员是 _flags 。这意味着 Chunk 0 的 prev_size 字段被当成了 _flags,以此类推。
- edit(8) 恰好利用了堆块复用的特性,覆盖了 Chunk 0 的 prev_size。
- 0xfffff7f5 是精心计算的 flags 魔法值,用于绕过后续的条件检查。紧跟在后面的 ;sh\x00 则是待会儿传给 system 的参数。###???###存疑
- 我们最终执行的是
system("\xf5\xf7\xff\xff;sh\x00")。使用分隔符;可以有效的执行 system(‘sh’)
伪造 _IO_FILE 链
1 | # 构造主体 |
这段代码的本质就是在一个扁平的内存块 (Chunk 0) 中,同时嵌合了 _IO_FILE 主体、_wide_data 结构体和 _wide_vtable 虚表,并将它们通过地址偏移相互勾连起来。
exit 触发
1 | exit() |
当程序调用 exit() 或者从 main 函数返回时,glibc 的 IO 刷新机制启动,发生如下连锁反应:
- exit() 调用 _IO_flush_all_lockp,遍历 _IO_list_all 找到了我们的 Chunk 0。
- 检查 _mode 等于 -1,进入宽字符流处理分支。
- 从合法的 _IO_wfile_jumps 虚表中调用 _IO_WFOVERFLOW (即 _IO_wfile_overflow 函数)。
- _IO_wfile_overflow 内部经过一小段条件判断(被我们伪造的 0, 0, 1, 2 绕过),随后调用
_IO_wdoallocbuf(fp)。 _IO_wdoallocbuf内部有一句致命代码:fp->_wide_data->_wide_vtable->doallocate(fp)。- 根据我们在 Chunk 0 中的布局,这句代码被直接翻译成了:system(Chunk0 Header )。
总 EXP:
1 | from pwn import * |
更新: 2026-04-15 11:49:36
原文: https://www.yuque.com/idcm/wnemg9/pgnci7oshh1drgz4