CVE-2017-8890 漏洞利用(root nexus6p@kernel 3.10)
由于CVE-2017-8890属于linux kernel 4.10版本之前网络子系统中的问题,因此Android也受影响。hardenedlinux上曾收集了Jeremy Huang的部分exploit,经过测试在nexus6p上可以控制PC指针。本文内容多是别人的经验总结,对该exploit做简单分析并基于该exploit完成后续的利用工作。
测试环境
* nexus6p
* 自编译的kernel version 3.10
利用过程
1. control PC
2. patch addr_limit
3. search init task_struct's init_head (has two pointers) by pushable_tasks 0x8c
4. search target process (init and exp) and get cred address by cpu_timers[2]
5. patch exp cred uid,gid,suid..... cap...... and get root
6. patch exp security's osid, sid and selinux_enabled, selinux_enforcing
7. system("/system/bin/sh")
一、控制PC
控制PC的过程Jeremy的exploit已经完成了。利用的思路在Freebuf上云图信安的文章[1]已经详细分析过了。这里简单阐述一下,server端首先通过setsockopt 中的MCAST_JOIN_GROUP参数初始化一个带有vulnerable obj ip_mc_socklist的socket,设置server的 socket监听后,创建client线程connect两次,因此server端accept返回,在内核中将父socket复制,产生了两个带有vulnerable obj ip_mc_socklist的子socket。
释放这两个socket时,ip_mc_socklist obj也会被释放两次。释放ip_mc_socklist obj的过程是在ip_mc_drop_socket中完成的,调用kfree_rcu注册回调函数,等待回调函数触发时来真正的释放这个obj。当rcu宽限期结束后,时钟中断触发时会调用rcu注册的回调函数。这时会kfree两次,造成double free。
注意:ip_mc_socklist obj 第二次free之前,由于kernel中的内存已经被释放了,正常不堆喷的情况下,可能会被其它的程序占位。在ip_mc_drop_socket中的ip_mc_leave_src中会对这个obj中的其它指针解引用,因此在ip_mc_leave_src中crash也是正常的。本质上crash的原因都是由于double free造成的。
了解了ip_mc_socklist obj 真正的释放过程后,就可以理解通过double free来控制PC的过程了。通常double free的利用有两种思路:一种是利用堆管理器的特性实现double free到代码执行,另一种就是通过占位把double free转化成UAF来使用。这里使用的是第二种。由于该漏洞的两次free的时机都是可控的,因此可以在第一次真正的kfree之后,通过堆喷射来占位释放的obj。
通过构造占位的obj中的数据,控制ip_mc_socklist.rcu中的func(回调函数地址)即可劫持PC或控制next_rcu将ip_mc_socklist链中的next_rcu劫持到用户态再修改func指针来劫持PC。
利用思路二中直接控制func:之前在ubuntu上动态调试时,我尝试在ip_mc_drop_socket的kfree_rcu之前用gdb手工修改内存中的func几十次后,发现依然不能劫持PC,感觉对于回调函数保存和触发过程还是不熟悉。
利用思路二中劫持next_rcu:由于不存在SMAP或者是PAN,劫持next_rcu到用户态后,就可以为所欲为了。做一个循环不停地占位func,可以保证回调函数触发时,func是我们控制的值。
漏洞分析过程还可以参考之前的文章。
在Jeremy的exploit中,还用到了bind_on_cpu这个函数。一开始不明白这个函数的作用,讲与之相关的调用删除后,有大概率会出现kernel page request error的log。
[virtual address 0c00011f
[ ] pgd = ffffffc00007d000
[ ] [0c00011f] *pgd=000000000e90b003, *pmd=00000000b3371003, *pte=02e00000a8469713
[ ] Internal error: Oops: 96000006 [
[ ] CPU: 0 PID: 3 Comm: ksoftirqd/0 Tainted: G W 3.10.73-g2c63035e12a8-00029-gc0f56dc0113e-dirty
[ ] task: ffffffc00e9a1580 ti: ffffffc00e9c0000 task.ti: ffffffc00e9c0000
[ ] PC is at rcu_process_callbacks+0x2ec/0x53c
[ ] LR is at rcu_process_callbacks+0x30c/0x53c
[ ] pc : [<ffffffc00029cafc>] lr : [<ffffffc00029cb1c>] pstate: 80000145
[ ] sp : ffffffc00e9c3ca0
[ ] x29: ffffffc00e9c3ca0 x28: 000000000c00011f
[ ] x27: ffffffc00e9c0000 x26: ffffffc0450d0648
[ ] x25: 0000000000000005 x24: 0000000000000003
[ ] x23: ffffffc0c0cccbd0 x22: ffffffc0010ff12e
[ ] x21: ffffffc0c0cccba8 x20: ffffffc00166fd4f
[ ] x19: ffffffc0017844c0 x18: 0000000000000000
[ ] x17: 0000007bc75fe360 x16: ffffffc00031c978
[ ] x15: 00000000004f1118 x14: 0ffffffffffffffe
[ ] x13: 0000000000000030 x12: 0101010101010101
[ ] x11: 7f7f7f7f7f7f7f7f x10: 0000000000000000
[ ] x9 : ffffffbc01148700 x8 : 0000000000000040
[ ] x7 : ffffffc0c4e4de80 x6 : 0000000000000004
[ ] x5 : 0000000000000000 x4 : 0000000000000001
[ ] x3 : 0000000000000101 x2 : 0000000000000101
[ ] x1 : ffffffc00e9c0000 x0 : 000000000c00011f
] Unable to handle kernel paging request at 最终,使用Jeremy的exploit劫持PC后,就可以开始后续利用了。
二、patch addr_limit
addr_limit代表当前thread可以访问的地址空间大小,如果patch了该thread的task_struct的addr_limit为-1,该thread就可以访问全部地址空间的数据了。目前了解的patch
addr_limit有两种方式。第一种在控制了一个寄存器和PC后,参考PXN的研究与绕过@莫灰灰的文章中,构造第一段jop来泄漏出SP到用户态。拿到SP,屏蔽低位后等于拿到了thread_union中的thread_info的地址,也就拿到了addr_limit的地址。用类似STR的指令将addr_limit的地址放在用户态后,再构造jop去patch
addr_limit。这要求我们可以控制PC两次,或者能一次jop完成这些操作。
在实际利用中,找了很久的gadget勉强可以泄漏出SP到用户态。如果想要控制一次PC的情况下就使用jop来完成SP的泄漏,计算addr_limit地址,patch addr_limit,也是可以的。只不过泄漏SP之后的jop比较难找,因此就转向了第二种方法。
第二种方法是360冰刃实验室在mosec2016提出的。利用了来自内核的馈赠:set_fs()。跟踪set_fs()的实现可以看到该函数完成了patch addr_limit的操作
在内核中有很多函数调用了set_fs(),执行一系列操作后会使用set_fs(oldfs)来恢复addr_limit,在恢复之前由于调用了函数指针,导致set_fs(oldfs)可以被绕过。
这样的函数有:kernel_setsockopt、kernel_sock_ioctl等。
具体来看一下kernel_setsockopt函数的反汇编代码,控制了X0后,就可以伪造X5的值,控制X5是0xFFFFFFC000B07120,就可以绕过set_fs(oldfs)实现对addr_limit的patch。patch了addr_limit后,用户态就可以实现任意读写内核态地址了。
用户态任意地址读写内核态需要借助pipe系统调用来实现。可以参考《Linux C一站式编程》感觉还是有点区别的,以后再研究。
三、搜索init_task中的struct list_head tasks
内核态任意地址读写后,为了实现找到exp进程完成patch工作,需要根据内核中的task_struct的双向链表来搜索到exp进程。init_task地址固定,内容在内核的二进制文件.data.rel段中,符号可以在/proc/kallsyms读到(需要有root权限)。当然也有非root就可以获取符号的方法,待补充。
struct list_head tasks在task_struct中定义,保存着当前task在task_struct双向链表中的前后节点。定义如下:
在定位tasks时,由于下面的成员pushable_tasks.prio在手机上总是0x8c,因此可以作为标记来在init_task地址处开始搜索。
四、搜索init和exp进程cred地址
由于我们最终需要的是init和exp进程的cred的地址来做patch,而cred是在task_struct结构体中是在tasks后面的,因此通过tasks.next可以搜索下一个task_struct中tasks后面的成员,根据comm值判断当前task_struct是谁的,找到exp和init的task_struct后,减sizeof(void )就可以得到对应的cred的地址。
在搜索comm时,根据cpu_timers中两个相同的指针 和 cred、*real_cred相等作为特征来搜索,可以定位到当前task_struct的comm。
五、patch exp cred
有了exp进程的cred后,借助pipe系统调用来patch cred中的成员即可。patch了这些成员后,调用system("/system/bin/sh")就可以获取root权限了。但是exp的context还不是init的context,仍然受到selinux的限制。
六、patch exp security and selinux
init和exp的cred地址我们都已经搜索到了,因此将cred中的security.osid和security.sid patch为init的即可。这时exp的context就是init的context了。
然后,patch selinux_enabled 和
selinux_enforcing 写0 即可关闭selinux。
七、system("/system/bin/sh");
利用效果:
当前的exp的context已经是init的了,比较神奇的时候调用某些命令,如id看到的context是toolbox的。
八、exploit
下载地址:
https://github.com/thinkycx/CVE-2017-8890/blob/master/nexus6p%40kernel-3.10/jni/exp.c
写exploit时遇到了很多问题,感谢geneblue前辈的指点。
还存在的问题
1、利用过程第二步patch addr_limit时不稳定。原因在于控制PC的过程是rcu机制用于释放对象的回调函数中触发的。回调函数是在linux kernel的软中断触发时调用,而软中断触发的场景有两个:正常硬件中断返回时触发软中断,Comm是exp;当内核中有大量软中断触发时,kernel为了保证用户态程序不要等太久,唤醒ksoftirqd线程来处理软中断,Comm是ksofriqrd。当Comm是ksoftiqrd时,调用回调函数控制PC后,无法patch exp的task_struct中的addr_limit,因此就不能完成内核任意地址读写,导致exp不稳定。
2、堆喷时,在kernel中喷射了大量的ipv6_mc_socklist 的obj来占位。控制PC时,exp中调用close对内核中用来占位的obj进行free了。在exp进程退出时,猜测这些大量的ipv6_mc_socklist obj还会free,因此占位的obj会被free两次,所以kernel就崩了,通过log也可以看到崩溃点是在ipv6相关的函数中。
参考
CVE-2017-8890漏洞分析与利用(Root Android 7.x)
http://www.freebuf.com/articles/terminal/160041.html
PXN的研究与绕过
https://blog.csdn.net/hu3167343/article/details/47394707
Android PXN绕过技术研究
https://geneblue.github.io/2016/07/27/Android%20PXN%E7%BB%95%E8%BF%87%E6%8A%80%E6%9C%AF%E7%A0%94%E7%A9%B6/
绕过PXN保护
http://blog.idhyt.com/2016/08/09/exploit-bypass-pxn/
https://github.com/dosomder/iovyroot/blob/master/jni/getroot.c
- End -
看雪ID:心许雪
https://bbs.pediy.com/user-730610.htm
本文由看雪论坛 心许雪 原创
转载请注明来自看雪社区
热门图书推荐:
(点击图片即可进入)
热门技术文章:
公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com