December 23, 2025

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.
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 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.
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.
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}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:
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).
req->h.length to 16req->value_len to 1000 bytes.16 < sizeof(REQ)? No. Proceed.memcpy(dest, req->value, 1000).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.
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.

To trigger the Write Primitive you abuse the 32-bit arithmetic on line 26 to trick the allocator.
req->value_len to your lucky number 4,294,967,281 (0xFFFFFFF1)sizeof(SBIE_INI_RC4_CRYPT_RPL) + value_len = 16 + value_len16 + 4,294,967,281 + 4,294,967,297 (0x100000001).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.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.
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.
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:
memcpy begins. It instantly overwrites the C++ object next door, corrupting its vtable.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.
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:
Disclaimer: This research was conducted on a local test environment. No 0-days were harmed in the making of this blog post.

Secure your code to ship faster
Link your Github repo in three clicks.