1. Overview

  • Unpatched Binary ID: irenum_unpatched.sys
  • Patched Binary ID: irenum_patched.sys
  • Overall Similarity Score: 0.9927
  • Diff Statistics: 36 matched functions, 1 changed function, 35 identical functions, 0 unmatched functions (both directions).
  • Verdict: The single code change adds a memset that zero-initializes the driver's registry-path pool buffer immediately after allocation. In the unpatched build the same buffer is already fully written by the subsequent copy plus wide null-terminator in the normal (even-length) path, and the buffer is never exposed to user mode. The change is redundant defensive hardening, not a delivered security fix. No reachable information-disclosure vulnerability is demonstrable.

2. Change Summary

  • Severity: None (informational).
  • Nature of Change: Redundant zero-initialization of a freshly allocated pool buffer (defense-in-depth hardening).
  • Affected Function: DriverEntry @ 0x1C0008008.

What the code does: DriverEntry (0x1C0008008) receives the driver's RegistryPath (a UNICODE_STRING*) in rdx. It reads RegistryPath->Length (a byte count), rejects lengths >= 0xFFFD, computes NumberOfBytes = Length + 2, and allocates that many bytes of PagedPool via ExAllocatePoolWithTag (tag 'IrEn' = 0x6E457249). The pointer is stored in global P (0x1C0003028) and the size in global word_1C0003022. It then copies Length bytes of the registry path into the buffer with memmove and writes a wide (2-byte) null terminator.

Why this is not a reachable information leak: The allocation is Length + 2 bytes. The memmove writes bytes [0, Length). The wide null-terminator write mov [rdi+rax*2], r14w (with rax = Length >> 1, r14w = 0) stores a 2-byte zero at offset 2 * (Length / 2). For any even Length this offset equals Length, so bytes [Length, Length+2) are zeroed and the copy plus terminator together cover the entire Length + 2 allocation. RegistryPath->Length is the byte length of a well-formed UNICODE_STRING supplied by the OS to DriverEntry; it is always even and is not attacker-controllable to an odd value. Consequently, in the unpatched build the buffer contains no uninitialized bytes in the path that actually runs.

Furthermore, the buffer is used only as the null-terminated absolute registry path argument to RtlQueryRegistryValues (to read the BreakOnEntry, DebugFlags, and EnumStaticDevice values under the service key). It is not returned through any dispatch/IOCTL path and is not copied to user mode. DriverEntry runs once at load, in system-thread context at PASSIVE_LEVEL, and is not reachable from an untrusted request surface.

The patched build's added memset zeroes the full NumberOfBytes before the copy. Because the copy plus terminator already initialize the whole buffer for even lengths, the memset is redundant. It is a defensive hardening pattern (zero-on-allocate), not a fix for a demonstrable defect.

3. Pseudocode Diff

// UNPATCHED DriverEntry (@ 0x1C0008008):
uint16_t Length = RegistryPath->Length;          // byte count, always even
if (Length < 0xFFFD) {
    DriverEntryRegPath = Length;                  // global 0x1C0003020
    word_1C0003022 = Length + 2;                  // global NumberOfBytes
    P = ExAllocatePoolWithTag(PagedPool, Length + 2, 0x6E457249); // 'IrEn'
}
if (Length >= 0xFFFD || !P)
    return 0xC000009A;                            // STATUS_UNRECOGNIZED_VOLUME
else {
    memmove(P, RegistryPath->Buffer, Length);     // copies Length bytes
    *((_WORD *)P + (Length >> 1)) = 0;            // wide null-term at offset 2*(Length/2)
    // for even Length, [0,Length) + [Length,Length+2) == full allocation
    // ... memset(&QueryTable, 0, 0xE0); RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE, P, ...)
}

// PATCHED DriverEntry (@ 0x1C0008008):
else {
    memset(P, 0, (uint16_t)word_1C0003022);       // ADDED: zero full NumberOfBytes (redundant)
    memmove(P, RegistryPath->Buffer, Length);
    *((_WORD *)P + (Length >> 1)) = 0;
    // ... memset(&QueryTable, 0, 0xE0); RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE, P, ...)
}

4. Assembly Analysis

Unpatched sequence (0x1C00080840x1C00080BF):

00000001C0008084  mov     cs:P, rax                 ; global P = Path
00000001C000808B  mov     rdi, rax                  ; rdi = Path
00000001C000808E  test    rax, rax
00000001C0008091  jz      loc_1C0008231             ; null check
00000001C0008097  movzx   r8d, word ptr [rsi]       ; Size = RegistryPath->Length
00000001C000809B  mov     rcx, rax                  ; dst = Path
00000001C000809E  mov     rdx, [rsi+8]              ; src = RegistryPath->Buffer
00000001C00080A2  call    memmove                   ; copy Length bytes
00000001C00080A7  movzx   eax, word ptr [rsi]       ; eax = Length
00000001C00080AF  shr     rax, 1                    ; rax = Length/2
00000001C00080BA  mov     [rdi+rax*2], r14w         ; Path[2*(Length/2)] = 0 (wide null-term)
00000001C00080BF  call    memset                    ; memset(&QueryTable, 0, 0xE0) -- zeroes the local RTL_QUERY_REGISTRY_TABLE, not Path

Patched sequence (0x1C00080840x1C00080D1):

00000001C0008084  mov     cs:P, rax                 ; global P = Path
00000001C000808B  mov     rdi, rax                  ; rdi = Path
00000001C000808E  test    rax, rax
00000001C0008091  jz      loc_1C0008243             ; null check
00000001C0008097  movzx   r8d, cs:word_1C0003022    ; Size = NumberOfBytes (Length+2)
00000001C000809F  xor     edx, edx                  ; Val = 0
00000001C00080A1  mov     rcx, rax                  ; dst = Path
00000001C00080A4  call    memset                    ; memset(Path, 0, Length+2) -- ADDED
00000001C00080A9  movzx   r8d, word ptr [rsi]       ; Size = RegistryPath->Length
00000001C00080AD  mov     rcx, rdi                  ; dst = Path
00000001C00080B0  mov     rdx, [rsi+8]              ; src = RegistryPath->Buffer
00000001C00080B4  call    memmove                   ; copy Length bytes
00000001C00080B9  movzx   eax, word ptr [rsi]       ; eax = Length
00000001C00080C1  shr     rax, 1                    ; rax = Length/2
00000001C00080CC  mov     [rdi+rax*2], r14w         ; Path[2*(Length/2)] = 0 (wide null-term)
00000001C00080D1  call    memset                    ; memset(&QueryTable, 0, 0xE0)

The only functional difference is the four instructions at 0x1C00080970x1C00080A4 that add memset(Path, 0, NumberOfBytes) before the copy. rdi already held Path in both builds; it is reused as the copy destination after the added memset, so no register-preservation change was required by the copy itself.

5. Reachability Assessment

  • DriverEntry (0x1C0008008) is the driver initialization routine. It is invoked once, at driver load, by the entry stub GsDriverEntry (0x1C0008260), which first calls __security_init_cookie and then DriverEntry.
  • The registry-path buffer at global P (0x1C0003028) is consumed only by RtlQueryRegistryValues as a null-terminated absolute registry path. No dispatch routine, IOCTL handler, or WMI/PnP path in either build reads back or returns this buffer to user mode.
  • The routine runs in system-thread context at PASSIVE_LEVEL during load. It is not reachable from an untrusted caller.
  • There is no attacker-controlled input on this path: RegistryPath is supplied by the OS and its Length is a valid even UNICODE_STRING byte count.

6. Impact Assessment

  • No exploit primitive is demonstrable. In the normal (even-length) path the unpatched buffer is fully initialized by the copy plus the wide null-terminator, so no uninitialized pool bytes exist to disclose. Even absent that, the buffer is never surfaced to user mode.
  • The added memset is a standard zero-on-allocate hardening pattern with no reachable behavioral effect for the inputs that actually occur.

7. Evidence Notes

  • word_1C0003022 (0x1C0003022) holds NumberOfBytes = Length + 2, set at 0x1C00080640x1C0008071; the added memset reads it as its size argument at 0x1C0008097.
  • The pre-existing memset at the unpatched 0x1C00080BF / patched 0x1C00080D1 zeroes the local RTL_QUERY_REGISTRY_TABLE structure (size 0xE0), not the path buffer, in both builds.
  • The copy routine at 0x1C0001B00 is memmove; the fill routine at 0x1C0001E40 is memset.
  • Image base is 0x1C0000000.

8. Changed Functions — Full Triage

  • DriverEntry (0x1C0008008, Similarity: 0.9798, Change Type: defensive_hardening)
  • Behavioral Change: Added memset(Path, 0, NumberOfBytes) (0x1C00080A4) to zero the full registry-path allocation before the copy.
  • Security Relevance: None. The buffer is already fully written by the copy plus wide null-terminator for the even lengths that actually occur, and it is never exposed to user mode. The change is redundant zero-init.

9. Unmatched Functions

  • Removed: None.
  • Added: None.

10. Independent Diff Confirmation

An independent content-level diff of both builds (matching by content across relocations, not by reused address) confirms DriverEntry (0x1C0008008) is the only function that changed. The remaining functions are byte-identical after accounting for the address shift caused by the four inserted instructions. The entry stub GsDriverEntry moved from 0x1C0008260 to 0x1C0008280 purely as a result of that shift; its body is unchanged. No omitted security-relevant change was found.

11. Confidence & Caveats

  • Confidence: High that the sole change is the added memset and that it is redundant defensive hardening rather than a delivered security fix.
  • Basis: For an even RegistryPath->Length the unpatched copy plus wide null-terminator write cover the entire Length + 2 allocation, leaving no uninitialized bytes; the buffer is not exposed to user mode; and the routine is init-only and not attacker-reachable.