给shellcode找块福地- 通过VDSO绕过PXN
0x01 引语
近日看雪论坛里有个兄弟在写漏洞利用的时候遇到了点问题。他发现rop实现提权的方式在不同设备版本适配的时候比较麻烦,需要做的工作较多。
他想知道有没有更好更稳定的方式,绕过PXN让内核执行shellcode。
最近一年,一种利用VDSO( Virtual Dynamic Shared Object,虚拟动态共享对象)机制的攻击方式,在脏牛(DirtyCow)等漏洞利用代码中得到应用。本文将介绍这种新型的绕过PXN的攻击方式。
0x02 回顾PXN
PXN( PrivilegedExecute-Never) “特权执行从不”技术由ARM公司提出,主要防御RET2USR的攻击,它的开启与否主要由页表属性的PXN位来控制。
0x03 传统的PXN绕过技术
1)利用ROP技术绕过PXN
其主要原理是通过控制内存中的一段数据,通过控制数据来控制代码执行流,如组合执行内核中特定的代码片段,从而达到修改内核中的关键数据,达到提权限的目的。这种攻击方式是需要进行不同机型中查找到多段代码片段,如果需要root的机型较多,则需要攻击者投入较多精力去做适配,另外由于ROP往往要做栈迁移,使得漏洞利用的稳定性不是很好。
2) 利用RET2DIR技术绕过PXN
该技术由哥伦比亚大学在2014年提出,其利用原理是,linux内核在设计的时候,为了提高内存的操作效率,在用户空间映射内存的时候,内核也相应地在内核的低端内存区地址映射一段影子内存。
同样,攻击者也可以将用户空间的攻击代码映射到内核的低端内存可执行区或者将特定数据进行喷射到内核的低端内存,进行内存布局,然后利用发现的漏洞,让内核执行攻击代码,从而达到提权的作用。这项技术在32位arm设备上有60%以上的成功率,而在64位arm中有96%的成功率。
通过RET2DIR和JOP方式的结合,可以使得UAF这类漏洞的利用代码比较稳定,而且成功率较高。
keen_team在cve-2015-3636的漏洞利用中使用这两个技术后,一时间RET2DIR成为漏洞利用的“倚天剑”。
不过在2016年七月google在android PIXEL(内核3.18-16.04)版本以后封杀了RET2DIR的攻击方式。
3) 通过修改寄存器绕过pxn
我们可以通过修改CP15/CR4寄存器信息来绕过PXN/SMEP。这种方式往往需要ROP一段内核的代码来修改CP15寄存器的值,其复杂度和ROP其实是一样的。
4) 通过内核特定函数完成PXN绕过
该技术在2016年MOSEC大会上由360团队公开,该技术巧妙地利用kernel_setsockopt函数的特性,通过控制r0,让内核执行set_fs(KERNEL_DS),实现任意地址读写权限的效果。这种方式在x64内核时需要进行ROP栈迁移,复杂度较高。
那有没有更好的方式,给SHELLCODE安营扎寨,让内核执行我们的提权代码呢?
0x04 新的攻击方法
由于linux内核是个庞大且复杂的系统,一定还有这样的“福地”,让我们可以在内核中找到,空间任意执行我们的ShellCode。
近年攻击者把目标放在了内核和用户空间共享的代码空间-VDSO( Virtual Dynamic Shared Object,虚拟动态共享对象)。
VDSO是内核为了减少内核与用户空间频繁切换,提高系统调用效率而提出的机制。特别是gettimeofday这种对时间精度要求特别高的系统调用,需要尽可能地减少用户空间到内核空间堆栈切换的开销。
我们可以通过cat /proc/self/maps命令来查看用户态vdso映射情况。
不过事情没有想象那么容易,该段空间是只读和可执行的。
通过阅读内核的代码,我们可以发现,如果我们可以把这段映射区改为可读可写可执行就可以了。
我们可以通过利用内核中现成的代码来完成页表属性的修改。我们发现可以通过内核导出函数set_memory_rw来打开内核页表的读写权限。set_memory_rw函数的定义如下:
int set_memory_rw(unsigned long virt, int numpages)
virt 为起始虚拟地址,可以设置为_text的虚拟地址,
numpages 为页表的数量。
假设我们可以修改vdso映射区的读写权限,那我们的攻击路径可以是这样:
1. 利用内核漏洞的执行set_memory_rw函数,修改vdso映射区的读写权限。
2. 在VDSO布置shellcode。
3. 调用shellcode提权。
那问题来了,用户态的vdso的虚拟地址是多少呢。
在高版本的glibc中,我们可以通过以下代码获得vdso的地址。
当然我们也可以通过读取/proc/selft/maps虚拟文件内容的方式获得用户态的vdso映射地址。我们的攻击路径可以进一步再完善为:
1. 获取vdso的映射地址。
2. 利用内核漏洞的执行set_memory_rw函数,修改vdso映射区的读写权限。
3. 在VDSO布置shellcode。
4. 调用shellcode提权。
0x05 布置shellcode
内核映射到用户空间的vdso其实为一个完整的ELF文件。该文件一般为,这里面包括了代码段和数据段。
当然我们也可以直接通过计算elf文件的长度,然后在文件长度的末端放置我们的shellcode,然后让内核去执行该段地址的代码即可。
0x06 进一步完善攻击路径
在很多情况下,我们利用的内核漏洞不一定是任意代码执行这一类的,我们利用的内核漏洞也可能是任意地址写任意值的这类漏洞。那这种情况下我们需要通过任意地址写转变成任意代码执行。
我们常见的方法是通过调用改写 ptmx_fops->unlocked_ioctl 或者是 覆盖ptmx_fops指针,使其指向我们要调用内核地址,然后调用 /dev/ptmx 的ioctl或者是check_flags来完成内核代码的调用。
由于这种方式需要进行较复杂的反汇编及偏移计算才能确定unlocked_ioctl的地址,另外check_flags函数只能传递一个参数,而且该函数只能传递一个32位的数据 ,显然这种方式需要我们进一步的改进。
那什么样的函数指针是比较好用的呢,我们要求这样的函数指针最好满足以下条件:
第一、可以传送多个参数。
第二、该函数指针可以在符号表里方便找到。
第三、这个函数调用函数指针前,各个参数都是透传,没有经过中间加工。
第四、函数返回值也没有经过内部加工。
在2016年11月韩国INetCop 安全团队的 dong-hoon you (x82)介绍了一个十分好用的系统调用函数 prtcl。
这个函数可以最多传输1~5个参数。 它的调用路径是这样的:
prctl->security_task_prctl-> (security_operations)->task_prctl 。
如果我们修改(security_operations)->task_prctl为我们调用的set_memory_rw函数地址。
那么我们通过用户态调用prtcl函数,使得内核就可以执行set_memory_rw函数,打开VDSO映射区的读写权限。
1. 获取vdso的映射地址。
2. 利用内核漏洞任意地址写的能力将(security_operations)->task_prctl 函数指针修改为set_memory_rw函数的地址。
3. 调用prtcl的系统调用,让内核执行set_memory_rw函数,修改vdso映射区的读写权限。
4. 在VDSO中布置shellcode。
5. 调用gettimeofday函数或通过prtcl的系统调用,让内核调用shellcode提权。
0x07 总结
针对内核ROP攻击等绕过PXN技术的不足,本文介绍了基于VDSO机制的新绕过PXN的方法。这种方法使得攻击代码能够轻松绕过PXN限制,多快好省地执行我们的提权代码。
除了介绍基于VDSO机制的新绕过PXN的方法,本文还介绍了prctl函数在漏洞利用中的运用。
后续我们将继续介绍其他新型的绕过PXN方法,敬请期待。
0x08参考资料
1. Johan Petersson,What is linux-gate.so.1?,
http://www.trilithium.com/johan/2005/08/linux-gate/
2. INetCop Security dong-hoon you (x82), New Reliable Android Kernel Root Exploitation Techniques,
http://powerofcommunity.net/poc2016/x82.pdf
3. Vasileios P. Kemerlis Michalis Polychronakis Angelos D. Keromytis, Columbia Universityret2dir: Rethinking Kernel Isolation, http://www.blackhat.com/docs/eu-14/materials/eu-14-Kemerlis-Ret2dir-Deconstructing-Kernel-Isolation.pdf
本文由看雪论坛 ggggwwww 原创,转载请注明来自看雪社区
热门阅读
点击阅读原文/read,还有更多干货等着你~