cea.sys — Integer overflow (CWE-190) leading to kernel pool buffer overflow (CWE-122) in EaSignalAggregatedEvent fixed
KB5082142
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:
- 16 bytes from
arg5(fills the allocation exactly), - 16 bytes from
arg6(first 16 bytes of overflow), arg8bytes (≈ 4 GB) fromarg7viamemmove (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
- 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).
- Create an aggregation event via
EACreateAggregateEventso thatarg1ofEaSignalAggregatedEventis a valid hash-table context resolvable byEaiFindAndAcquireAggregateEvent (sub_1c000251c). - Prepare the call arguments:
arg1= aggregation context handle.arg2= pointer to a 64-bit event-ID pair;*(uint32_t*)arg2and*((uint32_t*)arg2 + 1)must not both be zero (validation gate at function entry).arg5= non-NULL pointer to 16 bytes (forces+0x10to size).arg6= non-NULL pointer to 16 bytes (forces+0x10to size).arg7= non-NULL pointer to variable-length data buffer.arg8=0xFFFFFFF0(causes(0xFFFFFFF0 + 0x10 + 0x10) & 0xFFFFFFFF == 0x10).- Issue the call via the Broker Infrastructure RPC dispatch (
NdrClientCall3). No race-condition ordering is required — the path is synchronous. - Confirm the bug fired:
- At
0x1c00035df,EBX == 0x10whileR15D == 0xFFFFFFF0. - At
0x1c00035f0,ExAllocatePoolWithTagallocates 16 bytes. - At
0x1c0003645, the driver copiesarg8bytes 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+arg616-byte blocks copied via the twomovdquwrites). - The remaining bytes come from the
arg7buffer, copied bymemmove (sub_1c0008d80)at0x1c0003645. - 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. |
0x140008c0e–0x140008c10 (patched) |
cmp eax,edi; jb |
Added overflow check — absent in unpatched. |
Trigger setup from user mode
- Load
cea.sysis auto-loaded by Windows; no manual load needed. - Use the Broker Infrastructure user-mode API to:
- Create an aggregation context (call
EACreateAggregateEvent). - Call
EaSignalAggregatedEvent(viaNdrClientCall3) 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(or0xFFFFFFE1for 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: at0x1c00035df,ebx = 0x10. Allocation of 16 bytes succeeds.movdquat0x1c0003624fills it exactly.movdquat0x1c0003636writes 16 bytes past the end.memmove (sub_1c0008d80)at0x1c0003645copiesarg8bytes past the allocation — an out-of-bounds kernel pool write. - Use
!pool @rcxafter the overflow to inspect corrupted headers;!poolfind 0x41747645 1to 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 (r8at0x1c0003645) 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 ExAllocatePoolWithTag → ExAllocatePool2(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_1c000aab0 → data_14000b860, procedure index 5 → 0); reordered RtlValidSid / RtlRunOnceExecuteOnce; unified cleanup label. |
EaCreateAggregation |
0.965 | ExAllocatePoolWithTag → ExAllocatePool2(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 (r14→rdi); 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 swapsr14↔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 ExAllocatePoolWithTag → ExAllocatePool2 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:
EaSignalAggregatedEventis 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 overarg8, are not determinable from these binaries.- The overflow and the copy length are strictly 32-bit-bounded (
arg8is loaded as a DWORD at0x1c0003545; thememmovelength is the zero-extendedr15), 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; choosingarg8to 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 isExAllocatePoolWithTag(PagedPool, ...).
Manual verification checklist before PoC:
- Disassemble the user-mode Broker Infrastructure client to confirm the RPC opnum that lands in
EaSignalAggregatedEvent. - Reverse
EaiFindAndAcquireAggregateEvent (sub_1c000251c)(hash-table lookup onarg1) to learn how to construct a valid aggregation context. - Reverse the entry-validation gate to confirm exactly which
arg2event-ID pairs are accepted. - Verify the unpatched pool type / tag at runtime with
!poolfind 0x41747645 1.