通过 HTTP 层身份验证代理提取 NTLM 哈希,攻击者进程零 SSPI 调用。
HashSiphon 通过操纵 HTTP 身份验证流程而非直接调用 SSPI API 来提取当前用户的 NetNTLMv2 哈希。它提供两个变体:v1 在同一进程内通过 .NET 的 HTTP 栈路由 NTLM 身份验证,v2 将身份验证完全委托给不同 PID 中的 BITS 服务(svchost.exe),从而彻底打破进程级归因。
所有已知的 NTLM 哈希自提取工具 Internal Monologue、手动 SSPI 脚本及其衍生工具都会从攻击者自己的进程调用 AcquireCredentialsHandle → InitializeSecurityContext → AcceptSecurityContext。EDR 会挂钩这些 SSPI 函数并标记调用链。
HashSiphon 采取了一条根本不同的路径:
| 方面 | Internal Monologue | HashSiphon v1 | HashSiphon v2 |
|---|---|---|---|
| 来自攻击者 PID 的 SSPI 调用 | 4+ 次直接调用 | 0 次直接调用(WinHTTP 内部调用) | 我们拥有的任何进程中均为 0 次 |
| 身份验证进程 | 攻击者 PID | 攻击者 PID(通过 HTTP 栈) | svchost.exe(BITS 服务) |
| 安全 API 导入 | 需要 SSPI DLL 导入 | 我们的代码中无 | 我们的代码中无 |
| 检测面 | SSPI 挂钩、API 调用模式 | HTTP 回环流量 | BITS 作业 + 回环流量 |
| 进程归因 | 攻击者 PID | 攻击者 PID | 已打破,完全不同的 PID |
两个变体共享相同的核心:在 127.0.0.1 上运行一个极简 TCP 服务器,仅实现足够的 HTTP 协议来执行 NTLM 质询-响应交换,并使用受控的 8 字节质询,以便捕获的哈希可离线破解。
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 编译一个 C# TCP 服务器,绑定到 127.0.0.1:0(操作系统分配端口)HttpWebRequest + CredentialCache.DefaultCredentials 连接HTTP 401 并附带 WWW-Authenticate: NTLM 以触发协商权衡: WinHTTP 在同一 PID 内内部调用 SSPI,没有直接导入,但调用栈仍可追溯回我们。
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 创建一个指向 http://127.0.0.1:<port>/hashsiphon.bin 的下载作业svchost.exe,一个完全不同的 PID)连接到我们的服务器HTTP 200 并附带响应体,使 BITS 认为传输成功突破: 我们的进程从不调用 SSPI,不直接调用,不通过 WinHTTP 调用,完全不调用。整个 NTLM 计算发生在 svchost.exe 中。EDR SSPI 挂钩看到的调用栈在 BITS 服务进程中,而非我们的进程。
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 |
+----------------------------------------------------------+
如果 BITS 失败(服务被禁用、模块不可用),v2 会自动回退到启动一个子 powershell.exe 进程并使用 Invoke-WebRequest -UseDefaultCredentials。这仍然实现了 PID 分离,尽管子进程比 BITS 更显眼。