ret2dir
前言
ret2dir
是2014年在USENIX发表的一篇论文,该论文提出针对ret2usr
提出的SMEP
、SMAP
等保护的绕过。全称为return-to-direct-mapped memory
,返回直接映射的内存。论文地址:https://www.usenix.org/system/files/conference/usenixsecurity14/sec14-paper-kemerlis.pdf
ret2dir
在SMEP
与SMAP
等用于隔离用户与内核空间的保护出现时,内核中常用的利用手法是ret2usr
,如下图所示(图片来自论文)。首先是在内核中找到可以控制指针的漏洞,修改指针使其指向为用户空间,因此在用户空间布置恶意的数据或者代码,完成漏洞的利用。但是当SMEP
与SMAP
保护的出现,在内核态下,不能够执行或者访问用户空间的代码或者数据,导致了该利用方式失效,因为即使在用户空间中部署了payload
,在内核态下也无法访问。因此这种通过显示数据的共享方式已经不再适用了。
所以作者提出了一种思路,能否在内核空间中也能够访问到用户空间的数据。作者最终找到了一段区域,可以隐式的访问用户空间的数据。在内核中存在这部分区域direct mapping of all physical memory
,物理地址直接映射区。
这个映射区其实就是内核空间会与物理地址空间进行线性的映射,我们可以在这段区域直接访问到物理地址对应的内容。
那么作者就提出了一种攻击场景,由于在虚地址中的内容最终都会映射到物理地址上,若能将用户空间的数据同样映射到这段区域上,岂不是就可以在内核空间也可以访问到用户空间的数据了。该段区域也被称之为phsymap
,它是一段大的,连续的虚拟内存区域,它包含了部分或全部的物理内存的直接映射。下图这种情况作者也称之为是虚拟地址别名的情况,因为在用户空间与内核空间中都存在一个地址可以访问payload
。
最终作者构想的攻击场景如下图所示(图片来自论文),不同于ret2usr
,指针不再被修改为指向用户空间,而是指向了物理地址的直接映射区,由于该映射区指向物理地址,而在用户空间构造的payload
也会映射到物理地址,因此若能获得指向存在payload
的用户空间对应的物理地址在phsymap
位置,就能够直接执行用户空间的payload
。
想要获得映射地址有以下方法
(1)通过读取
/proc/pid/pagemap
获取,该文件中存放了物理地址与虚拟地址的映射关系,可是该文件需要root
权限才能读取。(2)通过大量覆盖
phsymap
内存的方法,提高命中率。使用堆喷技术,在该内存区填充大量的payload
这样既不会影响payload
的执行,又能够提高命中payload
的可能性,填充效果如下图
在旧版本的内核中phsymap
是具有可执行权限的,因此可以在用户空间中填充shellcode
,但是如今的内核版本phsymap
已经不具备可执行权限了,因此只能在里面填充ROP
链
miniLCTF_2022-kgadget
题目地址:https://github.com/h0pe-ay/Kernel-Pwn/tree/master/miniLCTF_2022
kgadget_ioctl
在kgadget_ioctl
中,当我们输入的操作码为0x1BF52
时,会将rdx
寄存器中的值进行解引用,并且以函数的方式调用该地址,这就导致了任意地址执行。
run.sh
题目提供的run.sh
开启了smep
与smap
的保护,但是没有开启地址随机化KASLR
。因此虽然我们可以控制内核执行任意的地址,但是由于题目开启了smep
与smap
,因此该地址值不能选择为用户空间的地址。
1 |
|
ret2dir利用流程
首先是如何执行我们指定的地址值的,可以看到实际是将我们传入的地址,解引用后存放到rbx
寄存器,结果通过将rbx
寄存器的值移动到栈顶,从而修改栈顶的值,接着调用ret
指令,使得执行被解引用的值。
想要使得内核提权,需要执行commit(prepare_kernel_cred(0)
,接着通过swapgs
和ret
指令的组合。因此需要找到一段内存,将该流程的ROP
链填充进去。这是因为kgadget_ioctl
并不是执行我们传入进去的地址,而是需要将该地址先解引用后再执行,相当于需要执行传入地址对应的内容。因此若我们直接将commit
函数的地址传入进去,它会执行commit
函数指向的内容。
那么这段区域需要选取在哪里,若我们直接再用户空间中构造这段payload
,接着将用户空间地址传递给ioctl
是不可行的,因为内核开启了smap
与smep
的保护,因此对用户空间的访问都是不被允许的。
因此需要用到ret2dir
的技巧,由于用户空间的虚拟地址同样会映射到物理地址,而在内核空间存在一段内存被称之为phsymap
,它存放着物理地址的内容,因此我们在用户空间填充的内容,可以在phsymap
找到。但是这段内存十分庞大,有64TB的大小,我们怎么才能确保搜索到存放我们payload
的地址呢?答案就是尽可能的填充,使得我们用户空间的payload
尽可能的大,那么我们搜索到的几率也会增大。
我们以页(4096
)为单位开辟内存,并且循环了0x4000
次,
1 |
|
可以发现,在用户空间写入的z
值,我们在内核空间同样可以访问到。当然写入的次数以及字节数是可以自己人为调整的,可以频繁尝试,尽可能的大的填充,这样我们找到的几率也更大。
当然有时候页的大小页不一定是4096,因此可以使用getconf PAGESIZE
获得页的大小
因此我们已经找到能够访问到用户空间payload
的内核地址值,接着需要将内核栈的空间迁移到phsymap
上,这是因为用原来的内核栈无法使得连续gadget
之间的调用。这里修改为测试gadget
,用于测试不做栈迁移会发生什么。
1 |
|
可以看到执行一次pop rdi; ret
,这是因为ret
指令会将当前栈顶的值弹出栈,而我们输入的值不再栈上,而是在phsymap
上。因此当我们输入的ROP
链不再栈上时,就需要使用栈迁移。
由于内核中存在着需要改变rsp
寄存器的gadget
,只要使用add rsp, xxx; ret
即可完成栈迁移。因此需要在栈上填入phsymap
的地址,使得经过add rsp, xxx
后能够使得rsp
指向phsymap
。为了使得栈上能够存储phsymap
的地址,这里需要借助一个结构体pt_regs
。
1 |
|
可以看到这个结构体存放了一系列的寄存器,这是因为在进行系统调用时,会完成从用户态到内核态的切换,因此需要保存用户态时的上下文寄存器,而这些寄存器的值都需要保存在pt_regs
中。使用下述代码测试上述pt_regs
结构体存放的位置。
1 |
|
可以看到我们在执行系统调用之前的参数,都会以pt_regs
结构体中的顺序进行存放,这里需要注意的是r11
寄存器用来存放了rflags
的值。
不过出题者在会对pt_regs
结构体中的部分寄存器的值进行修改。
最后只剩下r8
与r9
寄存器是可控的。但是只是用两个寄存器的值就足于完成栈迁移的操作了。
这里可以计算一下栈顶到r9
寄存器的距离0xffffc9000021ff98 - 0xffffc9000021fed0 = 0xc8
,因此找到add rsp 0xc0
的寄存器即可,因为ret
指令还会进行一次弹栈操作。这里一开始是使用extract-image.sh
进行提取,但是会报错。因此改用vmlinux-to-elf
,这个工具提取出的符号比较全。工具的地址为https://github.com/marin-m/vmlinux-to-elf
提取出来就可以愉快的获取gadget
。由于没找到add 0xc8
的gadget
,因此找了个平替的。再结合pop rsp; ret
指令即可完成栈迁移的操作。
1 |
|
接着需要考虑堆喷的填充大量内存,因为题目没有开启地址随机化,因此即使不使用堆喷,也能够定位到具体的地址,但是实际情况是该地址可以随机,因此需要确保落入到其他地址也能完成利用。由于第一条指令必须是add rsp, 0xa8; pop rbx; pop r12; pop rbp; ret;
,因为需要进行栈迁移。因此在一页的内存中,因使用尽量多的该指令进行填充,确保栈迁移的正常执行。
由于完成提权的payload
需要0x58
的大小,而该指令会将rsp
抬高0xc0
,因此用(4096 - 0x58 - 0xc0) / 8 = 0x1dd
,因此这里循环复制该指令0x1dd
次,接着将剩余空间使用ret
指令(常用的堆喷的指令)填充(这里使用了xor esi , esi; ret
,因为异或操作不影响。)
1 |
|
最后是在提权时没找到合适gadget
将prepare_kernel_cred
的返回值即rax
寄存器的值,移动到rdi
寄存器中。因此学了下出题者的wp
,发现出题者使用了init_cred
结构体作为commit_creds
函数的参数。
init_cred
是 Linux 内核中的一个结构体,用于表示进程的初始凭证。它包含了与进程相关的安全属性和权限信息。,init_cred
结构体通常用于表示初始的 root 凭证。因此只需要借助一个pop rdi;ret
的gadget
加上init_cred
结构体的地址就可以完成root
凭证的初始化了。
exp
最后完整的exp
如下
1 |
|
参考链接
- https://ctf-wiki.org/pwn/linux/kernel-mode/exploitation/rop/ret2dir/
- https://blog.wohin.me/posts/linux-kernel-pwn-05/
- https://arttnba3.cn/2021/03/03/PWN-0X00-LINUX-KERNEL-PWN-PART-I/#%E4%BE%8B%E9%A2%98%EF%BC%9AMINI-LCTF2022-kgadget
- https://www.anquanke.com/post/id/185408
- https://www.cnblogs.com/0xJDchen/p/6143102.html
- https://www.usenix.org/system/files/conference/usenixsecurity14/sec14-paper-kemerlis.pdf