TRIAGE OUTPUT: Serialised(
FileTriage {
file_name: "C:\\Users\\flux\\Downloads\\test\\drv.bin",
hypotheses: [
Hypothesis {
hypothesis_title: "Arbitrary writes to mapped VGA memory via METHOD_BUFFERED IOCTLs",
description_of_hypothesis: "The driver exposes multiple IOCTLs that accept user-supplied coordinates and pixel/bitmap data, then write directly into a shared VGA memory mapping at 0xA0000. Several handlers perform only coarse bounds checks on the destination rectangle but do not validate the source buffer length against the amount actually consumed for the requested width/height, creating a plausible kernel memory corruption or arbitrary-write condition within the mapped hardware buffer region if the caller can reach these IOCTLs with crafted parameters.",
bug_class: KernelArbitraryReadOrWrite,
primary_attack_surface: "DeviceIoControl on the device created by IoCreateDevice / symbolic link in entry()",
path_and_state_to_reach_vulnerability: "Open the device created in entry(); issue IOCTL 0x22c048, 0x22c04c, 0x22c050, or 0x22c054 with a sufficiently large input buffer to satisfy the superficial size gate, while supplying coordinates and dimensions that drive the bitmap write loops in FUN_00011d70, FUN_00011c10, FUN_00011f10, or FUN_00012140. The mapping must already be initialized by IOCTL 0x22c040, which calls FUN_00011a70 and MmMapIoSpace(0xA0000,0x10000).",
why_plausible: "These handlers compute write addresses from user-controlled x/y values and then repeatedly dereference and write through pointers derived from DAT_00013050. The code adjusts width/height to fit the display, but the source-pointer advancement logic is only partially validated, and one path (FUN_00011d70) consumes packed pixel data using param_5 while the IOCTL layer only checks a coarse input size threshold. The combination of direct memory writes and insufficient per-row byte accounting is a classic corruption surface.",
any_evidence_to_the_contrary: "The writes are confined to the VGA aperture mapped at 0xA0000, so this does not obviously become a write-what-where outside that region. The target looks like a boot/display utility rather than a general-purpose privileged driver, which may reduce practical exploitability. No explicit user-mode pointer dereference or copy into arbitrary kernel buffers was found in the inspected code.",
potentially_vulnerable_function: "FUN_0001232b / dispatch at 000122d0, especially FUN_00011d70, FUN_00011c10, FUN_00011f10, and FUN_00012140",
potentially_vulnerable_ioctl_if_relevant: "0x22c048, 0x22c04c, 0x22c050, 0x22c054",
severity: High,
likelihood_of_exploitability: Medium,
next_step: Conclude,
},
Hypothesis {
hypothesis_title: "Weak device access control on a directly exposed kernel device",
description_of_hypothesis: "The driver creates a device object and symbolic link using IoCreateDevice and IoCreateSymbolicLink, but no security descriptor, IoCreateDeviceSecure, ACL setup, or privilege check is visible in the analyzed entry path. If the device is reachable from unprivileged user mode, the IOCTL surface becomes broadly accessible and the display-memory write primitives may be triggerable by low-privilege callers.",
bug_class: KernelWeakIOCTLAccessControl,
primary_attack_surface: "Named device interface created in entry()",
path_and_state_to_reach_vulnerability: "Load the driver, open the symbolic link created in entry(), and send any of the supported IOCTLs directly from user mode. The code shown does not gate access on caller privileges or per-IOCTL access masks beyond standard device open semantics.",
why_plausible: "The initialization path is minimal: create device, stash the driver object, assign dispatch routines, create symlink. There is no visible security initialization or access-control logic. For a driver with direct hardware-writing IOCTLs, absence of explicit restrictions is noteworthy because it determines whether the attack surface is reachable at all.",
any_evidence_to_the_contrary: "The underlying device may inherit restrictive defaults from the OS or from the device type/driver installation context, but that is not evidenced in the binary. No code proving admin-only gating or secure DACLs was found.",
potentially_vulnerable_function: "entry at 000119d0",
potentially_vulnerable_ioctl_if_relevant: "All exposed IOCTLs, especially 0x22c040 through 0x22c058",
severity: Medium,
likelihood_of_exploitability: Medium,
next_step: Conclude,
},
Hypothesis {
hypothesis_title: "Unsafe use of MmMapIoSpace on fixed physical address without validation",
description_of_hypothesis: "The driver maps the physical VGA region at 0xA0000 with MmMapIoSpace in FUN_00011a70 and stores the returned virtual address globally. The mapping is later used for direct writes by the IOCTL handlers. The code does not validate the physical range beyond hardcoded constants, and there is no visible failure-hardening beyond a null check, making this a plausible kernel physical memory mapping risk if the environment or assumptions differ from standard VGA hardware.",
bug_class: KernelPhysicalMemoryMapping,
primary_attack_surface: "Initialization IOCTL 0x22c040 and subsequent graphics IOCTLs",
path_and_state_to_reach_vulnerability: "Call IOCTL 0x22c040 to invoke FUN_00011a70, which checks InbvIsBootDriverInstalled and InbvResetDisplay before calling MmMapIoSpace(0xA0000,0x10000,0). If successful, later IOCTLs write through the returned mapping.",
why_plausible: "Direct physical memory mapping is inherently sensitive in kernel drivers. The fixed address and size are hardcoded, and the mapped pointer is then reused globally without apparent lifetime tracking beyond a simple unmap path. If this code runs on unexpected platforms or with incompatible boot/display state, it could create undefined behavior or unintended physical access.",
any_evidence_to_the_contrary: "The mapping target is the conventional VGA aperture, which is a legitimate use case for display/boot drivers. The code does unmap in FUN_00011b60 and gates initialization on boot-driver state, so this looks more like fragile legacy hardware access than an immediate vulnerability.",
potentially_vulnerable_function: "FUN_00011a70 and FUN_00011b60",
potentially_vulnerable_ioctl_if_relevant: "0x22c040 and 0x22c044",
severity: Low,
likelihood_of_exploitability: Low,
next_step: Conclude,
},
Hypothesis {
hypothesis_title: "Potential out-of-bounds source read in packed bitmap blit path",
description_of_hypothesis: "The packed-pixel blit routine FUN_00011d70 advances its source pointer based on the requested width using param_5 = param_5 + (param_3 + 1 >> 1), while the IOCTL layer only requires param_4 > 0x17 and does not appear to validate that the input buffer actually contains the necessary number of bytes for the requested rectangle. This creates a plausible out-of-bounds read from the caller-provided buffer during pixel expansion.",
bug_class: KernelUncheckedUserPointer,
primary_attack_surface: "IOCTL 0x22c048 via the pixel blit path",
path_and_state_to_reach_vulnerability: "Submit IOCTL 0x22c048 with a structure that passes the 0x17-byte size check, sets coordinates within the active display bounds, and requests a width/height combination that causes the loop in FUN_00011d70 to consume more packed bytes than the input buffer contains.",
why_plausible: "The function dereferences param_5 repeatedly and increments it according to the requested width without any explicit per-row source-length parameter. In a METHOD_BUFFERED design, this would typically be safe only if the I/O manager provided a sufficiently large buffer and the handler validated it exactly; neither is shown here. If the buffer is too small, the code may read past the input region.",
any_evidence_to_the_contrary: "The handler may be using METHOD_BUFFERED semantics and the I/O manager may guarantee a nonpaged system buffer that is at least as large as the user-supplied input length. However, the decompilation does not show the IRP stack handling or explicit use of an input length field, so the actual reachability of an OOB read remains uncertain.",
potentially_vulnerable_function: "FUN_00011d70",
potentially_vulnerable_ioctl_if_relevant: "0x22c048",
severity: Medium,
likelihood_of_exploitability: Low,
next_step: Conclude,
},
Hypothesis {
hypothesis_title: "No strong evidence for classic memcpy/memmove-based stack or heap overflow",
description_of_hypothesis: "I did not find imports for memcpy, memmove, RtlCopyMemory, ZwMapViewOfSection, MmGetSystemRoutineAddress, PsLookupProcessByProcessId, ZwOpenProcess, or SePrivilegeCheck, and the inspected dispatch path consists mainly of fixed-format display IO rather than general-purpose copying or process manipulation. Based on the current evidence, a classic user-controlled memcpy-style overflow is not supported.",
bug_class: GenericMemorySafety,
primary_attack_surface: "N/A",
severity: Medium,
likelihood_of_exploitability: Low,
next_step: Conclude,
},
Hypothesis {
hypothesis_title: "No strong evidence for classic memcpy/memmove-based stack or heap overflow",
description_of_hypothesis: "I did not find imports for memcpy, memmove, RtlCopyMemory, ZwMapViewOfSection, MmGetSystemRoutineAddress, PsLookupProcessByProcessId, ZwOpenProcess, or SePrivilegeCheck, and the inspected dispatch path consists mainly of fixed-format display IO rather than general-purpose copying or process manipulation. Based on the current evidence, a classic user-controlled memcpy-style overflow is not supported.",
bug_class: GenericMemorySafety,
primary_attack_surface: "N/A",
},
Hypothesis {
hypothesis_title: "No strong evidence for classic memcpy/memmove-based stack or heap overflow",
description_of_hypothesis: "I did not find imports for memcpy, memmove, RtlCopyMemory, ZwMapViewOfSection, MmGetSystemRoutineAddress, PsLookupProcessByProcessId, ZwOpenProcess, or SePrivilegeCheck, and the inspected dispatch path consists mainly of fixed-format display IO rather than general-purpose copying or process manipulation. Based on the current evidence, a classic user-controlled memcpy-style overflow is not supported.",
bug_class: GenericMemorySafety,
primary_attack_surface: "N/A",
ilegeCheck, and the inspected dispatch path consists mainly of fixed-format display IO rather than general-purpose copying or process manipulation. Based on the current evidence, a classic user-controlled memcpy-style overflow is not supported.",
bug_class: GenericMemorySafety,
primary_attack_surface: "N/A",
primary_attack_surface: "N/A",
path_and_state_to_reach_vulnerability: "No credible path established from the currently inspected code.",
why_plausible: "The driver is small and uses global state plus IOCTL dispatch, so memory-safety bugs were initially plausible. However, the concrete code seen so far does not show the expected primitive patterns for stack/heap overflows in common driver exploit classes.",
any_evidence_to_the_contrary: "The code uses tightly bounded loops for the display surface and explicitly clamps width/height to the display size. No memcpy-like import was present, and no obvious buffer copy function was found in the decompiled paths reviewed.",
potentially_vulnerable_function: "None identified with sufficient evidence",
potentially_vulnerable_ioctl_if_relevant: "None identified",
severity: Low,
likelihood_of_exploitability: Low,
next_step: Conclude,
},
],
next_step: Conclude,
number_of_iterations: 1,
},
)
NOte the model for some reason is adding hypothesis that it says dont exist lol, I checked this wasnt a prompt issue as I re-ran against Claude and that was fine. Idk
TRIAGE OUTPUT: Serialised(
FileTriage {
file_name: "C:\\Users\\flux\\Downloads\\test\\drv.bin",
hypotheses: [
Hypothesis {
hypothesis_title: "Untrusted IOCTL input can directly drive low-level VGA memory writes without any user-pointer validation, creating a kernel memory corruption risk",
description_of_hypothesis: "The dispatcher at 000122d0 routes IOCTL 0x22c048 to FUN_00011d70 and IOCTL 0x22c04c to FUN_00011c10, passing fields read from the caller-supplied buffer. The decompiled helpers dereference the trailing buffer pointer argument directly: FUN_00011d70 uses param_5 as a byte* source and FUN_00011f10 writes output through param_5, while the dispatcher only checks that the input buffer is non-null and large enough. I did not find any ProbeForRead, ProbeForWrite, SEH wrapper, or handle-based validation around that embedded pointer. In kernel mode, that is a credible unchecked-user-pointer path.",
bug_class: KernelUncheckedUserPointer,
primary_attack_surface: "Device object \\Device\\BioNT_BS via symbolic link \\DosDevices\\BioNT_BS and IOCTLs handled in FUN_000122d0",
path_and_state_to_reach_vulnerability: "Open the device and issue IOCTL 0x22c048 or 0x22c050 with a valid-size buffer. Populate the structure so that the embedded pointer argument passed to the bitmap-copy helper points at attacker-controlled user memory. The helpers will then read from that pointer while writing into the mapped VGA region at DAT_00013050.",
why_plausible: "The code explicitly dereferences attacker-influenced pointer fields in kernel context. The only guards are size checks on the input buffer and coordinate clipping on the destination area; those do not verify the provenance or safety of param_5. The presence of direct pointer dereferences inside the helper functions makes the bug materially plausible.",
any_evidence_to_the_contrary: "The destination writes are clipped to the mapped display dimensions, so this is not an arbitrary write to unrestricted kernel memory. However, that clipping does not mitigate unsafe access to the source pointer. No contrary evidence was found in the decompiled code.",
potentially_vulnerable_function: "FUN_000122d0, FUN_00011d70, FUN_00011f10",
potentially_vulnerable_ioctl_if_relevant: "0x22c048 and 0x22c050",
severity: High,
likelihood_of_exploitability: High,
next_step: Conclude,
},
Hypothesis {
hypothesis_title: "IOCTL 0x22c058 may allow malformed palette input to be read past its intended structure boundaries",
description_of_hypothesis: "In case 0x22c058 of FUN_000122d0, the driver interprets the user buffer as a palette table, validates a count field with several range checks, and then iterates over param_3 + 2 while consuming three dwords per entry. Although the code checks (ulonglong)uVar3 * 3 + 8 <= param_4, it still derives pbVar1 and pbVar2 from puVar7 and reads them repeatedly without a dedicated structure parser. If the caller supplies a buffer that satisfies the arithmetic checks but does not actually match the assumed layout, the routine could read beyond the intended logical data and feed stale or attacker-chosen bytes into the VGA DAC programming path.",
bug_class: KernelArbitraryReadOrWrite,
primary_attack_surface: "Device object \\Device\\BioNT_BS via IOCTL 0x22c058",
path_and_state_to_reach_vulnerability: "Send IOCTL 0x22c058 with a buffer whose count field passes the size predicates and whose trailing bytes are arranged to confuse the triplet iteration. The code will walk the buffer as a sequence of palette entries and perform port writes based on those reads.",
why_plausible: "The routine performs nontrivial pointer arithmetic over untrusted input and uses the derived bytes directly in hardware programming. The structure assumptions are implicit in code rather than enforced by a schema or dedicated validation routine.",
any_evidence_to_the_contrary: "The checks on count and input length are fairly strict and reduce the chance of a simple overflow. The decompilation did not show an obvious linear overrun. As a result, this remains a weaker hypothesis than the unchecked-pointer issue.",
potentially_vulnerable_function: "FUN_000122d0 case 0x22c058",
potentially_vulnerable_ioctl_if_relevant: "0x22c058",
severity: Medium,
likelihood_of_exploitability: Medium,
next_step: Conclude,
},
Hypothesis {
hypothesis_title: "The driver exposes privileged VGA memory mapping and direct port I/O, but the current code does not show a direct attacker-controlled physical mapping",
description_of_hypothesis: "The initialization routine FUN_00011a70 maps the fixed VGA framebuffer physical range 0xA0000-0xAFFFF with MmMapIoSpace and the helper routines issue raw port I/O to 0x3ce/0x3cf and 0x3c8/0x3c9. This is a high-risk capability because it manipulates hardware memory and registers directly from kernel mode. However, the mapped address is fixed in the code and the visible guards require successful initialization and coordinate bounds checks before writes occur. I did not find evidence that a caller can choose arbitrary physical addresses or bypass those guards.",
bug_class: KernelPhysicalMemoryMapping,
primary_attack_surface: "Kernel driver initialization and display-manipulation IOCTLs",
path_and_state_to_reach_vulnerability: "Use IOCTL 0x22c040 to trigger FUN_00011a70, which maps the VGA region after boot-driver/display ownership checks. Subsequent IOCTLs 0x22c048/0x22c04c/0x22c050/0x22c054/0x22c058 operate on the mapped region and port registers.",
why_plausible: "Direct physical memory mapping in a kernel driver is inherently sensitive and can be exploitable if the surrounding validation is wrong. The code is intentionally low-level and stateful, so it deserves attention even if no concrete flaw is yet proven.",
any_evidence_to_the_contrary: "The physical address is hard-coded and not user supplied. The helpers clip coordinates against width/height before writing, which limits the write region. That makes an attacker-controlled arbitrary physical mapping unlikely from the current evidence.",
potentially_vulnerable_function: "FUN_00011a70, FUN_00011c10, FUN_00011d70, FUN_00011f10, FUN_00012140",
potentially_vulnerable_ioctl_if_relevant: "0x22c040, 0x22c048, 0x22c04c, 0x22c050, 0x22c054, 0x22c058",
severity: Medium,
likelihood_of_exploitability: Low,
next_step: Conclude,
},
Hypothesis {
hypothesis_title: "Display state is global and unsynchronized, but no concrete race or double-unmap was demonstrated",
description_of_hypothesis: "FUN_00011a70 and FUN_00011b60 manage global state variables DAT_00013040 and DAT_00013050 across initialization and teardown, while the dispatcher exposes separate IOCTLs for setup and cleanup. There is no visible locking around these globals. In a kernel driver that is callable from multiple threads, that can be a race-condition pattern. Nonetheless, the decompiled code does not show concurrent access logic, interlocked operations, or a clear mismatch between initialization and cleanup that would demonstrate an actual bug.",
bug_class: RaceCondition,
primary_attack_surface: "Internal display state transitions via IOCTL 0x22c040 and 0x22c044",
path_and_state_to_reach_vulnerability: "Call IOCTL 0x22c040 to initialize the display path and 0x22c044 to tear it down, potentially interleaving requests from multiple threads. If the global state is inconsistent, cleanup could run against stale state or skip unmapping.",
why_plausible: "The code uses shared mutable global state without apparent synchronization, which is a common source of kernel races.",
why_plausible: "The code uses shared mutable global state without apparent synchronization, which is a common source of kernel races.",
any_evidence_to_the_contrary: "The cleanup path only unmaps when the state flag is set, and no actual race window or exploitable interleaving was observed in the available code. This mak any_evidence_to_the_contrary: "The cleanup path only unmaps when the state flag is set, and no actual race window or exploitable interleaving was observed in the available code. This makes the hypothesis speculative.",
potentially_vulnerable_function: "FUN_00011a70 and FUN_00011b60",
potentially_vulnerable_ioctl_if_relevant: "0x22c040 and 0x22c044",
severity: Low,
likelihood_of_exploitability: Low,
next_step: Conclude,
},
],
next_step: Conclude,
number_of_iterations: 3,
},
)
It gave up without even doing initial triage??!
Cost ~ 40p I think.
TRIAGE OUTPUT: Serialised(
FileTriage {
file_name: "C:\\Users\\flux\\git\\ghidra-mcp\\docker\\binaries\\drv.bin",
hypotheses: [
Hypothesis {
hypothesis_title: "Arbitrary physical memory read/write via IOCTL 0x220014 using MmMapIoSpace and attacker-chosen destination pointer",
description_of_hypothesis: "The device dispatch routine FUN_00011260 handles IRP_MJ_DEVICE_CONTROL for a user-accessible device created in entry() via IoCreateDevice followed by IoCreateSymbolicLink. In case 0x220014, if InputBufferLength == 0x10, it interprets the system buffer as four DWORDs: a physical address (*puVar5), a size (puVar5[1]), and an attacker-controlled destination pointer (*(undefined8 *)(puVar5 + 2)). It calls MmMapIoSpace(*puVar5, puVar5[1], 0), then immediately calls FUN_00012600(attacker_dst, mapped_physical, size), then MmUnmapIoSpace. There is no validation of the physical address range, size, or destination pointer. This gives a caller the ability to read arbitrary physical memory and copy it to an arbitrary virtual address, which can target kernel memory.",
bug_class: KernelPhysicalMemoryMapping,
primary_attack_surface: "DeviceIoControl to the symbolic link created by IoCreateSymbolicLink in entry(); specifically IRP_MJ_DEVICE_CONTROL case 0x220014 in FUN_00011260",
path_and_state_to_reach_vulnerability: "Open the device object exposed by the driver, send IOCTL 0x220014 with a 16-byte METHOD_BUFFERED input buffer containing {PhysicalAddressLow, Size, DestinationPointerLow, DestinationPointerHigh}. FUN_00011260 maps the supplied physical address with MmMapIoSpace and copies Size bytes from the mapped region into the supplied 64-bit destination pointer using FUN_00012600. No special state is required beyond access to the device.",
why_plausible: "Decompiled code in FUN_00011260 case 0x220014 is explicit: 'lVar9 = MmMapIoSpace(*puVar5,puVar5[1],0); ... FUN_00012600(*(undefined8 *)(puVar5 + 2),lVar9,puVar5[1]);'. FUN_00012600 is a memmove-like implementation that copies exactly param_3 bytes from source param_2 to destination param_1. The user controls both the physical source and destination virtual address through the IOCTL input, and there are no access-control or range checks in this path.",
any_evidence_to_the_contrary: "The decompilation does not show the device object's security descriptor, so practical reachability depends on who can open the device. Also, because the IOCTL appears to use METHOD_BUFFERED semantics, the destination pointer is not the system buffer itself but an embedded pointer value, meaning exploitation requires knowledge of a writable kernel virtual address. Neither point negates the primitive visible in code.",
potentially_vulnerable_function: "FUN_00011260",
potentially_vulnerable_ioctl_if_relevant: "0x220014",
severity: Critical,
likelihood_of_exploitability: High,
next_step: Conclude,
},
Hypothesis {
hypothesis_title: "Unsafe use of attacker-supplied kernel pointer in IOCTL 0x220014 enables arbitrary kernel write primitive",
description_of_hypothesis: "The same IOCTL 0x220014 path not only maps physical memory but also trusts an embedded 64-bit pointer from the request buffer as the destination of a bulk copy. Because FUN_00012600 performs a raw memmove-style copy without probing or restricting the destination, a caller can direct the driver to write arbitrary bytes from mapped physical memory into chosen kernel virtual memory addresses.",
bug_class: KernelArbitraryReadOrWrite,
primary_attack_surface: "DeviceIoControl on the driver's device for IOCTL 0x220014 in FUN_00011260",
path_and_state_to_reach_vulnerability: "Reach FUN_00011260 through IRP_MJ_DEVICE_CONTROL, provide a 16-byte buffer accepted by case 0x220014, and place a chosen kernel virtual address in the third/fourth DWORDs of the buffer. The driver maps a user-chosen physical page and copies puVar5[1] bytes into the chosen destination pointer.",
why_plausible: "The decompiled logic directly passes '*(undefined8 *)(puVar5 + 2)' into FUN_00012600 as the destination pointer. FUN_00012600 contains only copy logic and no pointer validation, probing, or exception handling. This is a classic unchecked embedded-pointer misuse in kernel code.",
any_evidence_to_the_contrary: "This primitive depends on the caller knowing a useful writable kernel address and on the device being openable from the attacker's context. The code shown does not reveal a discretionary access control list or whether other surrounding OS mitigations would limit practical use.",
potentially_vulnerable_function: "FUN_00011260",
potentially_vulnerable_ioctl_if_relevant: "0x220014",
severity: Critical,
likelihood_of_exploitability: High,
next_step: Conclude,
},
Hypothesis {
hypothesis_title: "Unauthenticated system crash/reboot trigger via IOCTL 0x22000c",
description_of_hypothesis: "FUN_00011260 handles IOCTL 0x22000c by directly invoking func_0x000125e6(3). Given the imported HalReturnToFirmware and the surrounding boot/video-oriented functionality, this helper is likely a reboot or firmware-return path. The code performs no input validation, privilege check, or caller authorization before invoking it, making a denial-of-service path highly plausible.",
bug_class: GenericMemorySafety,
primary_attack_surface: "DeviceIoControl to IOCTL 0x22000c in FUN_00011260",
path_and_state_to_reach_vulnerability: "Open the device exposed by entry() and issue IOCTL 0x22000c. FUN_00011260 immediately calls func_0x000125e6(3) and completes the IRP without any gating logic.",
why_plausible: "The switch case is extremely short and unconditional: 'case 0x22000c: func_0x000125e6(3); break;'. Imports include HalReturnToFirmware and multiple boot-video APIs, which strongly suggests this driver exposes privileged machine-control operations. Even if the exact callee target was not resolved, the code clearly gives any successful caller access to a powerful internal routine.",
any_evidence_to_the_contrary: "The exact behavior of func_0x000125e6 is not decompiled in this sample, so I cannot conclusively state whether it reboots, powers off, or performs some other privileged operation. This is therefore an interesting and likely DoS-capable path, but less certain than the MmMapIoSpace issue.",
potentially_vulnerable_function: "FUN_00011260",
potentially_vulnerable_ioctl_if_relevant: "0x22000c",
severity: Medium,
likelihood_of_exploitability: Medium,
next_step: Conclude,
},
Hypothesis {
hypothesis_title: "Potential weak access control on privileged proxy IOCTLs that forward requests to arbitrary file handles",
description_of_hypothesis: "FUN_00011260 exposes IOCTLs 0x220018, 0x22002c, and 0x220030 that accept a user-supplied handle in the input buffer, resolve it with ObReferenceObjectByHandle, obtain the related device object, and forward either synchronous read requests or device control requests to that device. The driver itself does not apply any explicit policy checks before forwarding. This creates a plausible capability-bypass surface if the driver device is accessible to lower-privileged callers and if the forwarded handle can reference objects that the caller should not be able to drive in this manner.",
bug_class: KernelDangerousObjectOperation,
primary_attack_surface: "DeviceIoControl to IOCTLs 0x220018, 0x22002c, and 0x220030 in FUN_00011260",
path_and_state_to_reach_vulnerability: "Open this driver, pass a structured input buffer containing a handle and I/O parameters, and let FUN_00011260 call ObReferenceObjectByHandle on the supplied handle, then IoGetRelatedDeviceObject, then IoBuildSynchronousFsdRequest or IoBuildDeviceIoControlRequest followed by IofCallDriver. The request is sent using the driver's kernel context rather than a user-mode call path.",
why_plausible: "The decompiled code clearly shows forwarding behavior: for 0x220018 it builds a synchronous FSD request after ObReferenceObjectByHandle/IoGetRelatedDeviceObject; for 0x22002c and 0x220030 it builds IOCTL requests 0x4d030 and 0x4d014 to the related device. There is no visible ACL or privilege check in the driver's own logic. This is the sort of proxy design that can create access-control bypasses.",
any_evidence_to_the_contrary: "ObReferenceObjectByHandle is invoked with desired access 0, meaning the referenced handle must already exist in the caller's process. That significantly weakens the hypothesis because the caller may already possess whatever access is needed. I did not read downstream target drivers, so no actual security boundary bypass is confirmed from this binary alone.",
potentially_vulnerable_function: "FUN_00011260",
potentially_vulnerable_ioctl_if_relevant: "0x220018 / 0x22002c / 0x220030",
severity: Low,
likelihood_of_exploitability: Low,
next_step: Conclude,
},
],
next_step: Conclude,
number_of_iterations: 1,
},
)
Result for file: C:\Users\flux\git\ghidra-mcp\docker\binaries\drv.bin
[
Serialised(
Hypothesis {
hypothesis_title: "Arbitrary physical memory mapping and kernel copy via IOCTL 0x220014",
description_of_hypothesis: "Confirmed. The decompiled dispatch routine FUN_00011260 at 0x11260 handles IRP_MJ_DEVICE_CONTROL directly. It checks `if (*pcVar4 != '\\x0e')` before processing, which matches IRP major function 0x0e (device control), then reads the current stack location fields from `pcVar4`: input length from `*(int *)(pcVar4 + 0x10)`, output length from `*(uint *)(pcVar4 + 8)`, and the IOCTL code from `*(undefined4 *)(pcVar4 + 0x18)`. For case `0x220014`, the only gate is `if (iVar2 == 0x10)`. It then treats the system buffer at `*(param_2 + 0x18)` as a 16-byte caller-controlled structure (`puVar5`), calls `MmMapIoSpace(*puVar5, puVar5[1], 0)`, and on success calls `FUN_00012600(*(undefined8 *)(puVar5 + 2), lVar9, puVar5[1])` followed by `MmUnmapIoSpace(lVar9, puVar5[1])`. The helper FUN_00012600 at 0x12600 decompiles as a memmove/memcpy-style byte copy routine and performs the copy with `param_1` as destination, `param_2` as source, and `param_3` as size. Thus this IOCTL gives the caller control over: (1) the physical address mapped by the kernel, (2) the number of bytes mapped and copied, and (3) the destination virtual address written by kernel code. No address-range filtering, no capability check, and no ProbeForWrite/MDL validation are present in this path. The primitive is stronger than a mere physical-memory disclosure: it is an arbitrary kernel-mode write-to-chosen-virtual-address of attacker-chosen bytes sourced from attacker-chosen physical memory, plus physical memory disclosure when the destination points to user memory. Realistic second-order capabilities include reading arbitrary physical RAM into user space; harvesting kernel pointers and sensitive material from RAM; modifying kernel virtual memory if a kernel address is supplied as the destination; corrupting dispatch tables, token-related objects, process structures, callback pointers, security state, or code/data pages reachable via chosen physical source contents; destabilizing or crashing the kernel by writing invalid data to sensitive addresses; and using disclosed physical contents to defeat KASLR or locate escalation targets. From those primitives, realistic end goals include local privilege escalation to kernel/SYSTEM, arbitrary kernel code execution, disabling or bypassing security controls, credential/token theft or manipulation, persistence via kernel object/handler corruption, tampering with other processes or drivers, and full compromise of the Windows trust boundary enforced by the kernel.",
bug_class: KernelPhysicalMemoryMapping,
primary_attack_surface: "User-reachable device object created in entry and exposed through a DOS symbolic link; device-control requests are dispatched to FUN_00011260.",
path_and_state_to_reach_vulnerability: "entry (0x119d0) -> IoCreateDevice(param_1,0,0x13010,0x22,0,0,...) creates the device object -> entry stores FUN_00011260 into driver dispatch table slots at offsets 0xe0, 0x80, and 0x70, with 0xe0 corresponding to IRP_MJ_DEVICE_CONTROL -> entry calls IoCreateSymbolicLink(&DAT_00013000,0x13010) to expose the device via a DOS link -> external caller opens the symbolic link target and sends IRP_MJ_DEVICE_CONTROL with IoControlCode 0x220014 -> FUN_00011260 verifies major function 0x0e and switches on `*(undefined4 *)(pcVar4 + 0x18)` -> for 0x220014 and `InputBufferLength == 0x10`, it reads the 16-byte input buffer from `*(param_2 + 0x18)`, maps the supplied physical address and size with MmMapIoSpace, copies `puVar5[1]` bytes from the mapped kernel address to `*(undefined8 *)(puVar5 + 2)` via FUN_00012600, then unmaps.",
why_plausible: "Reachability is supported by decompiled entry code: `*(code **)(param_1 + 0xe0) = FUN_00011260; ... iVar1 = IoCreateSymbolicLink(&DAT_00013000,0x13010);`. Reading the Unicode_STRING contents in memory shows `DAT_00013000` points to a UTF-16 string `\\DosDevices\\...` and `0x13010` points to a UTF-16 string beginning `\\Device\\...`, confirming a named device plus DOS symbolic link are created. Attacker control is explicit in FUN_00011260: `puVar5 = *(undefined4 **)(param_2 + 0x18);` and in case `0x220014`: `lVar9 = MmMapIoSpace(*puVar5,puVar5[1],0); ... FUN_00012600(*(undefined8 *)(puVar5 + 2),lVar9,puVar5[1]);`. The only validation in that branch is `if (iVar2 == 0x10)`. There is no check on the physical address, no upper/lower bound on `puVar5[1]`, no check that destination lies in the caller's output buffer, and no exception handling around the copy. FUN_00012600 decompiles as a raw memmove-like routine, reinforcing that the destination pointer is dereferenced directly in kernel mode.",
any_evidence_to_the_contrary: "The decompiled entry call is `IoCreateDevice(param_1,0,0x13010,0x22,0,0,...)`, i.e. plain IoCreateDevice rather than IoCreateDeviceSecure, and no privilege or access check is present in FUN_00011260 before servicing 0x220014. I did not recover the exact security descriptor/ACL for the device object from this sample alone, so absolute proof of low-privilege open access is not present in the decompiled code. Also, the branch requires `InputBufferLength == 0x10`; that is a format check but not a meaningful mitigation. No other mitigation was found in the audited path.",
potentially_vulnerable_function: "FUN_00011260",
potentially_vulnerable_ioctl_if_relevant: "0x220014",
severity: Critical,
likelihood_of_exploitability: High,
next_step: Conclude,
},
),
Serialised(
Hypothesis {
hypothesis_title: "Unchecked embedded user pointers in VGA read/write IOCTL family can trigger arbitrary kernel read or write",
description_of_hypothesis: "Confirmed in decompiled code. The driver entry function `entry` at `000119d0` creates a device with `IoCreateDevice(param_1,0,0x13010,0x22,0,0,...)` and a DOS-visible symbolic link via `IoCreateSymbolicLink(&DAT_00013000,0x13010)`, then installs `FUN_00011260` as the dispatch routine for major functions at offsets `+0x70`, `+0x80`, and `+0xe0`. `FUN_00011260` handles IRP_MJ_DEVICE_CONTROL (`if (*pcVar4 == '\\x0e')`) and forwards unhandled IOCTLs to `FUN_000122d0(param_2 + 0x38, *(undefined4 *)(pcVar4 + 0x18), *(undefined8 *)(param_2 + 0x18), *(undefined4 *)(pcVar4 + 0x10), *(undefined4 *)(pcVar4 + 8))`. In `FUN_000122d0`, IOCTL `0x22c048` accepts an input buffer of at least `0x18` bytes and passes `*(undefined8 *)(param_3 + 4)` directly as the fifth argument to `FUN_00011d70(*param_3, param_3[1], param_3[2], param_3[3], *(undefined8 *)(param_3 + 4))`. IOCTL `0x22c050` likewise passes the embedded 64-bit value at input offset `0x10` to `FUN_00011f10(param_3, param_3 + 1, param_3 + 2, param_3 + 3, *(undefined8 *)(param_3 + 4))`. There is no probing, capture, MDL creation, try/except, or address-range validation for that embedded pointer in `FUN_000122d0` or the callee routines.\n\nThe decompiled body of `FUN_00011d70` shows direct reads from the attacker-supplied pointer inside nested loops: `pbVar5 = param_5; ... if ((uVar4 & 1) == 0) { *pbVar3 = *pbVar5 >> 4 | *pbVar3 & 0xf0; } else { *pbVar3 = (*pbVar5 ^ *pbVar3) & 0xf ^ *pbVar3; pbVar5 = pbVar5 + 1; } ... param_5 = param_5 + (param_3 + 1 >> 1);`. `pbVar3` is derived from `lRam0000000000013050`, which `FUN_00011a70` initializes by `MmMapIoSpace(0xa0000,0x10000,0)`, so this path copies bytes from the embedded pointer into the VGA aperture. The immediate primitive is an unchecked kernel dereference of a caller-chosen pointer for read. If the supplied address is invalid in the current thread context, the driver can bugcheck due to the lack of exception handling. If the supplied address is a valid kernel virtual address, the routine will read bytes from that kernel address range and write transformed nibble data into VGA memory, giving the caller a kernel-memory-to-device copy primitive over the requested rectangle. Because the same driver also exposes a readback path (`0x22c050`), this supports exfiltration of chosen kernel memory through the VGA buffer by first sourcing kernel bytes with `0x22c048` and then reading the resulting screen region back.\n\nThe decompiled body of `FUN_00011f10` shows direct writes through the attacker-supplied pointer: `pbVar11 = param_5; ... if ((uVar8 & 1) == 0) { *pbVar11 = bVar5 << 4; } else { *pbVar11 = *pbVar11 | bVar5; pbVar11 = pbVar11 + 1; } ... param_5 = param_5 + (uVar2 + 1 >> 1);`. Here `bVar5` is synthesized from bits read out of the mapped VGA memory at `lRam0000000000013050`. Thus IOCTL `0x22c050` gives a caller-chosen kernel write destination, with written data controlled by VGA contents and geometry. The written bytes are not fully arbitrary in one shot, but they are attacker-influenced because the same interface lets the caller initialize VGA contents beforehand using `0x22c048`, `0x22c04c`, `0x22c054`, and `0x22c044`. Chaining these operations yields a realistic arbitrary-kernel-read/write exploitation path over regions sized by the rectangle parameters, limited mainly by the 640x480 planar framebuffer dimensions and nibble packing. With a chosen kernel write primitive, realistic second-order capabilities include corrupting function pointers, dispatch tables, security-relevant globals, object fields, token-related pointers, callback arrays, or code/data pages mapped writable; with a chosen kernel read primitive, realistic capabilities include leaking KASLR-sensitive pointers, object addresses, security cookie material, credentials, or other kernel state needed to make a follow-on write reliable. Those capabilities can in turn enable local privilege escalation to SYSTEM, disabling protections, arbitrary code execution in kernel mode, tampering with kernel objects to bypass access control, persistence by modifying boot/startup kernel components or driver control structures, and complete compromise of the host OS. Even where direct privilege escalation is not immediately achieved, the unchecked dereference alone is sufficient for denial of service through invalid-pointer bugchecks.",
bug_class: KernelUncheckedUserPointer,
primary_attack_surface: "IRP_MJ_DEVICE_CONTROL in FUN_00011260 forwarding unrecognized IOCTLs into FUN_000122d0",
path_and_state_to_reach_vulnerability: "User opens the device exposed by `entry` (`000119d0`), which calls `IoCreateDevice(...,0x13010,...)` and `IoCreateSymbolicLink(&DAT_00013000,0x13010)`, then routes IRP_MJ_CREATE / IRP_MJ_CLOSE / IRP_MJ_DEVICE_CONTROL to `FUN_00011260`. A DeviceIoControl request reaches `FUN_00011260`, which checks major function `*pcVar4 == 0x0e` and on default dispatch calls `FUN_000122d0(param_2 + 0x38, IoControlCode, SystemBuffer, InputBufferLength, OutputBufferLength)`. First send IOCTL `0x22c040`, which in `FUN_000122d0` calls `FUN_00011a70((char)*param_3)`; `FUN_00011a70` maps physical VGA memory with `MmMapIoSpace(0xa0000,0x10000,0)` and sets `uRam0000000000013040 = 1` and `lRam0000000000013050` on success. Then send IOCTL `0x22c048` with an input buffer containing x/y/width/height at offsets 0x0..0xc and an embedded 64-bit pointer at offset 0x10; `FUN_000122d0` passes that pointer to `FUN_00011d70`, which dereferences it in a loop while writing into the mapped VGA aperture. Or send IOCTL `0x22c050` with the same layout; `FUN_000122d0` passes the embedded 64-bit pointer to `FUN_00011f10`, which writes bytes through it in a loop while reading from the VGA aperture.",
why_plausible: "Specific decompiled evidence: (1) Reachability: `entry` installs `FUN_00011260` as dispatch handler and creates a symbolic link, so external callers can issue IOCTLs. (2) Forwarding: `FUN_00011260` default case does `uVar7 = FUN_000122d0(..., *(undefined8 *)(param_2 + 0x18), *(undefined4 *)(pcVar4 + 0x10), *(undefined4 *)(pcVar4 + 8));`, passing the caller buffer pointer and lengths. (3) Embedded pointer consumption: in `FUN_000122d0`, `0x22c048` calls `FUN_00011d70(*param_3,param_3[1],param_3[2],param_3[3],*(undefined8 *)(param_3 + 4));`; `0x22c050` calls `FUN_00011f10(param_3,param_3 + 1,param_3 + 2,param_3 + 3,*(undefined8 *)(param_3 + 4));`. (4) Dangerous dereference for read path: `FUN_00011d70` uses `*pbVar5` and advances `pbVar5 = pbVar5 + 1`, where `pbVar5` originates from the embedded pointer. (5) Dangerous dereference for write path: `FUN_00011f10` uses `*pbVar11 = bVar5 << 4;` and `*pbVar11 = *pbVar11 | bVar5; pbVar11 = pbVar11 + 1;`, where `pbVar11` originates from the embedded pointer. (6) No user-pointer safety code was present in any of these routines: no `ProbeForRead`, `ProbeForWrite`, `MmProbeAndLockPages`, structured exception handling around the loop bodies, or canonical-address checks. (7) The only validation before use is buffer-size minimums (`0x17 < param_4`, `7 < param_5`) and rectangle clipping against `uRam0000000000013044/uRam0000000000013048`; those checks constrain only framebuffer coordinates, not the embedded pointer target/source.",
any_evidence_to_the_contrary: "There is a state gate: the VGA helpers only operate after `0x22c040` succeeds, because both `FUN_00011d70` and `FUN_00011f10` require `cRam0000000000013040 != '\\0'`, which `FUN_00011a70` sets only after `MmMapIoSpace(0xa0000,0x10000,0)` succeeds. Geometry checks clip accesses to the VGA aperture dimensions and reject some integer wrap with `param_1 <= param_1 + param_3` / `param_2 <= param_2 + param_4`. This limits the amount and content-shaping of data moved per request, but it does not validate or sanitize the embedded pointer. No privilege check or ACL enforcement was visible in the decompiled code: `IoCreateDevice` is called with a null device name argument in the decompiler output and no `IoCreateDeviceSecure`, no security descriptor setup, and no caller-token check appear before dispatching these IOCTLs.",
potentially_vulnerable_function: "FUN_000122d0",
potentially_vulnerable_ioctl_if_relevant: "0x22c048 and 0x22c050 (with related VGA state setup via 0x22c040)",
severity: High,
likelihood_of_exploitability: High,
next_step: Conclude,
},
),
Serialised(
Hypothesis {
hypothesis_title: "Unsafe forwarding of caller-supplied file handles and user buffers to other drivers without access control",
description_of_hypothesis: "Confirmed in FUN_00011260 at 0x00011260, the driver's major-function dispatch routine installed by entry() for IRP_MJ_CREATE, IRP_MJ_CLOSE, and IRP_MJ_DEVICE_CONTROL. For IOCTL 0x220018, the function takes the caller's SystemBuffer from `*(param_2 + 0x18)` as `puVar14`, checks only that the input length is > 0x1b and output length > 3, then calls `ObReferenceObjectByHandle(*(undefined8 *)puVar14, 0, *IoFileObjectType_exref, 0, &lStackX_10, 0)`. The desired access passed is 0, so this routine does not ask for any specific access rights beyond the caller already possessing a valid file handle object. It then calls `IoGetRelatedDeviceObject(lStackX_10)` and builds a subordinate IRP with `IoBuildSynchronousFsdRequest(4, uVar10, *(undefined8 *)(puVar14 + 2), puVar14[6], &puStackX_18, auStack_78, auStack_88)`, where both the data pointer `*(undefined8 *)(puVar14 + 2)` and transfer length `puVar14[6]` come directly from the caller-controlled structure. It marks the IRP stack location and forwards it via `IofCallDriver(uVar10)`. For IOCTLs 0x22002c and 0x220030, after only checking input length > 0x13 and output length > 3, it again references a caller-supplied file handle with `ObReferenceObjectByHandle(..., DesiredAccess=0, ..., IoFileObjectType, ...)`, obtains the related device object, and issues `IoBuildDeviceIoControlRequest` using fixed target IOCTLs 0x4d030 and 0x4d014 respectively, with the same caller-controlled pointer `*(undefined8 *)(puVar14 + 2)` reused as both input and output buffer and caller-controlled length `puVar14[4]` reused as both input and output size, then forwards the request with `IofCallDriver`. There is no policy check on what target device the supplied handle belongs to, no per-IOCTL allowlist of destination device stacks, no validation that the embedded buffer pointer refers to a safe kernel buffer, and no impersonation or authorization check before acting as a broker. The primitive granted by successful use is a kernel-mediated forwarding primitive: a user can cause this driver to send read or device-control IRPs into another device stack selected indirectly by a handle they supply, with attacker-selected buffer pointer and length fields embedded into the subordinate IRP. Realistic second-order capabilities depend on the downstream target driver: the caller may induce reads from a target file/device, trigger private IOCTLs that the target stack normally expects only from trusted in-kernel callers, reuse this driver as a broker to reach device objects through any handle they can open, and potentially obtain arbitrary kernel memory read/write or memory corruption if the downstream stack trusts the forwarded RequestorMode/buffer semantics or incorrectly handles the supplied pointers. End goals could therefore include disclosure of protected device/file data reachable through the handle, bypass of intended user-mode/device-interface restrictions by talking to a device through this broker instead of its intended API surface, and potentially privilege escalation to SYSTEM or full kernel code execution if a reachable downstream driver interprets the forwarded IRPs as trusted kernel-originated requests and performs unsafe accesses to the attacker-specified buffers. However, the impact is not self-contained in this driver alone; exploitation value depends on what other device object the attacker can already open and what that target stack does with the forwarded request.",
bug_class: KernelDangerousObjectOperation,
primary_attack_surface: "IRP_MJ_DEVICE_CONTROL in FUN_00011260 for IOCTLs 0x220018, 0x22002c, and 0x220030.",
path_and_state_to_reach_vulnerability: "entry(0x000119d0) creates the device with IoCreateDevice, creates a DOS symbolic link with IoCreateSymbolicLink, and installs FUN_00011260 into the driver object's dispatch table at offsets 0x70, 0x80, and 0xe0. An external user who opens the device through that symbolic link can send IRP_MJ_DEVICE_CONTROL to FUN_00011260. Inside FUN_00011260, when `*(IO_STACK_LOCATION->Parameters.DeviceIoControl.IoControlCode)` equals 0x220018, 0x22002c, or 0x220030, the code reads the caller's buffered I/O input structure from `IRP->AssociatedIrp.SystemBuffer` (`*(param_2 + 0x18)`), references the embedded handle via ObReferenceObjectByHandle, obtains the target device object with IoGetRelatedDeviceObject, builds a new IRP with IoBuildSynchronousFsdRequest or IoBuildDeviceIoControlRequest using caller-supplied pointer/length fields from that same structure, and forwards it with IofCallDriver.",
why_plausible: "The decompiled body of FUN_00011260 directly shows the forwarding path. For 0x220018: `uVar7 = ObReferenceObjectByHandle(*(undefined8 *)puVar14,0,*IoFileObjectType_exref,0,&lStackX_10,0); if (-1 < (int)uVar7) { uVar10 = IoGetRelatedDeviceObject(lStackX_10); ... puStackX_18 = *(undefined4 **)(puVar14 + 4); lVar9 = IoBuildSynchronousFsdRequest(4,uVar10,*(undefined8 *)(puVar14 + 2),puVar14[6],&puStackX_18,auStack_78,auStack_88); ... uVar7 = IofCallDriver(uVar10); }`. For 0x22002c/0x220030: `uVar7 = ObReferenceObjectByHandle(*(undefined8 *)puVar14,0,*IoFileObjectType_exref,0,&lStackX_10,0); ... uVar10 = IoGetRelatedDeviceObject(lStackX_10); ... lVar9 = IoBuildDeviceIoControlRequest(uVar11,uVar10,*(undefined8 *)(puVar14 + 2),puVar14[4],*(undefined8 *)(puVar14 + 2),CONCAT44(uVar16,puVar14[4]),...,auStack_78,auStack_88); ... uVar7 = IofCallDriver(uVar10);`. The only validations before these operations are null checks on the top-level buffer and minimal size thresholds (`input > 0x1b` or `> 0x13`, `output > 3`). No code checks the caller's privilege, the identity/class of the target device, the safety of the embedded pointer, or whether brokering these target operations is authorized.",
any_evidence_to_the_contrary: "The path is not completely unrestricted: the caller must already possess a valid handle to a FILE_OBJECT because the code uses `ObReferenceObjectByHandle(..., IoFileObjectType, ...)`, so the driver does not let an attacker name arbitrary device objects without first opening them. The decompiled code also performs basic size gating on the outer IOCTL buffer before dereferencing structure fields. No additional privilege check, ACL enforcement, caller-mode restriction, target-device allowlist, or buffer sanitization was found in the decompiled dispatch path. The absence of the device object's security descriptor in this sample means actual exposure to low-privilege users depends on the default or configured ACL on the created device/symbolic link, which is not set explicitly in entry().",
potentially_vulnerable_function: "FUN_00011260",
potentially_vulnerable_ioctl_if_relevant: "0x220018, 0x22002c, 0x220030",
severity: Medium,
likelihood_of_exploitability: Medium,
next_step: Conclude,
},
),
]