1. Overview

Property Value
Unpatched Binary mqac_unpatched.sys
Patched Binary mqac_patched.sys
Overall Similarity 0.6052
Matched Functions 512
Changed Functions 418
Identical Functions 94
Unmatched (Unpatched) 0
Unmatched (Patched) 0

Verdict: No security-relevant change is present between the two builds. The two files are different servicing/toolchain builds of the MSMQ AC kernel driver. The dominant difference across the 418 "changed" functions is the compiler inlining out-of-line ProbeForRead/ProbeForWrite calls into MmUserProbeAddress range checks, together with register reallocation and pool/memset wrapper inlining. In the specific function examined in detail, ACDeepProbeMsgPropsForReceive, no probe direction and no probe size changed between the two builds; the only difference is that one probe call was emitted inline in the patched build.


2. Vulnerability Summary

Finding 1 — Probe at output-structure offset 0x168 (No security-relevant change)

  • Severity: None (no change between builds)
  • Vulnerability Class: N/A
  • Affected Function: ACDeepProbeMsgPropsForReceive (unpatched 0x1400234E0, patched 0x1C0026338)
  • Location examined: struct field at rbx+0x168 (a2[45])

What the two builds actually contain: ACDeepProbeMsgPropsForReceive walks a caller-supplied structure of pointers and probes each non-NULL entry. For the pointer at offset 0x168, the unpatched build calls ProbeForRead(ptr, 0x10, 1) (an out-of-line import call). The patched build performs the identical validation as an inlined MmUserProbeAddress range check of the same size 0x10:

// patched ACDeepProbeMsgPropsForReceive, field 0x168
v30 = (unsigned __int64 *)MmUserProbeAddress;
if ( v29 != 0 && (v29 + 16 > MmUserProbeAddress || v29 + 16 < v29) )
    *(_BYTE *)MmUserProbeAddress = 0;

The size argument is 16 (0x10) in both builds. The inlined form is direction-neutral and does not touch the user pointer; it only performs the range/overflow check. The same inline shape is produced in the patched build for other fields in this function that are ProbeForRead in the unpatched build (for example offset 0x1F0, ProbeForRead(ptr, 4, 1) → inline range check of size 4). This confirms the offset-0x168 change is nothing more than the compiler inlining an existing ProbeForRead call: the probe direction (Read) and size (0x10) are unchanged.

There is no ProbeForReadProbeForWrite conversion and no size change. No user-mode-triggerable behavioral difference is introduced by the patch at this location.

Finding 2 — Probe sizes at offsets 0x198 / 0x1A0 / 0x1A8 / 0x1B8 (No change)

  • Severity: None
  • Affected Function: ACDeepProbeMsgPropsForReceive

The probe sizes for the surrounding write-phase pointers are identical in both builds:

Field offset Array index Probe (both builds)
0x198 a2[51] ProbeForWrite(ptr, 4, 1)
0x1A0 a2[52] ProbeForWrite(ptr, 1, 1)
0x1A8 a2[53] ProbeForWrite(ptr, 1, 1)
0x1B8 a2[55] ProbeForWrite(ptr, 8, 1)

No size argument changed between the unpatched and patched builds for any of these fields. (There is no probe at offset 0x1B0; that field is not present in the structure's probe sequence.)

Finding 3 — Read-phase probes at offsets 0x1F0 and above (No change)

  • Severity: None
  • Affected Function: ACDeepProbeMsgPropsForReceive

After the write-phase, the function probes a run of input pointers (offsets 0x1F0, 0x208, 0x218, …) with ProbeForRead. In the patched build these ProbeForRead calls are emitted as inline MmUserProbeAddress range checks with the same sizes. No direction or size changed; this is inlining only.


3. Pseudocode Diff

// ========================================================================
// ACDeepProbeMsgPropsForReceive
// a2 points to the caller-supplied pointer structure
// ========================================================================

// ---- UNPATCHED (mqac_unpatched.sys @ 0x1400234E0) ----

v28 = a2[43];                          // offset 0x158
if (v28) ProbeForWrite(v28, 4u, 1u);

v29 = a2[45];                          // offset 0x168
if (v29) ProbeForRead(v29, 0x10u, 1u); // out-of-line ProbeForRead, size 0x10

v30 = a2[48];                          // offset 0x180
if (v30) ProbeForWrite(v30, 4u, 1u);
// ...
v33 = a2[52];                          // offset 0x1A0
if (v33) ProbeForWrite(v33, 1u, 1u);
v34 = a2[53];                          // offset 0x1A8
if (v34) ProbeForWrite(v34, 1u, 1u);
v35 = a2[55];                          // offset 0x1B8
if (v35) ProbeForWrite(v35, 8u, 1u);


// ---- PATCHED (mqac_patched.sys @ 0x1C0026338) ----

// offset 0x168 — SAME ProbeForRead, size 0x10, emitted inline
v30 = (unsigned __int64 *)MmUserProbeAddress;
if (v29 != 0 && (v29 + 16 > MmUserProbeAddress || v29 + 16 < v29))
    *(_BYTE *)MmUserProbeAddress = 0;   // range/overflow check only; size 0x10 unchanged

// offsets 0x1A0 / 0x1A8 / 0x1B8 — SAME ProbeForWrite sizes (1, 1, 8), unchanged
ProbeForWrite(v34, 1u, 1u);
ProbeForWrite(v35, 1u, 1u);
ProbeForWrite(v36, 8u, 1u);

4. Assembly Analysis

Unpatched — offset 0x168 probe

; mqac_unpatched.sys — ACDeepProbeMsgPropsForReceive @ 0x1400234E0
; field a2[45] at struct offset 0x168 — out-of-line ProbeForRead, size 0x10

00000001400237E3  mov     rcx, [rbx+168h]           ; load pointer (Address)
00000001400237EA  mov     r13d, 10h                 ; Length = 0x10
00000001400237F0  test    rcx, rcx
00000001400237F3  jz      short loc_140023807       ; skip if NULL
00000001400237F5  mov     r8d, r14d                 ; Alignment = 1
00000001400237F8  mov     edx, r13d                 ; Length = 0x10
00000001400237FB  call    cs:__imp_ProbeForRead     ; ProbeForRead(ptr, 0x10, 1)

Patched — offset 0x168 probe (same probe, inlined)

; mqac_patched.sys — ACDeepProbeMsgPropsForReceive @ 0x1C0026338
; field at struct offset 0x168 — inlined range check, size 0x10 (unchanged)

00000001C0026637  mov     rax, [rbx+168h]           ; load pointer
00000001C002663E  mov     rdx, cs:MmUserProbeAddress
00000001C0026645  test    rax, rax
00000001C0026648  jz      short loc_1C002665E       ; skip if NULL
00000001C002664A  mov     rcx, [rdx]                ; rcx = MmUserProbeAddress
00000001C002664D  lea     r8, [rax+10h]             ; end = ptr + 0x10 (size unchanged)
00000001C0026651  cmp     r8, rcx
00000001C0026654  ja      short loc_1C002665B        ; out of range -> fault
00000001C0026656  cmp     r8, rax
00000001C0026659  jnb     short loc_1C002665E        ; no overflow -> ok
00000001C002665B  mov     [rcx], bpl                 ; write guard to raise fault

The patched sequence is the inline expansion of the same probe. It reads no data from and writes no data to the user pointer [rax]; it only compares ptr + 0x10 against MmUserProbeAddress and forces a fault (by writing the guard address) when the range check fails. The identical inline shape is produced for offset 0x1F0, which is an out-of-line ProbeForRead(ptr, 4, 1) in the unpatched build — establishing that this shape corresponds to an inlined ProbeForRead, not a converted ProbeForWrite.


5. Trigger Conditions

Not applicable. No behavioral difference exists between the builds in this function, so there is no patched-versus-unpatched trigger. The function is reached on the MSMQ message-receive path (ACDeviceControl IOCTL dispatch → CPacket::GetPropertiesACDeepProbeReceiveParamsACDeepProbeMsgPropsForReceive), and requires the Message Queuing feature to be installed so the \\.\MQAC device exists, but the probe validation performed there is the same in both builds.


6. Exploit Primitive & Development Notes

No exploit primitive is delivered or removed by the patch. The apparent ProbeForRead-versus-ProbeForWrite difference at offset 0x168 is a decompiler artifact of the compiler inlining an existing ProbeForRead call; the probe direction and size are unchanged, and the inline check performs the identical range validation as the out-of-line call. There is no demonstrated kernel write to a read-only page, no out-of-bounds write, and no denial-of-service primitive attributable to the patch.


7. Debugger Reference

Key instruction offsets (unpatched)

Offset Instruction Note
+0x37E3 mov rcx, [rbx+168h] Loads the offset-0x168 pointer
+0x37EA mov r13d, 10h Probe length 0x10
+0x37FB call ProbeForRead ProbeForRead(ptr, 0x10, 1) (unchanged in patched, inlined)

Struct/offset reference (both builds identical)

Field Offset Array Index (a2[n]) Probe (Unpatched) Probe (Patched)
0x168 a2[45] ProbeForRead(ptr, 0x10, 1) inline range check, size 0x10 (same ProbeForRead)
0x180 a2[48] ProbeForWrite(ptr, 4, 1) ProbeForWrite(ptr, 4, 1) (no change)
0x198 a2[51] ProbeForWrite(ptr, 4, 1) ProbeForWrite(ptr, 4, 1) (no change)
0x1A0 a2[52] ProbeForWrite(ptr, 1, 1) ProbeForWrite(ptr, 1, 1) (no change)
0x1A8 a2[53] ProbeForWrite(ptr, 1, 1) ProbeForWrite(ptr, 1, 1) (no change)
0x1B8 a2[55] ProbeForWrite(ptr, 8, 1) ProbeForWrite(ptr, 8, 1) (no change)

8. Changed Functions — Full Triage

Security-Relevant Changes

None identified. No function delivers a probe-direction change, a probe-size change, an added or removed bounds check, or any other security-relevant behavioral difference between the two builds.

Behavioral / Compiler Changes (Not Security-Relevant)

Function Similarity Change Type Note
ACDeepProbeMsgPropsForReceive (0x1400234E0 / 0x1C0026338) 0.6608 Behavioral One ProbeForRead call (offset 0x168, size 0x10) emitted inline in the patched build; all other probe directions and sizes identical. No security-relevant change.
ACDeviceControl (0x14000B100) 0.9541 Behavioral Main IRP/IOCTL dispatch. Register reallocation and ProbeForRead inlining to MmUserProbeAddress checks. Same IOCTL code comparisons and size checks.
sub_140006A58 0.9395 Behavioral ProbeForRead(arg3, 0x48, 1) inlined; array-index lookup (sub_14000D93C) inlined with its pre-existing bounds check; refcount decrement inlined. No new security boundary.
sub_14000644C 0.9362 Behavioral ProbeForRead(arg3, 0x18, 1) inlined; pool wrapper replaced with direct ExAllocatePoolWithTagPriority / ExFreePoolWithTag. Toolchain change only.
ACSendMessage (0x140009478) 0.9378 Behavioral ProbeForRead inlined; __builtin_memset replaced with a wrapper call; stack security cookie present. Compiler hardening only. This is the message-send path and does not reach ACDeepProbeMsgPropsForReceive.

Dominant pattern across all 418 changed functions: the compiler inlining ProbeForRead/ProbeForWrite into direct MmUserProbeAddress comparisons, register reallocation, and pool/memset wrapper inlining. These preserve the original validation logic.


9. Unmatched Functions

No functions were added or removed in either direction (unmatched_unpatched: 0, unmatched_patched: 0). No new sanitizer functions were added and no security checks were removed.


10. Confidence & Caveats

Confidence: High that no security-relevant change is present.

  • At offset 0x168, the probe is ProbeForRead(ptr, 0x10, 1) in the unpatched build and an inlined range check of the same size 0x10 in the patched build. The direction and size are unchanged; the only difference is inlining.
  • The inline shape at 0x168 is identical to the patched inline of offset 0x1F0, which is an out-of-line ProbeForRead in the unpatched build, confirming the shape corresponds to an inlined ProbeForRead.
  • The surrounding write-phase probe sizes (0x198, 0x1A0, 0x1A8, 0x1B8) are identical in both builds.
  • The remaining 417 changed functions differ only by probe inlining, register reallocation, and pool/memset wrapper inlining, consistent with a toolchain rebuild between servicing branches.

Caveats:

  1. The MSMQ Access Control device (\\.\MQAC) exists only when the Message Queuing feature is installed. The ACDeepProbeMsgPropsForReceive routine is reached on the message-receive path in both builds and validates the same pointers the same way.
  2. Whether the unpatched ProbeForRead at offset 0x168 is the intended direction for that pointer cannot be determined from this diff; it is identical in both builds, so it is not something the patch changes.