1. Overview

Field Value
Unpatched binary cea_unpatched.sys
Patched binary cea_patched.sys
Overall similarity 0.90
Matched functions 146
Changed functions 84
Identical functions 62
Unmatched (either direction) 0 / 0

Verdict: The patch closes a critical integer-overflow → kernel-pool-buffer-overflow in EaSignalAggregatedEvent by adding an explicit wrap-around check (new helper EapSignalContentPrepareEventForPublishing (sub_140008b90)), and broadly hardens the driver by migrating every ExAllocatePoolWithTag call to ExAllocatePool2.


2. Vulnerability Summary

Finding 1 — Critical: Integer Overflow (CWE-190) → Kernel Pool Buffer Overflow (CWE-122)

Attribute Value
Severity Critical
Class Integer overflow → heap/pool overflow
Function EaSignalAggregatedEvent (cea_unpatched!EaSignalAggregatedEvent, 0x1c00034b0)
Primitive Controllable, large kernel pool overflow (write size up to ~4 GB past allocation end)

Root cause. EaSignalAggregatedEvent computes the pool allocation size as a 32-bit sum of three components:

total_size = (arg5 ? 0x10 : 0) + arg8 + (arg6 ? 0x10 : 0)

arg8 is the caller-supplied variable-length data size. It is loaded as a 32-bit value at 0x1c0003545 (mov r15d, dword ptr [rbp+3Fh+Size]), so both the size arithmetic and the eventual copy length are 32-bit-bounded (maximum ~4 GB). The sum is performed with lea eax,[r15+rcx] followed by add ebx,eax — both 32-bit ops, with no overflow check. The only guard is a je that fires only if the sum wraps exactly to zero, missing the general overflow case.

If arg8 = 0xFFFFFFF0 and both arg5/arg6 are non-NULL, the wrap is:

0xFFFFFFF0 + 0x10 + 0x10 = 0x100000010  0x00000010 (32-bit truncated)

The driver then calls ExAllocatePoolWithTag(PagedPool, 0x10, 'EtvA') — allocating only 16 bytes — but proceeds to copy:

  1. 16 bytes from arg5 (fills the allocation exactly),
  2. 16 bytes from arg6 (first 16 bytes of overflow),
  3. arg8 bytes (≈ 4 GB) from arg7 via memmove (sub_1c0008d80).

The result is a catastrophic kernel pool overflow, controllable in both size and (partially) content.

Fix. The patched driver inserts a new helper, EapSignalContentPrepareEventForPublishing (sub_140008b90), that performs an explicit wrap detection:

lea  eax, [rdi+r8]    ; base + var_data
cmp  eax, edi         ; result < base   wrapped
jb   overflow_error   ; return STATUS_INTEGER_OVERFLOW (0xC0000095)

and adds an explicit buffer-too-small validation returning STATUS_BUFFER_TOO_SMALL (0xC0000023). All pool allocations also migrate from ExAllocatePoolWithTag to ExAllocatePool2(POOL_FLAG_NON_PAGED, ...).

Reachability. EaSignalAggregatedEvent (0x1c00034b0) is an exported function with no internal callers in the driver; the vulnerable size computation and the 'EtvA' allocation exist only inside it. It is therefore reached through its export: a caller supplies arg1 (aggregation context), arg2 (event-ID pair), arg5/arg6 (optional 16-byte blocks), arg7 (variable data pointer) and arg8 (variable data size). The size arithmetic operates directly on the caller-supplied arg8.

The WNF notification path does not reach this computation. WnfEventCallback (sub_1c00018c8) parses the WNF state buffer and calls EaiProcessNotification (sub_1c0001bc4) directly; EaiProcessNotification is a callee of EaSignalAggregatedEvent (invoked at 0x1c0003670 after the allocation), not a route back into the vulnerable code. The exact RPC opnum that dispatches into EaSignalAggregatedEvent from user mode is not determinable from these binaries.

Finding 2 — No security-relevant change: WNF State Data Parsing Refactored

Attribute Value
Severity None (refactor)
Class Validation logic extracted into a shared helper; no bounds check added or removed
Function WnfEventCallback (sub_1C00018C8) (similarity 0.5941 vs patched)

The unpatched WnfEventCallback (sub_1C00018C8) parses a packed bitfield structure from a WNF state buffer and already validates its bounds inline: it rejects buffers smaller than 8 bytes (cmp r9d, 8; jnb at 0x1c00019b9) and buffers smaller than count + 8 (cmp r9, rcx; jnb at 0x1c0001a41, with rcx = ((*buf >> 2) & 0xFFF) + 8). The patched version moves this same bitfield parsing and the same checks into a new helper EapSignalMessageReadFromBuffer (sub_140008a98), which enforces the equivalent conditions (size >= 8 at 0x140008aab returning STATUS_BUFFER_TOO_SMALL; size >= count + 8 at 0x140008ada returning 0xc000090b; and a trailing per-field size check returning STATUS_DATA_ERROR / 0xc000003e).

This is a refactor: no bounds check is added that was absent before, and none is removed. The WNF path does not reach the EaSignalAggregatedEvent size computation of Finding 1 — WnfEventCallback calls EaiProcessNotification (sub_1c0001bc4) directly, and does not invoke the overflow-checked builder EapSignalContentPrepareEventForPublishing (sub_140008b90). There is no security-relevant change on this path.


3. Pseudocode Diff

EaSignalAggregatedEvent — vulnerable size computation

// ============== UNPATCHED (0x1c00034b0) ==============
// arg5, arg6: pointers to 16-byte event-data blocks (optional)
// arg7      : pointer to variable-length data
// arg8      : variable-length data SIZE (32-bit, attacker-controlled)

int32_t arg6_contrib = (arg6 != NULL) ? 0x10 : 0;
int32_t arg5_contrib = (arg5 != NULL) ? 0x10 : 0;

// *** VULNERABLE: 32-bit addition with NO overflow check ***
int32_t total_size = arg6_contrib + (arg8 + arg5_contrib);   // can wrap

if (total_size == 0)              // *** only checks for *exact* zero wrap ***
    goto use_existing_buffer;     // (different buggy path)

// Allocation uses the (possibly wrapped) small size:
P = ExAllocatePoolWithTag(PagedPool, (uint32_t)total_size, 'EtvA');   // tiny alloc!

if (P) {
    if (arg5) { *(__m128*)P = *(__m128*)arg5; r12 = 2; ptr = P + 0x10; }   // 16-byte write
    if (arg6) { *(__m128*)ptr = *(__m128*)arg6; r12 |= 1; ptr += 0x10; }   // 16-byte write
    memmove (sub_1c0008d80)(ptr, arg7_data, arg8);     // *** copies arg8 (~4GB) bytes ***
}

// ============== PATCHED (via EapSignalContentPrepareEventForPublishing (sub_140008b90)) ==============
int32_t sum = base_size + var_data_size;     // lea eax,[rdi+r8]
if ((uint32_t)sum < (uint32_t)base_size)     // cmp eax,edi ; jb overflow
    return STATUS_INTEGER_OVERFLOW;          // 0xC0000095  *** ADDED CHECK ***

if (*arg4_provided_size >= (uint32_t)sum)    // cmp dword [r9],edi ; jae ok
    // proceed with memset + structured copy
else {
    *arg4_needed_size = sum;                 // output required size
    return STATUS_BUFFER_TOO_SMALL;          // 0xC0000023
}

WnfEventCallback (sub_1C00018C8) — WNF parsing path

// ============== UNPATCHED ==============
// Inline parsing of packed bitfields from WNF buffer.
// Extracts count (bits 2-13), type flag (bit 0), state flag (bit 1).
// Bounds checks ARE present inline:
//   - size >= 8            (cmp r9d,8; jnb  at 0x1c00019b9)
//   - size >= count + 8    (cmp r9,rcx; jnb at 0x1c0001a41)
// then calls EaiProcessNotification (sub_1c0001bc4) directly.

// ============== PATCHED ==============
status = EapSignalMessageReadFromBuffer (sub_140008a98)(wnf_buffer);  // same parsing + same checks, extracted
//   - size >= 8            (cmp edx,8;  jnb at 0x140008aab)
//   - size >= count + 8    (cmp rax,rcx; jnb at 0x140008ada)
// then calls EaiProcessNotification (sub_1c0001bc4) directly (unchanged path).
// Refactor only: no bounds check added or removed.

4. Assembly Analysis

EaSignalAggregatedEvent — full unpatched vulnerable path

; -------- Prologue / arg setup --------
0x1c00034b0 : push    rbp
0x1c00034b2 : push    rbx
0x1c00034b3 : push    rsi
0x1c00034b4 : push    rdi
0x1c00034b5 : push    r12
0x1c00034b7 : push    r13
0x1c00034b9 : push    r14
0x1c00034bb : push    r15
0x1c00034bd : lea     rbp, [rsp-0x7]
0x1c00034c2 : sub     rsp, 0xc8
0x1c00034c9 : mov     rax, qword [rel 0x1c000e040]   ; __security_cookie
0x1c00034d0 : xor     rax, rsp
0x1c00034d3 : mov     qword [rbp-0x11], rax
0x1c00034d7 : mov     rax, qword [rbp+0x6f]          ; arg5 (128-bit event data ptr)
0x1c00034db : xor     edi, edi
0x1c00034dd : mov     r13, qword [rbp+0x77]          ; arg6 (128-bit event data ptr)
0x1c00034e1 : mov     rbx, rcx                        ; arg1 (aggregation context)
0x1c00034e4 : mov     rcx, qword [rbp+0x8f]          ; arg9 (output ptr)
0x1c00034eb : xor     esi, esi
0x1c00034ed : mov     qword [rbp-0x69], rax           ; save arg5
0x1c00034f1 : xor     r12d, r12d
0x1c00034f4 : cmp     dword [rel 0x1c000e000], 0x4   ; ETW level check
0x1c00034fb : mov     r14, rdx                        ; arg2 (event ID pair)
0x1c00034fe : mov     rax, qword [rbp+0x7f]          ; arg7 (variable data ptr)
0x1c0003502 : mov     qword [rbp-0x61], rax
0x1c0003506 : mov     byte [rbp-0x80], r9b
0x1c000350a : mov     byte [rsp+0x40], r8b
0x1c000350f : mov     qword [rbp-0x71], rcx
0x1c0003513 : mov     qword [rbp-0x79], rdi

; -------- After lookup/validation passes, size computation --------
0x1c00035bf : call    0x1c0011614                     ; release push lock
0x1c00035c4 : mov     rax, qword [rbp-0x69]           ; arg5
0x1c00035c8 : neg     rax                              ; test arg5 != NULL
0x1c00035cb : mov     rax, r13                         ; arg6
0x1c00035ce : sbb     ecx, ecx                         ; ecx = arg5 ? -1 : 0
0x1c00035d0 : and     ecx, 0x10                        ; ecx = arg5 ? 0x10 : 0
0x1c00035d3 : neg     rax                              ; test arg6 != NULL
0x1c00035d6 : sbb     ebx, ebx                         ; ebx = arg6 ? -1 : 0
0x1c00035d8 : and     ebx, 0x10                        ; ebx = arg6 ? 0x10 : 0

; *** VULNERABLE: 32-bit add of attacker-controlled arg8 ***
0x1c00035db : lea     eax, [r15+rcx]   ; *** OVERFLOW: eax = arg8 + (arg5?0x10:0) ***
0x1c00035df : add     ebx, eax         ; *** OVERFLOW: ebx = total (32-bit!) ***
0x1c00035e1 : je      0x1c000364a      ; only jumps if ebx == 0  ← misses general wrap

; -------- Allocation with possibly-wrapped size --------
0x1c00035e3 : mov     edx, ebx                         ; edx = (wrapped) size
0x1c00035e5 : mov     ecx, 0x1                         ; PagedPool
0x1c00035ea : mov     r8d, 0x41747645                  ; 'EtvA'
0x1c00035f0 : call    qword [rel 0x1c0010178]         ; ExAllocatePoolWithTag
0x1c00035f7 : nop
0x1c00035fc : mov     rsi, rax                         ; rsi = tiny buffer
0x1c00035ff : test    rax, rax
0x1c0003602 : jne     0x1c000360b

; -------- Data copy into undersized buffer --------
0x1c000360b : mov     rax, qword [rbp-0x69]           ; arg5
0x1c000360f : mov     rcx, rsi
0x1c0003612 : test    rax, rax
0x1c0003615 : je      0x1c0003628
0x1c0003617 : movups  xmm0, xmmword [rax]              ; load 16 bytes (arg5)
0x1c000361a : mov     r12d, 0x2
0x1c0003620 : lea     rcx, [rsi+0x10]
0x1c0003624 : movdqu  xmmword [rsi], xmm0             ; *** WRITE 16 (buffer may be <16!) ***
0x1c0003628 : test    r13, r13                         ; arg6
0x1c000362b : je      0x1c000363e
0x1c000362d : movups  xmm0, xmmword [r13]              ; load 16 bytes (arg6)
0x1c0003632 : or      r12d, 0x1
0x1c0003636 : movdqu  xmmword [rcx], xmm0             ; *** WRITE 16 more past arg5 ***
0x1c000363a : add     rcx, 0x10
0x1c000363e : mov     rdx, qword [rbp-0x61]           ; arg7 (variable data)
0x1c0003642 : mov     r8, r15                          ; arg8 (HUGE copy length)
0x1c0003645 : call    0x1c0008d80                      ; *** MASSIVE COPY: arg8 bytes ***

Patched overflow check (new EapSignalContentPrepareEventForPublishing (sub_140008b90))

0x140008c0a : lea     eax, [rdi+r8]    ; base + var_data
0x140008c0e : cmp     eax, edi         ; result < base ⇒ wrapped
0x140008c10 : jb      0x140008c26      ; → STATUS_INTEGER_OVERFLOW
0x140008c14 : cmp     dword [r9], edi  ; provided buffer >= needed?
0x140008c17 : jae     0x140008c30      ; ok
0x140008c19 : mov     dword [r9], edi  ; output needed size
0x140008c1c : mov     ebx, 0xc0000023  ; STATUS_BUFFER_TOO_SMALL
0x140008c26 : mov     ebx, 0xc0000095  ; STATUS_INTEGER_OVERFLOW (new)

5. Trigger Conditions

  1. Obtain a handle to the EA aggregation context. Open the Brokered Event Aggregation interface from a user-mode Broker Infrastructure client (or trigger the WNF state-change subscription path).
  2. Create an aggregation event via EACreateAggregateEvent so that arg1 of EaSignalAggregatedEvent is a valid hash-table context resolvable by EaiFindAndAcquireAggregateEvent (sub_1c000251c).
  3. Prepare the call arguments:
  4. arg1 = aggregation context handle.
  5. arg2 = pointer to a 64-bit event-ID pair; *(uint32_t*)arg2 and *((uint32_t*)arg2 + 1) must not both be zero (validation gate at function entry).
  6. arg5 = non-NULL pointer to 16 bytes (forces +0x10 to size).
  7. arg6 = non-NULL pointer to 16 bytes (forces +0x10 to size).
  8. arg7 = non-NULL pointer to variable-length data buffer.
  9. arg8 = 0xFFFFFFF0 (causes (0xFFFFFFF0 + 0x10 + 0x10) & 0xFFFFFFFF == 0x10).
  10. Issue the call via the Broker Infrastructure RPC dispatch (NdrClientCall3). No race-condition ordering is required — the path is synchronous.
  11. Confirm the bug fired:
  12. At 0x1c00035df, EBX == 0x10 while R15D == 0xFFFFFFF0.
  13. At 0x1c00035f0, ExAllocatePoolWithTag allocates 16 bytes.
  14. At 0x1c0003645, the driver copies arg8 bytes into that 16-byte allocation — an out-of-bounds kernel pool write past the allocation end.

6. Exploit Primitive & Development Notes

Primitive (as demonstrable from the binary). A kernel pool out-of-bounds write where:

  • The size of the overflow is controlled by the 32-bit arg8 (up to ~4 GB, the DWORD maximum).
  • The content of the first 32 bytes past the undersized allocation is controlled (arg5 + arg6 16-byte blocks copied via the two movdqu writes).
  • The remaining bytes come from the arg7 buffer, copied by memmove (sub_1c0008d80) at 0x1c0003645.
  • The allocation is tag 'EtvA' (0x41747645), PagedPool, in the unpatched build.

By choosing arg8 to wrap to a small but non-zero total (e.g. arg8 = 0xFFFFFFE1 with both arg5/arg6 present → 0xFFFFFFE1 + 0x10 + 0x10 = 0x1 truncated), the allocation is 1 byte while the subsequent memmove length remains arg8 — an out-of-bounds pool write whose length is attacker-chosen.

End-to-end exploitation (pool layout control, adjacent-object selection, control-flow hijack) is not established from these two binaries and is not claimed here. The verified, reachable impact is a controlled-length kernel pool out-of-bounds write.


7. Debugger PoC Playbook (WinDbg / KD)

Breakpoints (paste directly)

bp cea_unpatched!EaSignalAggregatedEvent            ; function entry
bp cea_unpatched!EaSignalAggregatedEvent+0x12B      ; 0x1c00035db  ← overflow lea
bp cea_unpatched!EaSignalAggregatedEvent+0x12F      ; 0x1c00035df  ← overflow add
bp cea_unpatched!EaSignalAggregatedEvent+0x140      ; 0x1c00035f0  ← ExAllocatePoolWithTag
bp cea_unpatched!EaSignalAggregatedEvent+0x195      ; 0x1c0003645  ← copy call (overflow fires)

; Conditional breakpoint to only catch the bug:
bp 0x1c00035e1 ".if (@ebx < 0x30) & (@r15 > 0x100) {.echo INT_OVERFLOW_DETECTED} .else {gc}"

What to inspect at each breakpoint

Stop Address What to inspect
Entry 0x1c00034b0 rcx = arg1 (context), rdx = arg2 (event-ID pair — must not be 0,0), [rsp+0x28] = arg5, [rsp+0x30] = arg6, [rsp+0x38] = arg7, [rsp+0x40] = arg8 (overflow trigger).
Overflow lea 0x1c00035db r15d = arg8, ecx = arg5 ? 0x10 : 0. EAX = arg8 + arg5_contrib. If eax < r15d, wrap has occurred.
Overflow add 0x1c00035df ebx = final allocation size; compare against r15d. If ebx << r15d, integer overflow.
Allocation 0x1c00035f0 edx = size passed to ExAllocatePoolWithTag (will be tiny). Tag = 0x41747645.
Copy call 0x1c0003645 rcx = destination inside the 16-byte allocation, rdx = arg7 source, r8 = arg8 length (≈ 4 GB).

Key offsets

Offset Instruction Role
0x1c00035db lea eax,[r15+rcx] Vulnerable 32-bit add (1st half).
0x1c00035df add ebx,eax Vulnerable 32-bit add (2nd half).
0x1c00035e1 je 0x1c000364a Only catches exact zero wrap; misses general overflow.
0x1c00035f0 call ExAllocatePoolWithTag Allocates with wrapped (tiny) size.
0x1c0003624 movdqu [rsi],xmm0 Writes 16 bytes (arg5 data) — may already overflow.
0x1c0003636 movdqu [rcx],xmm0 Writes 16 bytes (arg6 data) past end.
0x1c0003645 call memmove (sub_1c0008d80) Copies arg8 bytes — massive overflow.
0x140008c0e0x140008c10 (patched) cmp eax,edi; jb Added overflow check — absent in unpatched.

Trigger setup from user mode

  1. Load cea.sys is auto-loaded by Windows; no manual load needed.
  2. Use the Broker Infrastructure user-mode API to:
  3. Create an aggregation context (call EACreateAggregateEvent).
  4. Call EaSignalAggregatedEvent (via NdrClientCall3) with:
    • arg1 = aggregation handle,
    • arg2 = non-zero event-ID pair,
    • arg5 / arg6 = pointers to 16-byte buffers,
    • arg7 = pointer to a (small) data buffer,
    • arg8 = 0xFFFFFFF0 (or 0xFFFFFFE1 for a smaller, controlled-length overflow).

The WNF notification path (WnfEventCallback (sub_1c00018c8)) does not reach this size computation; it calls EaiProcessNotification (sub_1c0001bc4) directly and is not an alternative trigger for this bug.

Expected runtime observation

  • With arg8 = 0xFFFFFFF0: at 0x1c00035df, ebx = 0x10. Allocation of 16 bytes succeeds. movdqu at 0x1c0003624 fills it exactly. movdqu at 0x1c0003636 writes 16 bytes past the end. memmove (sub_1c0008d80) at 0x1c0003645 copies arg8 bytes past the allocation — an out-of-bounds kernel pool write.
  • Use !pool @rcx after the overflow to inspect corrupted headers; !poolfind 0x41747645 1 to enumerate 'EtvA' allocations.

Struct / offset notes

  • Pool tag for vulnerable allocation: 0x41747645 ('EtvA').
  • Pool type (unpatched): PagedPool (0x1).
  • Allocation size in pool header will be small (e.g., 0x10); copy length (r8 at 0x1c0003645) will be huge.
  • Event-ID pair (arg2) is two 32-bit values; both zero is rejected by the entry gate.

8. Changed Functions — Full Triage

Security-relevant

Function Sim. Change Note
EaSignalAggregatedEvent 0.853 Security-relevant Removes vulnerable inline 32-bit size arithmetic; adds overflow-safe helper EapSignalContentPrepareEventForPublishing (sub_140008b90) returning STATUS_INTEGER_OVERFLOW / STATUS_BUFFER_TOO_SMALL; migrates ExAllocatePoolWithTagExAllocatePool2(POOL_FLAG_NON_PAGED).

Behavioral (not directly exploitable, but worth flagging)

Function Sim. Change
WnfEventCallback (sub_1C00018C8) 0.594 Refactor, not a security fix. WNF bitfield parsing plus its existing bounds checks (size >= 8, size >= count + 8) extracted into new helper EapSignalMessageReadFromBuffer (sub_140008a98); the same checks are present inline in the unpatched build (0x1c00019b9, 0x1c0001a41). Does not reach the Finding 1 computation.
Function Sim. Change
BripCreateBrokeredEvent (sub_1C0005340) 0.745 RPC stub swap (data_1c000aab0data_14000b860, procedure index 5 → 0); reordered RtlValidSid / RtlRunOnceExecuteOnce; unified cleanup label.
EaCreateAggregation 0.965 ExAllocatePoolWithTagExAllocatePool2(POOL_FLAG_NON_PAGED); added NULL-check on rdi before cleanup (prevents a null-deref on error path — minor hardening).
BriDeleteBrokeredEvent 0.979 RPC dispatch replaced: UUID-compare path (EapSystemBriDeleteEvent (sub_1c0008a58)EapSystemBriDeleteEvent (sub_140008984) via UuidEqual) and RPC helpers renamed (BripCreateRpcBindingHandle (sub_1c00058c0)/BripMapRpcStatus (sub_1c0005f84)BripCreateRpcBindingHandle (sub_140005ab4)/BripMapRpcStatus (sub_140005dac)).
EaiDuplicateConditionsVisitNode (sub_1C0003840) 0.842 Pool migration; register reallocation (r14rdi); improved copy loop.
EapQueryAggregatedEventConditionPostVisitNode (sub_1C0007760) 0.905 Pool migration; RtlCompareUnicodeString extracted to EapInitConditionParams (sub_140007008); copy-loop stride arithmetic restructured (rax_12*6 vs rax_10*6).

Cosmetic / register-allocation only

  • EACreateAggregateEvent (0.989) — pool migration + stack cookie offset shuffle.
  • EaiProcessNotification (sub_1C0001BC4) (0.942) — register swaps r14↔r15, r12↔r15b, rdi↔rsi; callee renames only (EaiSignalCallback (sub_1c00021d4)EaiSignalCallback (sub_14000413c), etc.).
  • __cpu_features_init (sub_1C0008C80) (0.393) — compiler-generated reordering of CPUID / XGETBV checks; no semantic change.

All remaining ~70 changes are dominantly ExAllocatePoolWithTagExAllocatePool2 migrations and minor control-flow normalization; they do not introduce behavioral differences relevant to security.


9. Unmatched Functions

Direction Count Notes
Added (patched only) 0 New helpers EapSignalContentPrepareEventForPublishing (sub_140008b90), EapSignalMessageReadFromBuffer (sub_140008a98), EapInitConditionParams (sub_140007008) appear inlined/referenced from changed functions but are not listed as standalone unmatched entries — they exist as sub-symbols of patched functions in the diff.
Removed (unpatched only) 0 No sanitizers or checks removed.

Implication: The security-relevant change is the added integer-overflow check in EapSignalContentPrepareEventForPublishing (sub_140008b90). EapSignalMessageReadFromBuffer (sub_140008a98) is a refactor that relocates already-present WNF bounds checks into a shared helper. Nothing protective was removed.


10. Confidence & Caveats

Confidence: High that the change is a genuine integer-overflow fix. The vulnerable 32-bit arithmetic, the missing wrap check, and the patched cmp eax,edi; jb → STATUS_INTEGER_OVERFLOW are all directly confirmed in both builds.

Caveats / assumptions:

  • EaSignalAggregatedEvent is an exported function with no internal callers, so it is reached through its export. The exact RPC procedure/opnum that dispatches into it from user mode, and therefore the privilege level and degree of attacker control over arg8, are not determinable from these binaries.
  • The overflow and the copy length are strictly 32-bit-bounded (arg8 is loaded as a DWORD at 0x1c0003545; the memmove length is the zero-extended r15), so the maximum out-of-bounds write length is ~4 GB, not a 64-bit value.
  • A large arg8 (e.g. 0xFFFFFFF0) drives a very long copy that will fault quickly; choosing arg8 to wrap to a small non-zero total (e.g. 0xFFFFFFE1) yields a short, controlled-length out-of-bounds write.
  • The patch migrates the vulnerable allocation to ExAllocatePool2(POOL_FLAG_NON_PAGED). On the unpatched binary it is ExAllocatePoolWithTag(PagedPool, ...).

Manual verification checklist before PoC:

  1. Disassemble the user-mode Broker Infrastructure client to confirm the RPC opnum that lands in EaSignalAggregatedEvent.
  2. Reverse EaiFindAndAcquireAggregateEvent (sub_1c000251c) (hash-table lookup on arg1) to learn how to construct a valid aggregation context.
  3. Reverse the entry-validation gate to confirm exactly which arg2 event-ID pairs are accepted.
  4. Verify the unpatched pool type / tag at runtime with !poolfind 0x41747645 1.