
Estrae l'hash NetNTLMv2 dell'utente corrente tramite proxy di autenticazione HTTP, evitando chiamate dirette a SSPI; la v2 delega l'autenticazione al servizio BITS per interrompere l'attribuzione del processo.
Estrazione di hash NTLM tramite proxy di autenticazione a livello HTTP, zero chiamate SSPI dal processo dell'attaccante.
HashSiphon estrae l'hash NetNTLMv2 dell'utente corrente manipolando i flussi di autenticazione HTTP invece di chiamare direttamente le API SSPI. Viene distribuito in due varianti: v1 instrada l'autenticazione NTLM attraverso lo stack HTTP di .NET all'interno dello stesso processo, mentre v2 delega completamente l'autenticazione al servizio BITS (svchost.exe) in un PID diverso, rompendo del tutto l'attribuzione a livello di processo.
Ogni strumento noto di auto-estrazione di hash NTLM Internal Monologue, script SSPI manuali e loro derivati chiama AcquireCredentialsHandle → InitializeSecurityContext → AcceptSecurityContext dal processo dell'attaccante stesso. Gli EDR agganciano queste funzioni SSPI e segnalano la catena di chiamate.
HashSiphon segue un percorso fondamentalmente diverso:
| Aspetto | Internal Monologue | HashSiphon v1 | HashSiphon v2 |
|---|
| Chiamate SSPI dal PID dell'attaccante | 4+ chiamate dirette | 0 dirette (WinHTTP chiama internamente) | 0 in qualsiasi processo di nostra proprietà |
| Processo di autenticazione | PID dell'attaccante | PID dell'attaccante (tramite stack HTTP) | svchost.exe (servizio BITS) |
| Import di API di sicurezza | Import di DLL SSPI richiesti | Nessuno nel nostro codice | Nessuno nel nostro codice |
| Superficie di rilevamento | Hook SSPI, pattern di chiamate API | Traffico HTTP su loopback | Job BITS + traffico su loopback |
| Attribuzione del processo | PID dell'attaccante | PID dell'attaccante | Rotta, PID completamente diverso |
Entrambe le varianti condividono lo stesso nucleo: un server TCP minimale su 127.0.0.1 che parla appena l'HTTP necessario per eseguire uno scambio challenge-response NTLM, con una challenge controllata di 8 byte in modo che l'hash catturato sia crackabile offline.
HashSiphon.ps1)┌──────────────────────────┐
│ PowerShell (PID X) │
│ │
│ ┌────────────────────┐ │ ┌──────────────────────┐
│ │ TCP Server (C#) │◄─┼──────────┤ HttpWebRequest + │
│ │ Loopback :random │ │ HTTP │ DefaultCredentials │
│ │ │──┼──────────► │
│ │ 1. Send 401+NTLM │ │ NTLM │ WinHTTP auto-auths │
│ │ 2. Send Type 2 │ │ Type │ using current user's │
│ │ 3. Capture Type 3 │ │ 1/2/3 │ credentials │
│ │ 4. Extract hash │ │ │ │
│ └────────────────────┘ │ └──────────────────────┘
└──────────────────────────┘
Add-Type, si associa a 127.0.0.1:0 (porta assegnata dal sistema operativo)HttpWebRequest + CredentialCache.DefaultCredentialsHTTP 401 con WWW-Authenticate: NTLM per avviare la negoziazioneCompromesso: WinHTTP chiama internamente SSPI all'interno dello stesso PID, nessun import diretto, ma lo stack di chiamate risale comunque fino a noi.
HashSiphonV2.ps1)┌─────────────────────┐ ┌───────────────────────────┐
│ PowerShell (PID X) │ │ svchost.exe (PID Y) │
│ │ │ BITS Service │
│ ┌───────────────┐ │ HTTP │ │
│ │ TCP Server │◄─┼─────────┤ BITS downloads from our │
│ │ (Background │ │ NTLM │ server, auto-authenticates│
│ │ Runspace) │──┼─────────► using job owner's creds │
│ └───────────────┘ │ Type │ │
│ │ 1/2/3 │ SSPI calls happen HERE, │
│ Start-BitsTransfer─┼────────►│ not in PID X │
│ (Trigger only) │ COM │ │
└─────────────────────┘ └───────────────────────────┘
│
└── Our process: TcpListener + Start-BitsTransfer
Zero SSPI. Zero security API imports.
Start-BitsTransfer crea un job di download che punta a http://127.0.0.1:<port>/hashsiphon.binsvchost.exe, un PID completamente diverso) si connette al nostro serverHTTP 200 con un body in modo che BITS consideri il trasferimento riuscitoSvolta: Il nostro processo non chiama mai SSPI, né direttamente, né tramite WinHTTP, né in alcun modo. L'intero calcolo NTLM avviene in svchost.exe. Gli hook SSPI degli EDR vedono lo stack di chiamate nel processo del servizio BITS, non nel nostro.
powershell -ExecutionPolicy Bypass -File HashSiphon.ps1
powershell -ExecutionPolicy Bypass -File HashSiphonV2.ps1
[*] Compiling HashSiphon v2 server...
[+] Server compiled
HashSiphon v2.0 - BITS Service Proxy Authentication
Auth by svchost.exe (BITS), not our process
[1] NTLM HTTP server ready on 127.0.0.1:52847
[2] Controlled challenge: 1122334455667788
[3] Triggering BITS transfer to our server...
[4] BITS transfer initiated
[+] User: ivan
[+] Domain: DESKTOP-ABCDEF
[+] NT response: 280 bytes (NTLMv2)
+----------------------------------------------------------+
| NetNTLMv2 HASH - Extracted via BITS service proxy! |
+----------------------------------------------------------+
| hashcat -m 5600 | john --format=netntlmv2 |
+----------------------------------------------------------+
ivan::DESKTOP-ABCDEF:1122334455667788:<NTProofStr>:<ClientBlob>
+----------------------------------------------------------+
| ATTRIBUTION ANALYSIS |
+----------------------------------------------------------+
| Our PID: 844 (PowerShell) |
| Auth by: BITS service (svchost.exe, PID 5500) |
| SSPI calls: Zero from PID 844 |
| Our APIs: TcpListener + Start-BitsTransfer only |
+----------------------------------------------------------+
Se BITS fallisce (servizio disabilitato, modulo non disponibile), v2 ripiega automaticamente sull'avvio di un processo figlio powershell.exe con Invoke-WebRequest -UseDefaultCredentials. Questo ottiene comunque la separazione dei PID, sebbene il processo figlio sia più visibile rispetto a BITS.