From context_handle to type confusion: A Type Confusion Pattern in Windows RPC Servers

From context_handle to type confusion: A Type Confusion Pattern in Windows RPC Servers

Original text: “From context_handle to type confusion”k0shl, Whereisk0Shl (Jun 26, 2026). Code blocks and crash dumps below are reproduced verbatim with attribution captions.
Conceptual cybersecurity illustration for Windows RPC type-confusion vulnerabilities
Windows RPC context-handle type confusion. Illustrative image (Wikimedia Commons), not from the original article.

Executive Summary

Windows RPC servers routinely expose several different context-handle types within the same interface — for example a generic open/close handle alongside a typed object handle. A context handle is essentially an index that rpcrt4 resolves to a memory region holding an object, much like an object reference inside the OS. The recurring question k0shl explores is deceptively simple: what happens if you hand an object of one context-handle type to an interface that expects a different one? When the server does not validate, the answer is type confusion — the wrong object is dereferenced and operated on as if it were the expected type.

The root cause turns out to live in the NDR64 context-handle descriptor. rpcrt4 distinguishes two relevant descriptor types: FC64_SUPPLEMENT (0x75) and FC64_BIND_CONTEXT (0x70). For SUPPLEMENT handles, the runtime validates the context ID during unmarshalling and throws exception 0x6 (invalid_handle) on a mismatch. For BIND_CONTEXT handles, the runtime simply indexes the object and passes it straight into the interface — so the only thing standing between an attacker and type confusion is whatever explicit type check the interface author remembered to add. This post traces that pattern from a first arbitrary-handle-close primitive (CVE-2025-48815) to two further crashes in MSMQ and the Windows Firewall service (CVE-2025-53143 and CVE-2025-54104).

A Type Confusion Vulnerability Pattern in Windows RPC Servers

This class of issue generally arises from insufficient constraints in IDL definitions combined with a lack of proper validation inside the RPC interface itself. The investigation started in early 2025 while auditing the attack surface of Windows HTTP services. The ssdpsrv component hosts not only an HTTP service but an RPC server too, and that RPC server exposes several distinct context-handle types — such as CONTEXT_HANDLE_TYPE and SYNC_HANDLE_TYPE — each managed by a different interface:

_SSDPOpenRpc
_SSDPCloseRpc

_InitializeSyncHandle
_RemoveSyncHandle

Context handles are ubiquitous in RPC and behave like indices: in rpcrt4 a context handle maps to a specific memory region that usually stores an object, mirroring how object references work inside Windows. That observation prompts the core idea — since a context handle is just an index into a memory region, what would happen if a CONTEXT_HANDLE_TYPE object were passed into an interface that expects a SYNC_HANDLE_TYPE?

CVE-2025-48815

Inside ssdpsrv!_RemoveSyncHandle, the sync handle is a handle value opened by the InitializeSyncHandle interface, which creates a semaphore object:

__int64 __fastcall InitializeSyncHandle(_QWORD *a1)
{
  [...]
  Semaphore = CreateSemaphoreExW(0i64, 0, 0x7FFFFFFF, 0i64, 0, 0x1F0003u);
  [...]
  *a1 = v4;
  return 0i64;
}

_RemoveSyncHandle then simply calls CloseHandle on that value:

BOOL __fastcall RemoveSyncHandle(void **a1)
{
  [...]
    result = CloseHandle(v1);
    *a1 = 0i64;
  [...]
}

However, passing a context handle returned by _SSDPOpenRpc into RemoveSyncHandle still succeeds — even though that context points to a CONTEXT_HANDLE object rather than a sync handle. This is a straightforward type-confusion case. Notably, the handle returned by _SSDPOpenRpc actually points to the global variable g_lSsdpRef, which tracks a reference count:

__int64 __fastcall SSDPOpenRpc(_QWORD *a1)
{
  [...]
      ++g_lSsdpRef;
      *a1 = &g_lSsdpRef;
  [...]
}

By repeatedly calling _SSDPOpenRpc, the returned context handle can be driven to point at a reference-count value that matches any small integer handle value — e.g. 0x1C4 or 0x200. When that value is passed to _RemoveSyncHandle, ssdpsrv ends up closing an arbitrary handle accessible in the process context.

FC_SUPPLEMENT and FC_BINDING_CONTEXT

If many RPC servers expose multiple context-handle types within one interface, why does ssdpsrv allow cross-type usage while others reject it with exception 0x6 (invalid_handle)? Answering that required reverse-engineering the RPC runtime. A useful trick: exceptions inside RPC servers don’t break into the debugger by default, so to catch exception 0x6 at the moment it is thrown, use:

sxe 00000006

Breaking there and walking the call stack leads to NDRSContextUnmarshall2. Rather than analyze all of RPCRT4 in depth, a shortcut helps: set a breakpoint on the same function inside ssdpsrv and compare against the other target server. Surprisingly, the breakpoint never fired in ssdpsrv — meaning the root cause was not in that function. Comparing the surrounding logic outward reveals the key difference: in the IDL of ssdpsrv the context-handle descriptor begins with 0x70, while in the other server it is 0x75:

.rdata:000000018003AE04   db 70h
.rdata:000000018003AE05   db 0EDh

.rdata:0000000140017850   db 75h
.rdata:0000000140017851   db 70h

This hints that different types exist among context handles. The type definitions are documented in colleague victorv’s blog (v-v.space/2023/09/06/rpc_readme), which also explains in detail how to identify RPC parameter types — recommended reading. There, 0x75 represents SUPPLEMENT and 0x70 represents BIND_CONTEXT:

FC64_BIND_CONTEXT = 112 0x70
FC64_SUPPLEMENT = 117 0x75

With those descriptor types in mind, the runtime behavior becomes clear. When the incoming context handle is of type SUPPLEMENT, rpcrt4 validates whether the context ID matches during unmarshalling and throws exception 0x6 on a mismatch. When the handle is of type BIND_CONTEXT, rpcrt4 directly indexes the corresponding object and passes it into the RPC interface — with no runtime ID check.

From binding_context to type confusion

From here the hunt becomes simpler: find an RPC server that exposes multiple context handles under one interface and you have a set of candidate attack surfaces. James Forshaw’s NtObjectManager (part of the sandbox-attacksurface-analysis-tools) parses an RPC server’s IDL and clearly distinguishes FC_BINDING_CONTEXT from FC_SUPPLEMENT, so the filtering reduces to finding interfaces that contain multiple occurrences of "[out] /* FC_BINDING_CONTEXT */".

CVE-2025-53143

Applying the pattern to the MSMQ queue manager (MQQM) yields a crash in transaction handling. The register state at the fault:

0:034> r
rax=00000283529a8f50 rbx=00000283529a8f50 rcx=00007ffe440edb60
rdx=0000000000000001 rsi=00007ffe440eba28 rdi=0000000000000001
rip=00007ffe44027373 rsp=000000a4c2c7e9d0 rbp=0000000000000011
 r8=7ffffffffffffffc  r9=000000a4c1bb1000 r10=0000000000000000
r11=000000a4c2c7e830 r12=0000000000000001 r13=000000000000000e
r14=000000a4c2c7ee90 r15=0000000000000000
iopl=0         nv up ei pl nz na pe nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010202
MQQM!CTransaction::StartPrepareRequest+0x5b:
00007ffe`44027373 834b1420        or      dword ptr [rbx+14h],20h ds:00000283`529a8f64=???????? 

And the call stack, reaching the transaction code through the RPC runtime and QMCommitTransaction:

0:034> k
 # Child-SP          RetAddr               Call Site
00 000000a4`c2c7e9d0 00007ffe`440265e6     MQQM!CTransaction::StartPrepareRequest+0x5b
01 000000a4`c2c7ea10 00007ffe`44037e01     MQQM!CTransaction::InternalCommit+0x62
02 000000a4`c2c7ea40 00007ffe`43ff81ab     MQQM!QMDoCommitTransaction+0xcd
03 000000a4`c2c7ea70 00007ffe`43fc5685     MQQM!qmcomm_v1_0_S_QMCommitTransaction+0xb
04 000000a4`c2c7eaa0 00007ffe`68113882     RPCRT4!Ndr64StubWorker+0x862
05 000000a4`c2c7ead0 00007ffe`680c599c     RPCRT4!NdrServerCallAll+0x3c
...

CVE-2025-54104

The same pattern reaches the Windows Firewall service (mpssvc / fwbase) via FWUnregisterProduct, crashing in a string copy:

0:013> r
rax=0000000000000000 rbx=0000000002000200 rcx=00007fffe5104ad8
rdx=00000080007fe188 rsi=00000080007fe188 rdi=0000008001ff9b10
rip=00007fffe50c3943 rsp=00000080007fe120 rbp=00000080007fe1f0
 r8=00000080007fe088  r9=0000000000000000 r10=0000000000000000
r11=0000000000000246 r12=0000000000000000 r13=0000008001ff9c78
r14=00007fffe5104000 r15=0000008001ff9c60
iopl=0         nv up ei pl zr ac po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010256
fwbase!FwStringCopy+0x53:
00007fff`e50c3943 66833c4300      cmp     word ptr [rbx+rax*2],0 ds:00000000`02000200=???? 

Its call stack:

0:013> k
 # Child-SP          RetAddr               Call Site
00 00000080`007fe120 00007fff`d90fd4cd     fwbase!FwStringCopy+0x53
01 00000080`007fe160 00007fff`d90f253b     mpssvc!SvrImpl_FWUnregisterProduct+0x4d
02 00000080`007fe1b0 00007fff`e983de83     mpssvc!RPC_FWUnregisterProduct+0x9b
03 00000080`007fe1e0 00007fff`e9841e83     RPCRT4!Invoke+0x73
...

This kind of vulnerability is not universally present in RPC services. When an IDL defines a context handle as BIND_CONTEXT, many interfaces do perform explicit validation — for example checking the object type embedded in the object value. Type confusion becomes possible only when that validation is missing.

Key Takeaways

  • Many Windows RPC servers expose multiple context-handle types under a single interface — a recurring type-confusion attack surface.
  • rpcrt4 treats two NDR64 descriptors differently: FC64_SUPPLEMENT (0x75) is ID-validated at unmarshall time; FC64_BIND_CONTEXT (0x70) is indexed directly with no runtime check.
  • For BIND_CONTEXT handles, the only defense is an explicit type check inside the interface function; omit it and a cross-type handle is dereferenced as the wrong object.
  • In ssdpsrv (CVE-2025-48815), a context handle pointing at the g_lSsdpRef reference counter can be groomed to any small integer, turning _RemoveSyncHandle into an arbitrary CloseHandle primitive.
  • The same pattern produced crashes in MSMQ’s MQQM (CVE-2025-53143) and the Windows Firewall service mpssvc/fwbase (CVE-2025-54104).
  • Tooling matters: sxe 00000006 catches the otherwise-silent RPC exception, and NtObjectManager filters IDLs for multiple FC_BINDING_CONTEXT outputs to surface candidates quickly.

Defensive Recommendations

  • Validate object type inside every BIND_CONTEXT interface. Never assume rpcrt4 has checked it — for BIND_CONTEXT handles it has not. Verify a type tag stored in the object before using it.
  • Prefer SUPPLEMENT-style context handles where the design allows, so the runtime enforces context-ID matching and rejects cross-type handles with exception 0x6.
  • Tighten IDL constraints. Avoid exposing several interchangeable context-handle types on one interface unless each consumer strictly validates the type it receives.
  • Audit existing servers with NtObjectManager: flag interfaces with multiple [out] /* FC_BINDING_CONTEXT */ handles and review each consumer for a missing type check.
  • Treat handle/object pointers from RPC as untrusted indices. Bounds- and type-check before dereferencing; never feed them straight into CloseHandle or pointer writes.
  • Add fuzzing for cross-type handle passing — deliberately submit a handle from interface A to interface B and watch for exception 0x6 absence, which signals an unvalidated BIND_CONTEXT path.
  • Run RPC services at least privilege and apply Microsoft’s patches for CVE-2025-48815, CVE-2025-53143 and CVE-2025-54104.

Conclusion

The throughline is a single design fact with outsized consequences: rpcrt4 only auto-validates SUPPLEMENT context handles, while BIND_CONTEXT handles are indexed and handed to the interface unchecked. Where the interface author adds an explicit object-type check, the design is safe; where they don’t, a context handle of one type can be smuggled into code expecting another, yielding anything from an arbitrary-handle close to memory corruption. Auditing for multiple BIND_CONTEXT handles under one interface — and confirming each consumer validates the type it receives — is the practical way to find and close this class of bug.

References

Original text: “From context_handle to type confusion” by k0shl at Whereisk0Shl.

Comments are closed.