asisctf final 2021 rbtree writeup
比赛的时候一直在看这个题,最后能够构造稳定的 UAF,可一直没有找到方法泄露或者任意地址写可控内容。感谢出题人放出了 exp。😊
https://github.com/r4j0x00/ctf-challenges/tree/main/ASIS-Finals-2021/pwn-rbtree
exp逻辑十分清晰,所以我只分析一下重要的两个点。
eventfd
这是这个题官方解法中比较重要的一环。通过 efd = eventfd(0, 0);
来创建一个新的 eventfd 文件。此时,在内核中通过 kmalloc 分配了一个 struct eventfd_ctx
结构体,这个结构体的大小为 0x30, slab 大小为 0x40:
1 | // https://elixir.bootlin.com/linux/v5.15.7/source/fs/eventfd.c#L30 |
在官方解法中,通过 eventfd_ctx
来占位 msg2 被 free 后的空缺。之后依次 free(msg1), free(msg3)
,从而将双向循环链表恢复合法。
考虑到题目的 queue_msg 结构体的的双向链表正好在 offset 0x18 处,其 prev 与 eventfd_ctx 的 count 重合,所以可以从 eventfd_ctx 的 count 泄露出内核堆地址。
一个小细节,直接通过 read 来读取 eventfd 内的内容会导致 count 清空,在解链时会导致链不合法,所以得这样读:
1 | uint64_t get_eventfd_count(int efd) { |
/proc/sys/kernel/modprobe
这对于我来说是个内核提权新姿势。内核在 /proc/sys/kernel/
注册了一堆文件,其中包括 modprobe
。在内核中,这些文件的权限控制是由 ctl_table
结构体定义的。
1 | // https://elixir.bootlin.com/linux/v5.15.7/source/include/linux/sysctl.h#L116 |
kern_table
被分配在在内核堆空间,所以我们只需要泄露出内核堆地址就可以修改 /proc/sys/kernel/modprobe
的权限了,把原来的 0644 改成 0666 即可达到和修改 modprobe_path 相同的效果。