December 23, 2025

ALPC You Later: CVE-2025-64721 Sandbox Escape Smashing The Heap Over IPC

Mav Levin

Founding Security Researcher

The Matrix for Windows

Sandboxie is a technical marvel. Long before Microsoft shipped "Windows Sandbox" (which uses Hyper-V requiring VT-x hardware), Sandboxie was doing something arguably harder: software-only User-mode API Virtualization.

It creates a "Matrix" for applications. When a sandboxed virus.exe thinks it’s writing to C:\Windows\System32, Sandboxie’s kernel driver (SbieDrv.sys) and user-mode hook engine (SbieDll.dll) transparently redirect that call to a localized folder. To the process, the lie is perfect. The opcode executes, the syscall returns success, but the reality has been warped.

Maintaining this illusion requires a "God" process outside the matrix. Enter SboxSvc.exe. This service runs with elevated SYSTEM permissions, holding the keys to the real file system and registry. It acts as the "Responsible Adult" between sandboxed processes and the real computer resources.

The Nervous System: Raw ALPC

To understand the bug, you have to understand how the prisoner talks to the warden.

Standard Windows Inter-Process Communication (IPC) usually relies on RPC (Remote Procedure Call). RPC is safe, typed, and generates stubs that handle buffer sizes for you. But that safety and abstraction come at a performance cost.

The Wire Protocol

The communication channel is a named ALPC Port: \RPC Control\SbieSvcPort.

When a sandboxed process needs to do something privileged (like resolving a path or checking a password), SbieDll.dll constructs a raw C-struct, packs it into an ALPC message, and fires it into the port using NtRequestWaitReplyPort.

There is no IDL (Interface Definition Language). There is no type safety. There is only a raw stream of bytes and a custom header:

1typedef struct _MSG_HEADER {
2    ULONG length; // Total length of the packet
3    union {
4        ULONG msgid;  // The command (opcode)
5        ULONG status;
6    };
7} MSG_HEADER;

SboxSvc receives these packets, inspects the msgid, and dispatches them to handlers. It’s a classic "Fat Dispatcher" pattern.

Feature: "Secure" Parameters

Deep in the SbieIniServer (the component responsible for handling configuration), there is a message ID: MSGID_SBIE_INI_RC4_CRYPT (0x180F).

Its purpose is "simple machine-bound obfuscation" encrypting non-critical data (like cached passwords) using RC4 so they aren't sitting as cleartext in Sandboxie.ini.

The handler, RC4Crypt, takes an input buffer, encrypts it with RC4, and returns the transformed buffer.

Vulnerability: The 4GB "Oopsie" Also Known As CVE-2025-64721

Here is the handler code in Sandboxie/core/svc/sbieiniserver.cpp. The sandboxed process controls the raw msg struct content. Can you spot the bug?

1// From Sandboxie/core/svc/msgids.h
2typedef struct _MSG_HEADER {
3    ULONG length; // Total length of the packet
4    union {
5        ULONG msgid;  // The command (opcode)
6        ULONG status;
7    };
8} MSG_HEADER;
9
10// From Sandboxie/core/svc/sbieiniwire.h
11struct tagSBIE_INI_RC4_CRYPT_REQ /* same struct as tagSBIE_INI_RC4_CRYPT_RPL*/{
12    MSG_HEADER h;
13    ULONG value_len;
14    UCHAR value[1]; // Flexible array member
15};
16typedef struct tagSBIE_INI_RC4_CRYPT_REQ SBIE_INI_RC4_CRYPT_REQ;
17
18// From Sandboxie/core/svc/sbieiniserver.cpp
19MSG_HEADER *SbieIniServer::RC4Crypt(MSG_HEADER *msg, HANDLE idProcess, bool isSandboxed)
20{
21    SBIE_INI_RC4_CRYPT_REQ *req = (SBIE_INI_RC4_CRYPT_REQ *)msg;
22    
23    if (req->h.length < sizeof(SBIE_INI_RC4_CRYPT_REQ))
24        return SHORT_REPLY(STATUS_INVALID_PARAMETER);
25    ULONG rpl_len = sizeof(SBIE_INI_RC4_CRYPT_RPL) + req->value_len;
26    
27    SBIE_INI_RC4_CRYPT_RPL *rpl = (SBIE_INI_RC4_CRYPT_RPL *)LONG_REPLY(rpl_len);
28    if (!rpl) 
29        return SHORT_REPLY(STATUS_INSUFFICIENT_RESOURCES);
30    rpl->value_len = req->value_len;
31    
32    memcpy(rpl->value, req->value, req->value_len);
33    // ... RC4 logic ...
34    return rpl;
35}

Vulnerability Root Cause

Did you spot the bug? Look closely at line 23.

When auditing this handler, one detail stood out to me immediately. The code validates req->h.length, the total message size claimed by the header, against the size of the request struct. This prevents processing messages that are obviously too small.

However, it never validates req->value_len. It assumes that the length of the data payload (value_len) naturally fits inside the total message (h.length). But in raw ALPC, you control both fields independently. You can tell the service: "Here is a 20-byte message, and by the way, the payload inside it is 1 gigabyte."

This missing bounds check is the key to the kingdom. It grants you two distinct primitives:

  • Relative Heap Lead: By lying about the size, you can read past the end of our buffer.
  • Relative Heap Write: By (ab)using an Integer Overflow, you can write past the end of the allocation.

Reading Service Memory

Before you smash the heap, let's loot its data. While Sandboxie's unique software-only virtualization together with Windows virtual memory mechanics, mean shared DLLs (and thus our ROP gadgets) are at the same known addresses inside and outside the sandbox, the heap remains a randomized mystery. To leak heap data, you leverage the missing bounds check on value_len to perform an Out-of-Bounds Read via memcpy (line 34).

Setup

  1. You construct a tiny ALPC message (e.g., 20 bytes).
  2. You set req->h.length to 16
  3. You set req->value_len to 1000 bytes.
  4. Line 23 checks 16 < sizeof(REQ)? No. Proceed.
  5. Line 34 executes memcpy(dest, req->value, 1000).

Leak

req->value points to the ALPC message buffer in the service's heap. memcpy grabs our 4 bytes of payload, plus the next 996 bytes of whatever data is next on the heap. This can include sensitive pointers, vtables, and heap metadata.

Decrypt

The service encrypts this "stolen" memory and sends it back (line 36). Normally, this data would be unusable gibberish. However, simply pass that data back to the same API to decrypt it, free of charge.

You now have an insider view of the Service's memory.

Integer Overflow & Heap Obliteration

To trigger the Write Primitive you abuse the 32-bit arithmetic on line 26 to trick the allocator.

Math

  • You set req->value_len to your lucky number 4,294,967,281 (0xFFFFFFF1)
  • The calculation (line 26): sizeof(SBIE_INI_RC4_CRYPT_RPL) + value_len = 16 + value_len
  • The addition evaluates to 16 + 4,294,967,281 + 4,294,967,297 (0x100000001).
  • On Windows x64, a ULONG remains a 32-bit integer. When the addition 0xFFFFFFF1 + 0x10 occurs, the result is 0x100000001. However, because the destination variable is a 32-bit ULONG, the upper bit of the sum is truncated. The result wraps around, and the allocator sees a request for exactly 1 byte.

Allocation

The code calls LONG_REPLY(1) (line 28). Sandboxie uses a custom pool allocator that slices memory into 128-byte cells. When you ask for 1 byte, the allocator rounds up and hands you a valid, 128-byte heap chunk. The if (!rpl) check passes.

Heap Smash

You enter memcpy with rpl (the destination) pointing to a tiny 128-byte buffer. But the copy length is the original req->value_len before the truncated sum, 0xFFFFFFF1 bytes, which is 4 Gigabytes.

"Heap overflow" is an understatement here. This is Heap Obliteration. The copy operation relentlessly writes through the heap, overwriting metadata, objects, and vtables, continuing until it hits an unmapped page.

Exploitation: Memory Buzzer Beater

The immediate result of this excessive overflow is a crash (0xC0000005/STATUS_ACCESS_VIOLATION) on the first unmapped page, causing a Denial of Service for every sandboxed process on the system. To escape the sandbox and get code execution, you must survive or stop the 4GB copy.

You perform an acrobatic maneuver using two threads:

  1. Groom: You spray the heap to ensure a valuable C++ object (with a vtable) is allocated immediately after our 128-byte heap block you reserve for our ALPC packet.
  2. Prepare (Thread A): You spawn Thread A that endlessly loops, calling a benign function that uses our target object's vtable from step 1.
  3. Saboteur (Thread B): You send the malicious ALPC packet. The 4GB memcpy begins. It instantly overwrites the C++ object next door, corrupting its vtable.
  4. Intercept (Thread A): Thread A accesses the object one millisecond later. Instead of the real function, the corrupted vtable redirects execution to your ROP chain. Your first priority? Suspend the copying thread so you don't crash.

The memcpy freezes. The crash is averted. You have escaped the sandbox and now have SYSTEM-level access on the host.

It’s a high-stakes game of memory chicken. You have to hijack a car while it’s driving off a cliff, and pull the handbrake before it hits the bottom. This is left as an exercise for the reader.

Conclusion

Sandboxie leverages incredible low-level operating system internals to create a fast, secure, sandboxed environment. But attackers only need to find one critical vulnerability to cripple the defenses.

In this case, the reliance on manual C-style pointer arithmetic over a safe interface definition (like IDL) left a gap. A single missing integer overflow check, coupled with implicit trust in client-provided message lengths, turned the Responsible Adult into a victim.

Modern software vulnerabilities are often buried deep in assumptions, edge cases, and low-level implementation details. Depthfirst is built to go that deep (hence the name), helping find and fix vulnerabilities that traditional tools can’t.

Thank you to DavidXanatos from Sandboxie for patching this vulnerability within an hour of my report!

Timeline:

  • November 12, 2025 — Reported
  • November 12, 2025 — Patched
  • December 11, 2025 — Advisory Published
  • December 23, 2025 — Details Published

Disclaimer: This research was conducted on a local test environment. No 0-days were harmed in the making of this blog post.

Button Text

Secure your code to ship faster

Link your Github repo in three clicks.

Demo depthfirst now