Discord Activity to Native RCE Chain: 17-Step Exploit Breakdown
The Discord Activity to Native RCE attack chain exploits a V8 type-confusion primitive, popup-policy manipulation, and Electron IPC hijacking to escalate from a sandboxed Activity page to arbitrary native code execution on Windows.
This deep dive examines the complete end-to-end exploit chain documented in the bikini/exploitarium repository, specifically the Discord Activity Stock Client RCE PoC. The attack targets Discord 1.0.9245 and achieves deterministic native code execution through a carefully orchestrated sequence moving from renderer compromise to full process control.
The 17-Step Exploit Chain Explained
The chain follows a linear progression through three distinct phases: renderer compromise, policy bypass, and native escalation. Each step builds on the previous, with no retries or branching allowed—failure at any stage aborts the entire attack.
Phase 1: Initial Activity Load and Primitive Construction (Steps 1–6)
Steps 1–6 establish the foundation inside Discord's sandboxed Activity environment.
Step 1: Activity Loads via Discord Proxy
Discord's proxy forwards the mapped URL https://<APP_ID>.discordsays.com to the local server, loading the malicious Activity inside a sandboxed Chromium renderer. This occurs in exploit.html as the entry point.
Step 2: Instance Registration and Lease Acquisition
The Activity generates a random per-session instance ID and requests a one-shot lease from server.js. The server validates and returns a cryptographically random lease token:
// From server.js - lease generation
const lease = crypto.randomUUID();
activeLeases.set(lease, { instance, token: crypto.randomUUID() });
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ lease }));
Step 3: Popup-Policy Worker Deployment
The server responds with the popup-policy worker script and prefetches the second-stage document containing the native payload. This worker survives navigation and maintains execution context across page transitions.
Step 4: CSP Nonce Propagation
Discord injects a fresh Content-Security-Policy nonce for every response. The Activity copies this nonce onto every script in the prefetched document, ensuring scripts execute after the nonce rotation—a critical reliability measure.
Step 5: V8 Type-Confusion Primitive
The first renderer forces V8 into a type-confusion code path, constructing an in-cage arbitrary read/write primitive. This operates within V8's compressed-pointer cage but provides sufficient capability for subsequent stages.
Step 6: Payload-Driven Discovery and Trampoline Installation
Using the primitive, the exploit:
- Recovers the V8 cage base address
- Locates the generated-function dispatch entry
- Identifies the native marker
- Installs a compact native trampoline for future code execution
Phase 2: Popup-Policy Bypass and Auxiliary Window (Steps 7–9)
Steps 7–9 break out of the single-window restriction, enabling multi-renderer coordination essential for the final payload.
Step 7: Popup-Policy Binary Patching
The worker modifies the popup-policy instructions in-memory:
- Flips the auxiliary-window branch condition to always true
- Fixes sandbox-flag propagation to maintain renderer integrity
// Client-side memory manipulation in exploit.html
writeMemory(popupPolicyAddr, patchedBytes);
flushInstructionCache(popupPolicyAddr, patchedBytes.length);
restorePageProtections(popupPolicyAddr);
Step 8: Auxiliary Window Creation
With the patched policy, window.open() now succeeds. The Activity:
- Stops its initial navigation
- Writes the nonce-adjusted second-stage document into the initial document
- Spawns the auxiliary renderer with full second-stage payload
Step 9: Second Renderer Rebuilds Primitive
The second renderer repeats the primitive construction from scratch. Crucially, it now discovers Discord's Electron IPC implementation in the loaded image—information unavailable to the first renderer due to initial sandbox restrictions.
Phase 3: Native Escalation and Execution (Steps 10–17)
Steps 10–17 execute the final native payload and restore system state.
Step 10: Native IPC Payload Construction
The second-stage renderer builds three Electron IPC calls using a generated Win64 payload:
| IPC Call | Purpose |
|---|---|
DISCORD_SETTINGS_SET("WEBAPP_ENDPOINT", <ACTIVITY_PROXY_ORIGIN>) |
Redirects Discord's webapp endpoint to attacker-controlled origin |
DISCORD_SETTINGS_SET("WEBAPP_PATH", "/native-proof") |
Sets the path for the native payload endpoint |
DISCORD_APP_RELAUNCH() |
Triggers Discord process restart with new settings |
Step 11: Async Persistence Delay
The payload inserts an explicit ~3 second delay before the relaunch IPC. This ensures asynchronous settings writes fully persist to disk before process termination.
Step 12: Relaunch to Temporary Endpoint
Discord launches a new main renderer for the temporary endpoint https://<ACTIVITY_PROXY_ORIGIN>/native-proof, requesting it from the Activity proxy server.
Step 13: Native Stage Loads
The native page executes the engine stage again, this time with different objectives:
- Resolves
CreateProcessW,Sleep, andExitProcessfrom the stock Discord binary image - Allocates fresh executable memory for the final payload
Step 14: Calculator Process Launch
The native trampoline executes the final payload:
; Generated in-memory trampoline (conceptual)
mov rax, [CreateProcessW_address]
lea rcx, [calc_path] ; L"C:\\Windows\\System32\\calc.exe"
xor rdx, rdx ; lpCommandLine = NULL
xor r8, r8 ; lpThreadAttributes = NULL
xor r9, r9 ; lpProcessAttributes = NULL
mov [rsp+20h], 0 ; dwCreationFlags = 0
call rax ; CreateProcessW(...)
Step 15: Sentinel-Based Completion Signaling
The compromised renderer exits with a distinct sentinel value. A fresh renderer detects this sentinel, reports successful native execution, and enters a recovery-wait state.
Step 16: Settings Restoration
run.ps1 executes the cleanup sequence:
- Verifies the
settings.jsonbackup via SHA-256 hash - Restores original Discord configuration
- Writes recovery signal for coordination
Step 17: Final Discord Relaunch
The verification page triggers the final Discord relaunch on the original endpoint, returning the client to pristine state with proof-of-completion logged.
Core Mechanisms Enabling the Chain
The exploit succeeds through three interacting primitives that bypass progressively stronger security boundaries.
V8 Renderer Memory Primitive
The type-confusion vulnerability provides arbitrary read/write within V8's compressed-pointer cage. According to the bikini/exploitarium source code, this enables:
- Discovery of native library addresses
- In-memory code generation and trampoline installation
- Cross-renderer state coordination
This primitive is constructed identically in both renderers but applied differently based on available capabilities.
Activity Popup-Policy Manipulation
Discord's Activity sandbox restricts window.open() to prevent multi-window attacks. By patching the conditional branch that enforces this restriction, the attacker gains:
- Ability to spawn auxiliary renderers
- Clean separation between primitive construction and IPC discovery phases
- Survival of worker context across navigation
The patch modifies executable code in the renderer process—requiring the memory primitive from Step 5 and careful instruction cache management.
Electron IPC Bridging
Discord's Electron integration exposes privileged IPC channels normally inaccessible to web content. The second-stage renderer leverages this to:
- Mutate persistent Discord settings (
settings.json) - Force application relaunch with attacker-controlled endpoint
- Execute native payload in a fresh renderer with full process privileges
This represents the critical trust boundary crossing from renderer compromise to native code execution.
Key Source Files and Their Roles
| File | Primary Function | Critical Code Elements |
|---|---|---|
server.js |
One-shot orchestration server | Lease generation, popup-policy worker serving, /native-proof endpoint |
exploit.html |
Dual-stage renderer payload | Primitive construction, policy patching, IPC payload generation |
wasm-module-builder.js |
Native payload assembly helper | WebAssembly bytecode construction for in-memory code generation |
run.ps1 |
Environment preparation and cleanup | Binary validation, settings backup/restore, execution coordination |
All files operate under a strict one-shot execution model enforced by server.js—the lease system prevents replay or reuse of any stage.
Summary
- The Discord Activity to Native RCE chain requires 17 deterministic steps moving from sandboxed web content to arbitrary Windows process execution
- Three core primitives enable the attack: V8 memory corruption, popup-policy binary patching, and Electron IPC hijacking
- The exploit uses dual renderers—the first escapes sandbox restrictions, the second discovers and abuses privileged IPC channels
run.ps1guarantees clean execution by backing up and verifyingsettings.jsonvia cryptographic hash before and after the proof- All coordination flows through
server.js, which enforces single-use leases and serves stage-specific payloads
Frequently Asked Questions
What Discord version does this exploit target?
This proof-of-concept targets Discord 1.0.9245 specifically. The exploit depends on exact binary layouts for V8 type-confusion offsets, popup-policy branch locations, and Electron IPC method addresses. Later Discord versions may have patched the underlying V8 vulnerability or modified the vulnerable code paths.
Why does the chain need two separate renderers?
The first renderer operates under strict Content Security Policy and sandbox restrictions that prevent direct access to Electron IPC channels. By patching the popup policy and opening an auxiliary window, the second renderer loads with different initialization state that exposes the Discord-native IPC implementation. This two-stage design cleanly separates privilege escalation from capability discovery.
How does the exploit persist the malicious endpoint?
The attack uses Discord's own settings persistence mechanism through three IPC calls: DISCORD_SETTINGS_SET for the endpoint and path, followed by DISCORD_APP_RELAUNCH. The ~3 second delay ensures these asynchronous writes complete to disk before the process terminates. Discord's standard relaunch behavior then loads the attacker-controlled endpoint as if it were legitimate configuration.
What prevents this exploit from working on patched systems?
Three remediation angles exist: V8 patches that close the type-confusion primitive, popup-policy hardening that prevents runtime modification or removes the auxiliary window restriction entirely, and IPC access controls that restrict renderer access to privileged settings channels. Discord has likely addressed one or more of these vectors in subsequent releases.
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 →