NSSCTF-CISCN-2022-house_of_cat 题解
题目
checksec

seccomp

只能 orw ,但是限制 read 的 fd 为 0 ,于是可以先 close(0) 再 orw
IDA
main

handle_command

parse_command


需要逆向解析出指令格式
execute_command

menu

套一层指令解析的菜单题
add

只能申请 largerequest 0x420~0x470 ,放入 largebin 的话, 0x420~0x430 一个 bin , 0x440~0x470 一个 bin
edit

edit 限制为两次,只能写 0x30
show

用于 leak
delete

有 UAF
攻击思路
先分析出命令格式: COMMAND | r00tQWB QWXFarg
然后发现要 login 成为管理员,再 cat 拿菜单
1 | LOGIN | r00tQWB QWXFadmin |
之后就是堆题
只能申请固定范围内的 largebin ,考察 largebin attack 技巧,而 edit_count 的限制让这题变得棘手
由于程序无法正常退出,我们需要利用两次 largebin attack :一次劫持 stderr 结构体指针,把它覆盖成可控堆地址再在可控堆地址上写 fake IO_FILE 结构体;另一次利用错位篡改 topsize 为一个较小值,这样可以触发 sysmalloc 中的一个 __malloc_assert ,从而执行 fake stderr 中 house of apple2 流程。这两次攻击会耗尽 edit_count
在阅读 largebin 相关源码时我们注意到,一个 chunk 从 unsortedbin 转移到 largebin 时,如果它是最小的,那么就会触发 largebin attack 的核心流程:
1 | victim_index = largebin_index (size); |
注意到这里的 fwd->fd 即 largebin中最大的 chunk ,这意味着我们进行的两次 largebin attack 都需要劫持该 chunk 的 bk_nextsize
顺带提一个小技巧:在 leak libc 和 leak heap 前,我们已经布置好了堆上的分隔式结构防止发生合并,但是我们还需要借助 libc 和 heap 数据去构造 fake stderr , fake _wide_data , fake srop struct , rop chain ,而 edit 只允许写 0x30 的数据,因此我们可以把目标 chunk 给 delete 后再马上 add 回并写入伪造结构,这样不会有任何其他影响
完成 largebin attack 后,接下来我们选择 house of apple2 的 IO_FILE 调用链为:
1 | __malloc_assert ---> _fxprintf ---> locked_vfxprintf ---> __vfprintf_internal ---> |
这里我根据自己的理解,把调用链分成了三层:
第一层是执行 fake stderr 中的 fake vtable 的虚表指针之前的部分,这部分除了正常 house of apple2 的 fake stderr 要伪造的内容外,还有一些额外的内容需要伪造,不属于 house of apple2 调用链
第二层属于 house of apple2 调用链,在跳转的 target rip 之前
第三层即跳转至 target rip
这里给出 roderick 大佬的 house of apple2 的构造方式:
_flags 设置为 ~(2 | 0x8 | 0x800) ,如果不需要控制 rdi ,设置为 0 即可;
vtable 设置为 _IO_wfile_jumps/_IO_wfile_jumps_mmap/_IO_wfile_jumps_maybe_mmap 地址(加减偏移),使其能成功调用 _IO_wfile_overflow 即可;
_wide_data 设置为可控堆地址 A ,即满足 *(fp + 0xa0) = A
_wide_data->_IO_write_base 设置为 0 ,即满足 *(A + 0x18) = 0
_wide_data->_IO_buf_base 设置为 0 ,即满足 *(A + 0x30) = 0
_wide_data->_wide_vtable 设置为可控堆地址 B ,即满足 *(A + 0xe0) = B
_wide_data->_wide_vtable->doallocate 设置为地址 C 用于劫持 RIP,即满足 *(B + 0x68) = C
对于本题,还应:
_lock 设置为可读写地址,不要影响到其他部分,即满足 *(fp + 0x88) = rw_addr
此外,还要注意一个细节:堆地址比写入地址低 0x10
到这里,伪造结构基本布置完毕,然后是 setcontext 环节


这里以 rdx 为基准,经动态调试得知, rdx 会被赋值为 _wide_data , 即 *(fp + 0xa0) ,这是在调用链第一层末尾完成的
这一步做完后,愉快地打 rop 就行啦
exp
1 | from pwn import * |