|
| 1 | +*Author: [Dredsen](https://dredsen.github.io/)* |
| 2 | + |
| 3 | +--- |
| 4 | + |
| 5 | +## Background |
| 6 | + |
| 7 | +This is my first proper driver write-up, so a quick scene-setter before the technical bits. |
| 8 | + |
| 9 | +**LOLDrivers** ("Living Off the Land Drivers") is a community-maintained catalogue of legitimately signed Windows kernel drivers that contain exploitable vulnerabilities. The idea behind **BYOVD** (Bring Your Own Vulnerable Driver) attacks is simple: load one of these signed drivers yourself, abuse its exposed kernel primitives, and walk away with capabilities that would otherwise require an unsigned driver - bypassing Driver Signature Enforcement entirely, since the driver is already signed by a real vendor. |
| 10 | + |
| 11 | +The driver here is `FADA64.sys` - the **Broadcom Frame Access Driver**, a 2003-era NDIS protocol driver that shipped inside HP Smart Update Firmware DVD 9.30. |
| 12 | + |
| 13 | +--- |
| 14 | + |
| 15 | +## File Metadata |
| 16 | + |
| 17 | +| Field | Value | |
| 18 | +|---|---| |
| 19 | +| Filename | `FADA64.sys` | |
| 20 | +| Original Name | `FAD.SYS` | |
| 21 | +| MD5 | `ce9692c1cc7a575f71a899932d895603` | |
| 22 | +| SHA1 | `9d75631fec6c8296d70201b5e72c982d5a661ab6` | |
| 23 | +| SHA256 | `3e9b3937a792007d9d959b0749c2a61cf62fd56b12d2bf0857d761f89fa7e112` | |
| 24 | +| Size | 21,344 bytes | |
| 25 | +| Architecture | x64 | |
| 26 | +| Compiled | 2003-04-24 | |
| 27 | +| Company | Broadcom Corporation | |
| 28 | +| Description | Frame Access Driver | |
| 29 | +| Source | HP Smart Update Firmware DVD 9.30 - `hp\swpackages\cp013583.exe` | |
| 30 | + |
| 31 | +--- |
| 32 | + |
| 33 | +## Certificate |
| 34 | + |
| 35 | +| Field | Value | |
| 36 | +|---|---| |
| 37 | +| Subject | `CN=Broadcom Corporation, OU=Engineering Software` | |
| 38 | +| Issuer | VeriSign Class 3 Code Signing 2004 CA | |
| 39 | +| Thumbprint | `9C3FBCEB8F2A6CAC510DE477E2F65A38DD760BB9` | |
| 40 | +| Serial | `3232677944AFAE8837C58AF50DD11A8A` | |
| 41 | +| Valid | 2006-08-24 → 2009-10-23 (expired) | |
| 42 | +| Signature Type | Catalog signed - `cpqteam.cat` / `cpqtmmp.cat` | |
| 43 | +| Status | Expired - WHQL-era, accepted with DSE bypass or on older OS | |
| 44 | + |
| 45 | +The driver is **catalog signed** rather than embedded-signed, meaning the signature lives in a separate `.cat` file rather than the driver PE itself. Both `.cat` files are included in the original HP package. |
| 46 | + |
| 47 | +--- |
| 48 | + |
| 49 | +## Vulnerability Summary |
| 50 | + |
| 51 | +The core vulnerability is an **arbitrary physical memory read primitive**. |
| 52 | + |
| 53 | +The driver creates a device `\\.\FAD` with a **NULL DACL** - meaning any user on the system can open a handle to it without any privilege check. Through IOCTL `0x223EF4`, an unprivileged caller can supply an arbitrary physical address and a byte count. The driver maps that physical address into kernel virtual address space using `MmMapIoSpace` and copies the contents back to the caller's output buffer via `RtlMoveMemory`. |
| 54 | + |
| 55 | +No `RequestorMode` check. No validation of the supplied physical address. No access control beyond the ability to open the device - which anyone can do. |
| 56 | + |
| 57 | +**What this gives an attacker:** the ability to read arbitrary physical memory from user mode. Practically useful for kernel ASLR bypass (read known physical offsets to locate kernel structures), memory enumeration, or credential hunting without touching `LSASS` at the virtual-address level. |
| 58 | + |
| 59 | +**What this does *not* give:** an arbitrary write. The write path was fully traced and ruled out - more on that below. |
| 60 | + |
| 61 | +--- |
| 62 | + |
| 63 | +## Device Setup |
| 64 | + |
| 65 | +The driver's `DriverEntry` at `0x17000` creates the device with no security descriptor (7-parameter `IoCreateDevice` call, `NULL` for the SD argument) and sets `DO_BUFFERED_IO`: |
| 66 | + |
| 67 | +```c |
| 68 | +// DriverEntry @ 0x17000 |
| 69 | +IoCreateDevice(DriverObject, 0x38u, L"\\Device\\FAD", 0x22u, 0, 0, &DeviceObject); |
| 70 | +DeviceObject->Flags |= 0x10u; // DO_BUFFERED_IO |
| 71 | +IoCreateSymbolicLink(L"\\DosDevices\\FAD", L"\\Device\\FAD"); |
| 72 | +``` |
| 73 | +
|
| 74 | +| Field | Value | |
| 75 | +|---|---| |
| 76 | +| Device Name | `\Device\FAD` | |
| 77 | +| Symlink | `\DosDevices\FAD` → `\\.\FAD` | |
| 78 | +| Device Type | `0x22` (`FILE_DEVICE_UNKNOWN`) | |
| 79 | +| DevExt Size | `0x38` | |
| 80 | +| Exclusive | No | |
| 81 | +| `DO_BUFFERED_IO` | Yes (`Flags |= 0x10`) | |
| 82 | +| Security Descriptor | **NULL** (world-accessible) | |
| 83 | +
|
| 84 | +`DO_BUFFERED_IO` means the I/O manager handles buffer copying between user and kernel - the driver works with a kernel-mode copy of the user's buffer rather than directly with the user pointer. This is relevant when sizing the input/output struct. |
| 85 | +
|
| 86 | +--- |
| 87 | +
|
| 88 | +## IOCTL Surface |
| 89 | +
|
| 90 | +The dispatch handler lives at `sub_12750` (registered as `MajorFunction[14]`). |
| 91 | +
|
| 92 | +| IOCTL Code | Handler | Description | |
| 93 | +|---|---|---| |
| 94 | +| `0x223EE4` | `sub_11370` | Open NDIS adapter by name (string from user buffer) | |
| 95 | +| `0x223EF4` | `sub_12640` | **Physical memory read** - `MmMapIoSpace` + `RtlMoveMemory` | |
| 96 | +| `0x223EB4` | inline | Read 16-byte stats from per-open context | |
| 97 | +| `0x223EB8` | inline | Zero internal counters | |
| 98 | +| `0x223EE8` | inline | `NdisRequest` to open adapter | |
| 99 | +| `0x223EEC` | inline | Set config word | |
| 100 | +
|
| 101 | +All codes use `METHOD_BUFFERED` (bits 1–0 = `0`). There is **no `RequestorMode` check anywhere in the dispatch handler**. |
| 102 | +
|
| 103 | +--- |
| 104 | +
|
| 105 | +## The Vulnerable IOCTL - `0x223EF4` |
| 106 | +
|
| 107 | +`sub_12640` is the physical memory read handler. The input buffer layout is simple: 8 bytes of physical address followed by 4 bytes of size. The driver iterates in 4KB pages, mapping each physical page into kernel VA space with `MmMapIoSpace(MmNonCached)`, copying its contents into the output buffer, then unmapping: |
| 108 | +
|
| 109 | +```c |
| 110 | +// Input/output buffer layout (METHOD_BUFFERED, minimum size 0x10): |
| 111 | +// [+0x00] PHYSICAL_ADDRESS PhysAddr - physical address to read (user-controlled) |
| 112 | +// [+0x08] UINT32 Size - byte count to read (user-controlled) |
| 113 | +
|
| 114 | +__int64 sub_12640(__int64 a1, PHYSICAL_ADDRESS *a2, unsigned int InputLen, |
| 115 | + unsigned int OutputLen, _DWORD *BytesOut) |
| 116 | +{ |
| 117 | + PHYSICAL_ADDRESS i = *a2; // physical address - straight from user buffer |
| 118 | + UINT32 remaining = a2[1].LowPart; // size - straight from user buffer |
| 119 | +
|
| 120 | + for (; remaining > 0x1000; remaining -= 0x1000) { |
| 121 | + PVOID kva = MmMapIoSpace(i, 0x1000, MmNonCached); |
| 122 | + RtlMoveMemory(a2, kva, 0x1000); // copy physical → output buffer |
| 123 | + MmUnmapIoSpace(kva, 0x1000); |
| 124 | + a2 += 512; // advance output pointer 0x1000 bytes |
| 125 | + i.QuadPart += 0x1000; |
| 126 | + } |
| 127 | + if (remaining) { |
| 128 | + PVOID kva = MmMapIoSpace(i, remaining, MmNonCached); |
| 129 | + RtlMoveMemory(a2, kva, remaining); |
| 130 | + MmUnmapIoSpace(kva, remaining); |
| 131 | + } |
| 132 | +} |
| 133 | +``` |
| 134 | + |
| 135 | +The driver does validate that `InputLen >= 0x10` before entering `sub_12640`, so you need at least 16 bytes in your input buffer. Otherwise: no bounds checking, no address validation, no privilege check. |
| 136 | + |
| 137 | +--- |
| 138 | + |
| 139 | +## Exploit Chain |
| 140 | + |
| 141 | +``` |
| 142 | +1. OpenFile \\.\FAD (no credentials required - NULL DACL, any user) |
| 143 | +
|
| 144 | +2. Build input struct: |
| 145 | + PHYSICAL_ADDRESS phys = TARGET_PA; // e.g. 0x0 (IVT), 0x400 (BDA), or wherever |
| 146 | + UINT32 size = 0x1000; // up to a full page per call |
| 147 | +
|
| 148 | +3. DeviceIoControl( |
| 149 | + hFAD, |
| 150 | + 0x223EF4, |
| 151 | + &struct, sizeof(struct), // input buffer |
| 152 | + &struct, sizeof(struct), // output buffer (same, BUFFERED) |
| 153 | + &bytes, |
| 154 | + NULL |
| 155 | + ) |
| 156 | +
|
| 157 | +4. struct now contains bytes read from physical address TARGET_PA |
| 158 | +``` |
| 159 | + |
| 160 | +Interesting targets at known physical offsets (no ASLR at the physical layer): |
| 161 | + |
| 162 | +- `0x0` - Interrupt Vector Table (real-mode era, but still mapped) |
| 163 | +- `0x400` - BIOS Data Area |
| 164 | +- `0x1000` and up - early physical RAM, often contains kernel image remnants post-boot |
| 165 | + |
| 166 | +For a kernel ASLR bypass: read physical memory to locate the kernel image, extract the base, then pivot to virtual-address operations via a second primitive. |
| 167 | + |
| 168 | +--- |
| 169 | + |
| 170 | +## Ruling Out a Write Primitive |
| 171 | + |
| 172 | +Before finalising the assessment I traced every write-capable import to its call site to confirm there is no write path back to physical or kernel memory. |
| 173 | + |
| 174 | +**`MmMapLockedPagesSpecifyCache`** (`sub_11CD0`): maps a user-supplied MDL, then calls `RtlCopyMemory(mapped_va, ndis_packet_buffer, length)`. Data flows **from the network into the user's own buffer** - this is the packet receive path. No user data is written to any kernel or physical destination. |
| 175 | + |
| 176 | +**`MmMapIoSpace`** (`sub_12640`): maps the user-supplied physical address, then `RtlMoveMemory(output_buffer, kva, size)`. Copy direction is **physical → output buffer**, read-only. No reverse path. |
| 177 | + |
| 178 | +**`NdisRequest`** (IOCTL `0x223EE8` path): issues NDIS OID SET requests to the NIC. Writes only to **the network adapter's own hardware registers** via the NDIS miniport - not to kernel memory or physical memory outside the NIC. OID and data are user-supplied but constrained to what the NIC's miniport accepts. |
| 179 | + |
| 180 | +**Conclusion:** every `RtlMoveMemory` / `RtlCopyMemory` call site copies either physical → output buffer or network packet → user MDL. **No arbitrary write primitive exists in this driver.** |
| 181 | + |
| 182 | +--- |
| 183 | + |
| 184 | +## Impact |
| 185 | + |
| 186 | +| Property | Assessment | |
| 187 | +|---|---| |
| 188 | +| Primitive | Arbitrary physical memory **read** | |
| 189 | +| Privilege required | **User** (any logged-in user, NULL DACL) | |
| 190 | +| Write primitive | None | |
| 191 | +| DSE bypass required | No - driver is legitimately signed (expired cert, but accepted on older OS or with catalog tricks) | |
| 192 | +| CVE | None assigned | |
| 193 | +| MITRE ATT&CK | T1068 - Exploitation for Privilege Escalation | |
| 194 | +| Practical use | Kernel ASLR bypass, physical memory enumeration, credential search | |
| 195 | + |
0 commit comments