mqac.sys — compiler inlining of user-mode pointer probes; no probe direction or size altered
KB5078752
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(unpatched0x1400234E0, patched0x1C0026338) - 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 ProbeForRead→ProbeForWrite 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::GetProperties → ACDeepProbeReceiveParams → ACDeepProbeMsgPropsForReceive), 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 isProbeForRead(ptr, 0x10, 1)in the unpatched build and an inlined range check of the same size0x10in the patched build. The direction and size are unchanged; the only difference is inlining. - The inline shape at
0x168is identical to the patched inline of offset0x1F0, which is an out-of-lineProbeForReadin the unpatched build, confirming the shape corresponds to an inlinedProbeForRead. - 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:
- The MSMQ Access Control device (
\\.\MQAC) exists only when the Message Queuing feature is installed. TheACDeepProbeMsgPropsForReceiveroutine is reached on the message-receive path in both builds and validates the same pointers the same way. - Whether the unpatched
ProbeForReadat offset0x168is 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.