给shellcode找块福地-
通过VDSO绕过PXN
0x01 引语
近日看雪论坛里有个兄弟在写漏洞利用的时候遇到了点问题。他发现rop实现提权的方式在不同设备版本适配的时候比较麻烦,需要做的工作较多。
他想知道有没有更好更稳定的方式,绕过PXN让内核执行shellcode。
最近一年,一种利用VDSO( Virtual Dynamic Shared Object,虚拟动态共享对象)机制的攻击方式,在脏牛等漏洞利用代码中得到应用。本文将介绍这种新型的绕过PXN的攻击方式。
0x02 回顾PXN
PXN( PrivilegedExecute-Never) “特权执行从不”技术由ARM公司提出,主要防御RET2USR的攻击,它的开启与否主要由页表属性的PXN位来控制。
64位内核中,内核对用户空间的内存页默认开启PXN位。通过设置PXN位,内核就不能直接执行用户空间的攻击代码,从而有效防御RET2USR攻击。
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映射情况。
由于该段用户空间和内核空间是一一映射的, 如果我们能在这段空间中布置我们的提权代码,我们可以让内核来执行该段代码,从而提权。
不过事情没有想象那么容易,该段空间是只读和可执行的。
通过阅读内核的代码,我们可以发现,如果我们可以把这段映射区改为可读可写可执行就可以了。
int arch_setup_additional_pages(struct linux_binprm *bprm, int uses_interp)
{
ret = _install_special_mapping(mm,
vdso_base, vdso_text_len,
VM_READ|VM_EXEC|
VM_MAYREAD|VM_MAYWRITE|VM_MAYEXEC, &vdso_spec[1]);
... }
我们可以通过利用内核中现成的代码来完成页表属性的修改。我们发现可以通过内核导出函数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提权。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)