evilsocket/jscythe: Abuse the node.js inspector mechanism in order to force any node.js/electron/v8 based process to execute arbitrary javascript code.
Posted by jpluimers on 2025/02/13
This is based on SIGUSR1, so means you need to run as the same user on the same local system, but it can be used for interesting techniques like extending node.js/electron based applications beyond what they were designed for.
[Wayback/Archive] evilsocket/jscythe: Abuse the node.js inspector mechanism in order to force any node.js/electron/v8 based process to execute arbitrary javascript code.
The behaviour has been documented and was known for a long time: [Wayback/Archive] sigusr1 node.js – Twitter Search.
It got my attention because of [Wayback/Archive] Simone Margaritelli on Twitter: “You can force any v8/Electron process to execute arbitrary js code (child_process, http, etc) by forcefully enabling and abusing the builtin debug mechanism … here’s VS Code executing Calc, but I suspect any Electron app is susceptible 🔥 it works with SIP enabled on macOS”
The combination of “SIP enabled on macOS” and “🔥” put a lot of people on the wrong foot as it kind of implicated it was a serious security vulnerability imposing a threat model.
Later they clarified: [Wayback/Archive] Simone Margaritelli on Twitter: “@addisoncrump_vr what’s the threat model for process injection? i was trying to work around SIP on macOS”.
On SIGUSR1
SIGUSR1 is one of the two user Signals that are used for Inter-Process Communication specifically for this purpose:
The SIGUSR1 and SIGUSR2 signals are sent to a process to indicate user-defined conditions.
It is, like other signals, supported by [Wayback/Archive] Process: signal-events | Node.js v18.8.0 Documentation, specifically:
'SIGUSR1'is reserved by Node.js to start the [Wayback/Archive] debugger. It’s possible to install a listener but doing so might interfere with the debugger.…
When
SIGUSR1is received by a Node.js process, Node.js will start the debugger.…
The signal used to trigger the creation of a diagnostic report. Defaults to
'SIGUSR2'.
Not just node.js does support SIGUSR1, Electron based applications do too as at the core they are node.js based..
Thread and responses
More messages in this [Wayback/Archive] Thread by @evilsocket on Thread Reader App including [Wayback/Archive] Simone Margaritelli on Twitter: “Source code -> github.com/evilsocket/jscythe“.
Others chimed in with links too, for instance:
- [Wayback/Archive] Christian on Twitter: “@evilsocket AFAIK, Electron patched this (if the app developer is opting-in) in this PR,
github.com/electron/electron/pull/33188, and is available on several recent versions.github.com/1Password/electron-hardenerandgithub.com/electron/fuseslet you toggle that and plug the hole :) This is great work/tools either way :)” / Twitter- [Wayback/Archive] fix: disable SIGUSR1 when –inspect is disabled by MarshallOfSound · Pull Request #33188 · electron/electron
Notes: SIGUSR1 is no longer handled when the
node_cli_inspectfuse is disabled - {Wayback/Archive] 1Password/electron-hardener: A fast and small Rust library to make Electron apps more secure.
A Rust library and command line tool to harden Electron binaries against runtime behavior modifications.
- [Wayback/Archive] electron/fuses
Flip Electron Fuses and customize your packaged build of Electron
- [Wayback/Archive] electron/fuses.md at main · electron/electron
Fuses … at a high level they are “magic bits” in the Electron binary that can be flipped when packaging your Electron app to enable / disable certain features / restrictions. Because they are flipped at package time before you code sign your app the OS becomes responsible for ensuring those bits aren’t flipped back via OS level code signing validation (Gatekeeper / App Locker).
- [Wayback/Archive] electron/fuses.md at main · electron/electron
- [Wayback/Archive] fix: disable SIGUSR1 when –inspect is disabled by MarshallOfSound · Pull Request #33188 · electron/electron
- [Wayback/Archive] actae0n on Twitter: “@evilsocket I have some example scripts here in the `scripts` folder if people are looking for things to do with this primitive. Used it in the past to pull Slack sessions. Some apps are blocking the command line switch, but the SIGUSR1 trick still works
github.com/xpcmdshell/electron-probe“- [Wayback/Archive] xpcmdshell/electron-probe
Electron-Probe leverages the Node variant of the Chrome Debugging Protocol to execute JavaScript payloads inside of target Electron applications. This allows an attacker to extract secrets and manipulate the application as part of their post-exploitation workflow.
- [Wayback/Archive] xpcmdshell/electron-probe
The result
I split this apart as there was no alt-text to the picture so I used Google Lens to OCR it which takes a bit more space to include above.
[Wayback/Archive] Simone Margaritelli on Twitter: “FINALLY!!!! Node.js universal API hooking on macOS with SIP enabled using jscythe injection 🥳 it’s interesting that by using the global Runtime object and targeting the extensionHost proces, this affects every extension running in VSCode … that’s all I wanted to achieve”
var HOOKED_FUNCTIONS = HOOKED_FUNCTIONS || {}; var FUNCTION_LOGS = FUNCTION_LOGS || []; function hook(mod, mod_name, fn_name, fn) { var fn_full_name = mod_name + '.' + fn_name; if(!(fn_full_name in HOOKED_FUNCTIONS) ){ mod[fn_name] = function(args) { var ret = fn(args); FUNCTION_LOGS.push({ timestamp: Math.floor(Date.now() / 1000), module: mod_name, func: fn_name, args: args, ret: ret, }); return ret; }; HOOKED_FUNCTIONS [fn_full_name] = true; } } var mod_name = 'fs'; for( var attr_name in require(mod_name) ) { var maybe_fn = require(mod_name)[attr_name]; if(typeof maybe_fn == 'function') { hook(require(mod_name), mod_name, attr_name, maybe_fn); } } JSON.stringify(HOOKED_FUNCTIONS)
--jeroen






Leave a comment