zer0pt ctf 2022 krce writeup
0x00 简介
题目给出了内核模块 buffer.ko 和与之交互的 interface。和常规的内核题目不同,我们无法拿到完整的用户空间执行权限,只能通过 interface 与内核交互。所以这个题目的最大挑战是回到用户态后,如何拿到 shell。
我们在内核空间利用部分使用了常规的解法: 将漏洞转化为任意地址读写,覆盖 poweroff_cmd
然后执行 __orderly_poweroff
,从而任意命令执行 (并不完全任意,在下文详述)。在最后的命令执行中出现了一些奇妙问题,让我们思考了很久,不过我们最后还是成功地拿到了 root shell。
0x01 漏洞
1 | typedef struct { |
在 buffer_edit
和 buffer_show
函数中有明显的越界读写,可是越界读写只包括堆块后的物理直接映射区 (direct mapping area)。因为用户进程的内存页也会被映射到这块区域,我们考虑了直接溢出到用户进程空间的可能性。但是我们发现输入和输出十分缓慢,根本无法在限定时间内泄露或覆盖将近 30M 的空间。所以我们重新审计了代码。
虽然在 buffer_new
函数中 index
使用的是 uint32_t
类型,但在后面的 buffer_edit
和 buffer_show
中却使用了 int32_t
类型。这样就会导致更为严重的数组下溢,此时内存布局如下 (without kalsr)。
0x02 利用
在 buffer
前有指向内核基地址的 link,我们可以通过其泄露内核基地址 (kernel base) 和模块基地址 (kmod base) 。当然,如果有必要我们也可以泄露出内核堆地址。
之后我们通过地址 0xffffffffc0002348
所指向的 0xffffffffc0002350
将后面的部分原样覆盖,直到 buffer。将 buffer 覆盖为我们想要的地址之后就可以实现内核空间的任意读写 (arb read/write),当然我们现在已经只需要任意地址写 (arb write) 就可以了。
我们选择覆盖 poweroff_cmd
为 "/bin/sh\x00"
,并且覆盖 buffer
模块的 fops
虚表的 ioctl
函数为 __orderly_poweroff
。这样我们在下次执行 ioctl
时就能执行 run_cmd("/bin/sh")
以 root 执行 /bin/sh
。
1 | static int __orderly_poweroff(bool force) |
到目前为止还一切顺利,一般来说到了这一步已经可以算做完成了利用了。但是… 现在的问题是我们没有办法和 shell 交互…
我们想到我们的终端设备是 /dev/console
。所以可以通过 /bin/sh -i <> /dev/console >&0 2>&1
来重定向输入输出。然而测试之后发现仍然不行。我们尝试了很多重定向的 trick 后发现执行 /bin/sh -c sh</dev/console>/dev/console
可以显示出终端提示符 “#”,但是只能执行一条命令并且有时可以看到命令输出有时看不到。最后,我们发现是我们输入命令的同时会让 interface 进程结束,并执行 poweroff -f
,打印输出和 poweroff 存在竞争…
最后… 我们拿到 shell 后首先执行了 rm /sbin/poweroff
…
0x03 EXP
请原谅我不太想美化脚本了,所以你看到的是一个很丑陋的脚本…
1 | #!/usr/bin/env python3 |