1. Overview

  • Unpatched Binary: msrpc_unpatched.sys
  • Patched Binary: msrpc_patched.sys
  • Overall Similarity Score: 0.6907
  • Diff Statistics:
  • Matched Functions: 1000
  • Changed Functions: 628
  • Identical Functions: 372
  • Unmatched Functions (Unpatched/Patched): 0 / 0

Verdict: No security-relevant change is delivered in the NDR MES (Marshalling-Enabled Serialization) type-pickling functions. The apparent difference in NdrMesTypeDecode, NdrMesTypeDecode2, and NdrMesTypeAlignSize is a refactor: validation that the unpatched build performs inside helper functions (NdrpCommonTypeHeaderUnmarshall, NdrpCommonTypeHeaderSize) is inlined into the callers in the patched build, and the format-string dispatch changes from a single indirect call to direct calls for a few common handlers. The same validation and the same raised exceptions exist in BOTH builds. The rest of the 628 changed functions is dominated by a general toolchain/library rebuild (WIL feature-staging helpers, integer-overflow helper renames, memset intrinsic variants, Control Flow Guard dispatch thunks), not a targeted memory-safety fix.


2. Change Summary

Finding: MES type-header validation is inlined, not added

  • Severity: None (no security-relevant change)
  • Nature: Refactor / inlining + module rebuild
  • Affected Functions: NdrMesTypeDecode (unpatched 0x1C0025E30, patched 0x18003A3E0), NdrMesTypeDecode2 (unpatched 0x1C0026000, patched 0x180039ED0), NdrMesTypeAlignSize (unpatched 0x1C0034830, patched 0x180039670)

What actually changed: In the unpatched build, NdrMesTypeDecode validates the MES handle and format-string header by calling the helper NdrpCommonTypeHeaderUnmarshall (0x1C0025F68). That helper contains, in order: - cmp dword ptr [rcx+190h], 1 / jnzRpcRaiseException(0x723) — handle-state check - when [rcx+1A1h] != 0: cmp [rcx+1C8h], 8 / jbRpcRaiseException(0xC000000D) — minimum buffer-size check for non-fixed-buffer handles - test dword ptr [rcx+1A0h], 40000h / jzNdrpReadPicklingBuffer, then cmp byte ptr [rdx], 1 / jnzRpcRaiseException(0x724) — format-string version check

In the patched build these same three checks, with the same offsets (+0x190, +0x1C8, +0x1A0) and the same exception codes (0x723, 0xC000000D, 0x724), are inlined directly into NdrMesTypeDecode (0x18003A4300x18003A65D). The helper NdrpCommonTypeHeaderUnmarshall still exists in the patched build (0x18003A834) and is still called from the patched NdrMesTypeDecode2. No new check is introduced; the guard set is identical across both builds.

The only literal constant that differs is the size cap on the handle field at +0x4C: - Unpatched: cmp dword ptr [rdi+4Ch], 0A0000h / jaExRaiseStatus(0x725) - Patched: cmp dword ptr [rdi+4Ch], 0A000Ch / jaExRaiseStatus(0x725)

Because the comparison raises when the field is strictly greater than the constant, and the patched constant (0xA000C) is 12 larger than the unpatched constant (0xA0000), the patched build permits a slightly larger value than the unpatched build. This is a marginal relaxation of a size cap, not a security hardening, and there is no demonstrable memory-safety consequence in either direction.

The format-string dispatch also changed shape. The unpatched NdrMesTypeDecode special-cases only NdrPointerUnmarshall and otherwise performs a single guarded indirect call (call rax). The patched build additionally special-cases NdrNonEncapsulatedUnionUnmarshall, NdrSimpleStructUnmarshall, and NdrComplexStructUnmarshall with direct calls (_guard_dispatch_icall). The dispatch table lookup and the set of reachable handlers are unchanged; this is a Control Flow Guard / call-site devirtualization refactor, not a change in what is parsed or how bounds are enforced.

NdrMesTypeAlignSize follows the identical pattern: the unpatched build calls NdrpCommonTypeHeaderSize (0x1C00347AC), which already contains cmp dword ptr [rcx+190h], 0 / jnzRpcRaiseException(0x723) and the [rcx+1A0h] & 0x80000 size accounting; the patched build (0x180039670) inlines that same logic. Same checks, same exception, same size cap relaxation.

NdrMesTypeDecode2 was changed by inlining the body of NdrMesTypeDecode into it (it no longer delegates via a subroutine call) and applying the same dispatch devirtualization. It still validates the handle (NdrpValidateMesHandle), applies the same +0x4C cap, and calls NdrpCommonTypeHeaderUnmarshall. No pre-validation was added; the unpatched build already validated through the callee.


3. Code Comparison

The validation the two builds enforce is the same; only its placement (helper vs. inlined) differs.

// === UNPATCHED helper NdrpCommonTypeHeaderUnmarshall (0x1C0025F68),
//     called by NdrMesTypeDecode (0x1C0025E30) ===
void NdrpCommonTypeHeaderUnmarshall(MIDL_ES_MESSAGE* h)
{
    if (h->state_190 != 1)                       RpcRaiseException(0x723);
    if (h->nonfixed_1a1 && h->bufsize_1c8 < 8)   RpcRaiseException(0xC000000D);
    if (!(h->flags_1a0 & 0x40000)) {
        NdrpReadPicklingBuffer(h, 8);
        if (*(uint8_t*)h->buf_8 != 1)            RpcRaiseException(0x724);  // version
    }
    // ...
}

// === PATCHED NdrMesTypeDecode (0x18003A3E0) — same checks, now inlined ===
void NdrMesTypeDecode(MIDL_ES_MESSAGE* h, PFORMAT_STRING fmt, void* out)
{
    NdrpValidateMesHandle(h);
    if (out == NULL)              ExRaiseStatus(0xC0030009);
    if (h->size_4c > 0xa000c)     ExRaiseStatus(0x725);   // unpatched: 0xa0000
    if (h->state_190 != 1)                       ExRaiseStatus(0x723);
    if (h->nonfixed_1a1 && h->bufsize_1c8 < 8)   ExRaiseStatus(0xC000000D);
    if (!(h->flags_1a0 & 0x40000)) {
        NdrpReadPicklingBuffer(h, 8);
        if (*(uint8_t*)h->buf_8 != 1)            ExRaiseStatus(0x724);
    }
    // ... format-string dispatch (same table, direct calls for common handlers)
}

4. Assembly Evidence

Unpatched NdrMesTypeDecode (0x1C0025E30) — validation path

00000001C0025E45  call    NdrpValidateMesHandle
00000001C0025E4A  cmp     [rsp+38h+ppMemory], 0
00000001C0025E50  jz      loc_1C003773E              ; -> ExRaiseStatus(0xC0030009)
00000001C0025E56  cmp     dword ptr [rdi+4Ch], 0A0000h
00000001C0025E5D  ja      loc_1C0037750             ; -> ExRaiseStatus(0x725)
00000001C0025E85  call    NdrpCommonTypeHeaderUnmarshall  ; state/bufsize/version checks
00000001C0025EEF  movzx   ecx, byte ptr [rsi]       ; read format-string byte
00000001C0025F10  mov     rax, ds:rva off_1C0004E90[r8+rcx*8]  ; dispatch table

Unpatched NdrpCommonTypeHeaderUnmarshall (0x1C0025F68) — the checks already present

00000001C0025F6E  cmp     dword ptr [rcx+190h], 1
00000001C0025F78  jnz     loc_1C0037866            ; -> RpcRaiseException(0x723)
00000001C0025F7E  cmp     byte ptr [rcx+1A1h], 0
00000001C0025F8A  jz      short loc_1C0025F98
00000001C0025F8C  cmp     [rcx+1C8h], edx          ; edx = 8
00000001C0025F92  jb      loc_1C0037871           ; -> RpcRaiseException(0xC000000D)
00000001C0025F98  test    dword ptr [rcx+1A0h], 40000h
00000001C0025FA2  jz      loc_1C003787C            ; -> version-byte check -> 0x724

Patched NdrMesTypeDecode (0x18003A3E0) — same checks, inlined

000000018003A409  cmp     dword ptr [rdi+4Ch], 0A000Ch
000000018003A410  ja      loc_18003A608           ; -> ExRaiseStatus(0x725)
000000018003A430  cmp     dword ptr [rbx+190h], 1
000000018003A437  jnz     loc_18003A61A           ; -> ExRaiseStatus(0x723)
000000018003A43D  cmp     byte ptr [rbx+1A1h], 0
000000018003A444  jz      short loc_18003A453
000000018003A446  cmp     dword ptr [rbx+1C8h], 8
000000018003A44D  jb      loc_18003A62C           ; -> ExRaiseStatus(0xC000000D)
000000018003A453  test    dword ptr [rbx+1A0h], 40000h
000000018003A45D  jz      loc_18003A63E           ; -> version-byte check -> 0x724

The offsets (+0x190, +0x1A1, +0x1C8, +0x1A0) and exception codes (0x723, 0xC000000D, 0x724, 0x725) are byte-for-byte identical between the two builds; only the placement (helper call vs. inlined) and the size-cap constant (0xA0000 vs. 0xA000C) differ.


5. Trigger Conditions

No vulnerability is present, so there is no trigger. Both builds reject the same malformed handle states (wrong operation state, undersized non-fixed buffer, wrong format-string version) with the same exceptions before any type-specific unmarshalling routine runs. A handle whose state byte at +0x190 is not 1, whose non-fixed buffer at +0x1C8 is smaller than 8, or whose format-string version byte is not 1, is rejected in the unpatched build exactly as it is in the patched build.


6. Exploit Primitive & Development Notes

None. There is no reachable out-of-bounds read or write introduced or removed by this diff in the MES type-pickling path. The claimed type-confusion / OOB primitive would require the validation to be absent in the unpatched build; it is present. No exploit primitive is demonstrable.


7. Debugger Notes

Not applicable — there is no vulnerable state to reproduce. For reference, the validation both builds enforce can be observed at: - Unpatched: helper NdrpCommonTypeHeaderUnmarshall at 0x1C0025F68 (checks at 0x1C0025F6E, 0x1C0025F8C, 0x1C0025F98). - Patched: inlined into NdrMesTypeDecode at 0x18003A430, 0x18003A446, 0x18003A453.

Handle offsets referenced by both builds: +0x190 handle state (must be 1), +0x1A0 flags (bit 0x40000 gates the version check), +0x1A1 non-fixed-buffer flag, +0x1C8 non-fixed buffer size (must be ≥ 8), +0x4C size field (capped).


8. Changed Functions — Full Triage

  • NdrMesTypeDecode: [Not security] Helper validation (NdrpCommonTypeHeaderUnmarshall) inlined; format-string dispatch devirtualized to direct calls for NdrNonEncapsulatedUnionUnmarshall / NdrSimpleStructUnmarshall / NdrComplexStructUnmarshall; size cap 0xA00000xA000C (marginal relaxation). Same guard set and exceptions in both builds.
  • NdrMesTypeDecode2: [Not security] Now inlines the NdrMesTypeDecode body instead of delegating via a subroutine call; still validates the handle and calls NdrpCommonTypeHeaderUnmarshall. No new pre-validation.
  • NdrMesTypeDecode3: [Not security] Wrapper; reaches the same decode path. No new validation.
  • NdrMesTypeAlignSize: [Not security] Inlines the NdrpCommonTypeHeaderSize logic (state check +0x1900x723 and +0x1A0 & 0x80000 size accounting are present in the unpatched helper too); same size cap relaxation.
  • NdrMesTypeEncode2: [Not security] Setup-then-delegate to NdrMesTypeEncode; size arithmetic lives in the same downstream helpers in both builds. No delivered overflow fix demonstrable in this function.
  • Register-allocation / rebuild churn only: NdrMesTypeAlignSize2, NdrMesTypeAlignSize3, NdrMesTypeFree2, MesBufferHandleReset, and Ndr64AsyncClientCall differ by register allocation, stack-slot layout, and relocated call targets. No security-relevant logic change.
  • Module-wide rebuild: The bulk of the 628 changed functions reflects a general toolchain/library rebuild — WIL feature-staging helpers (wil_details_*, Feature_*__private_IsEnabled*), integer-overflow helper renames (UIntAdd/ULongMult/ULongLongMultULongAdd/MultiplyWithOverflowCheck), memset intrinsic variants (__memset_spec_*), and Control Flow Guard dispatch thunks (_guard_dispatch_icall). These are servicing/rebuild churn, not a targeted memory-safety fix.

9. Unmatched Functions

  • Added (patched only): rebuild-introduced helpers and thunks (e.g. _guard_dispatch_icall, __memset_spec_*, MultiplyWithOverflowCheck, various LRPC/EEInfo helpers). None represents a security fix to the MES pickling path.
  • Removed (unpatched only): superseded helpers (e.g. UIntAdd, ULongMult, ULongLongMult, several wil_details_* and Feature_* staging helpers). Consistent with a toolchain/library rebuild.

10. Confidence & Caveats

  • Confidence Level: High. The three checks the diff appears to "add" (0x723, 0xC000000D, 0x724) are present in the unpatched build inside NdrpCommonTypeHeaderUnmarshall (0x1C0025F68) and NdrpCommonTypeHeaderSize (0x1C00347AC), at the same offsets and with the same exception codes; the patched build merely inlines them. The only literal constant change (0xA00000xA000C) relaxes rather than tightens the cap.
  • Caveat: This is a large rebuild (628 changed functions). The audit confirms the MES type-pickling functions carry no delivered security fix and that the surrounding churn matches a toolchain/library rebuild signature. A function-by-function review of all NDR marshalling routines was not exhaustively performed; no security-relevant change was identified in the functions this report covers.