How the Discord Activity RCE Exploit Works: From V8 Memory Corruption to Native Code Execution
The Discord Activity RCE exploit chains a V8 heap corruption primitive with an Electron IPC abuse vector to execute arbitrary native code, ultimately calling CreateProcessW to launch Windows binaries from within a sandboxed Activity.
The bikini/exploitarium repository demonstrates a proof-of-concept that escapes the Discord Activity sandbox and achieves remote code execution (RCE) on the host system. This exploit targets the Discord desktop client's renderer process, leveraging memory corruption in the V8 JavaScript engine to inject a payload that hijacks Electron's inter-process communication (IPC) mechanism.
Exploit Chain Architecture
The attack consists of nine logical components implemented across server.js and exploit.html. The infrastructure begins with an HTTP tunnel that provisions the malicious Activity, while the client-side code executes a multi-stage memory corruption and IPC injection sequence.
Activity Provisioning and Protocol Handshake
The attacker operates a local HTTP server defined in server.js that listens on 127.0.0.1:3077 by default. This server implements the Discord Activity protocol handshake through two critical endpoints: /protocol/register and /protocol/lease (lines approximately 260-170). These endpoints generate a lease token that authorizes the Activity to request privileged actions later in the exploit chain.
Stage 1 — V8 Memory Primitives and Sandbox Bypass
Inside exploit.html, the buildPrimitive() function (approximately line 360) constructs a WebAssembly-based addrof/fakeobj primitive. This yields arbitrary read/write capabilities (read64, write64) on the V8 heap. The exploit uses these primitives in installElectronIpcPayload (lines approximately 1150-1200) to overwrite the auxiliary-window flag within the renderer's V8 object. Flipping this flag allows the Activity to open a second renderer window that shares the same process, breaking the sandbox isolation.
Stage 2 — Electron IPC Abuse and Native Payload Delivery
With the auxiliary window enabled, the exploit crafts a malicious Electron IPC message. The installElectronIpcNativeInvokePayload function (lines approximately 1270-1360) resolves internal Discord/Electron function addresses (ipcCreate, invoke, etc.) and assembles a Return-Oriented Programming (ROP) chain. This chain culminates in a call to CreateProcessW with the path to the target executable, defaulting to C:\Windows\System32\calc.exe.
Persistent Endpoint Injection
The crafted IPC message also modifies Discord's internal settings. It writes a temporary web-application endpoint (/native-proof) into WEBAPP_ENDPOINT and triggers DISCORD_APP_RELAUNCH. The relaunch logic, handled in the /rce-stage2 endpoint (lines approximately 998-1030), ensures Discord reloads the Activity from the attacker-controlled endpoint, placing the native payload inside the main renderer process.
Native Execution Verification
Once the relaunched renderer executes the native payload at /native-proof (lines approximately 236-260), the code verifies successful execution by checking DiscordNative.processUtils.getLastCrash().rendererCrashExitCode. A value of 0x51a71338 signals successful native execution. The payload itself resolves CreateProcessW via PE parsing in resolveImport (lines approximately 800-820) and invokes it through a memory stub generated at markerAddr (lines approximately 1110-1130).
Step-by-Step Execution Flow
-
Start the proof-of-concept server:
export ACTIVITY_RCE_SPAWN_PATH="C:\\Windows\\System32\\calc.exe" node server.js -
Configure the Discord Activity in the Developer Portal to point to your tunnel URL (e.g.,
https://<APP_ID>.discordsays.com). -
Register a protocol instance:
curl -s "http://127.0.0.1:3077/protocol/register?generation=abcd1234&instance=myinst" -
Obtain a lease token for stage 2:
curl -s "http://127.0.0.1:3077/protocol/lease?generation=abcd1234&instance=myinst"This returns a JSON object:
{"status":"granted","token":"<random-hex>","attempt":1,"expiresAt":...} -
Trigger the stage 2 payload delivery:
curl -G "http://127.0.0.1:3077/rce-stage2" \ --data-urlencode "instance=myinst" \ --data-urlencode "token=<token>" \ --data-urlencode "origin=https://myapp.discordsays.com" -
Discord relaunches, executes the native proof, and spawns the configured process.
Key Source Files and Functions
server.js: Implements the HTTP server, protocol handshake endpoints (/protocol/register,/protocol/lease), and the/rce-stage2payload generation handler.exploit.html: Contains the V8 heap exploitation logic,buildPrimitive(), and the auxiliary window bypass viainstallElectronIpcPayload.installElectronIpcNativeInvokePayload: Constructs the ROP chain and IPC message for native code execution in the main Discord process.resolveImport: Parses PE headers to locateCreateProcessWorExitProcessin the host module.
Environment Configuration
The proof-of-concept supports several environment variables to customize the attack:
ACTIVITY_RCE_SPAWN_PATH: Path to the executable spawned by the payload (default:calc.exe).ACTIVITY_PROTOCOL_LEASE_MS: Duration of the protocol lease token validity.ACTIVITY_PUBLIC_ORIGIN: Collector origin for verification callbacks reporting the 0x51a71338 success code.
Summary
- The Discord Activity RCE exploit combines V8 memory corruption with Electron IPC abuse to escape the sandbox and execute native code.
- Arbitrary read/write primitives in
exploit.htmlenable the auxiliary-window policy bypass required to access the IPC channel. - The
installElectronIpcNativeInvokePayloadfunction crafts a ROP chain that ultimately callsCreateProcessWwith attacker-controlled arguments. - Environment variables like
ACTIVITY_RCE_SPAWN_PATHcontrol the final payload execution without recompiling the exploit. - Success is verified by checking for the specific exit code 0x51a71338 in the renderer crash log returned by
DiscordNative.processUtils.getLastCrash().
Frequently Asked Questions
What makes the Discord Activity RCE exploit possible?
The exploit exists because Discord Activities run in a V8 renderer process that can corrupt memory to access the same-process IPC channel used by the main Electron application. By using the buildPrimitive() addrof/fakeobj gadget to enable auxiliary windows, the attacker gains access to unsandboxed native code execution through the CreateProcessW Windows API call, as implemented in the bikini/exploitarium repository.
How does the V8 memory primitive work in this exploit?
The buildPrimitive() function in exploit.html (line ~360) uses WebAssembly to construct an addrof/fakeobj gadget pair. This provides read64 and write64 operations that can modify internal V8 flags. Specifically, the installElectronIpcPayload function (lines ~1150-1200) targets the auxiliary-window policy flag located at a calculable offset from heap objects, enabling the second renderer window required for IPC access.
What is the purpose of the 0x51a71338 exit code?
According to the source code in server.js (lines ~236-260), the value 0x51a71338 serves as a magic number indicating successful native payload execution. The /native-proof page checks DiscordNative.processUtils.getLastCrash().rendererCrashExitCode for this specific value to confirm that the ROP chain successfully invoked CreateProcessW before reporting RCE_VERIFY_SUCCESS to the attacker's collector endpoint.
Can this exploit work without user interaction in Discord?
No, the exploit requires the victim to actively launch the malicious Activity from a Discord channel by clicking the Activity's Launch button. However, once launched, the entire chain—from V8 corruption to native code execution—proceeds automatically without additional user consent, leveraging the trusted nature of the Discord client to execute the staged payloads in exploit.html and the stage 2 IPC injector.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →