ssteelfactor-oss/Kestrel
GitHub: ssteelfactor-oss/Kestrel
纯 C 编写的低可观测性 Active Directory 安全枚举工具,通过原生 ADSI/COM 接口产生与正常域活动无异的 LDAP 流量来规避 EDR 检测。
Stars: 93 | Forks: 7
# Kestrel
通过原生 ADSI/COM 接口进行被动 Active Directory 安全枚举。
无需 .NET。无需 PowerShell。无需托管运行时。
## 现有工具存在的问题
如果你从事 AD 安全工作,你就会知道 BloodHound。它通过委派链、ACL 边缘和组成员关系映射攻击路径,并且做得非常出色。问题不在于它做了什么,而在于它是如何做到的。
SharpHound 是 BloodHound 的收集器,它作为 .NET 程序集运行。它产生的 LDAP 流量模式是任何合法域工作站都不会产生的。EDR 解决方案可以检测到它——不是因为它利用了什么漏洞,而是因为其行为特征是明确无误的。ADRecon、PowerView 和大多数基于 Python 的替代工具也是如此:运行时本身就是它们的指纹。
对于运行内部审计的防御者来说,这是一个未解决的问题。你需要枚举自己的域以在攻击者之前发现错误配置,但每个可用的工具都会大声宣告自己的存在。
## 一种不同的方法
自 Windows 2000 以来,Windows 就拥有原生的 AD 接口:**ADSI** (Active Directory Service Interfaces)。它是一个基于 COM 的 LDAP 抽象层,当加入域的组件查询目录时,操作系统本身就使用它。组策略处理使用它。MMC 管理单元使用它。`net user /domain` 也使用它。
它产生的流量与正常的域活动无法区分,因为它*就是*正常的域活动。
这就是 Kestrel 构建的基础。
## 工作原理
Kestrel 使用纯 C 语言编写,直接调用 ADSI COM 接口:没有包装器,没有抽象。从网络数据包的角度来看,每个查询都是经过身份验证的 LDAP 绑定,随后是分页搜索请求。这与每个域控 (DC) 每分每秒从每台域工作站上看到的情况完全一样。
```
IDirectorySearch *pSearch = NULL;
ADsGetObject(ldapPath, &IID_IDirectorySearch, (void **)&pSearch);
pSearch->lpVtbl->SetSearchPreference(pSearch, prefs, 2);
pSearch->lpVtbl->ExecuteSearch(pSearch, filter, attrs, count, &hSearch);
```
组通过 **Well-Known RID + Domain SID** 解析,而不是通过名称。这意味着 Kestrel 可以在任何语言(英语、俄语、德语等)安装的域上正常运行,而无需硬编码组名字符串。
## 客观而言的特征
Kestrel 是**低可观测性的,而非隐身的**,README 永远不会声称其他情况。
大多数模块通过 ADSI 发起经过身份验证的 LDAP 绑定和分页搜索——这与操作系统用于组策略、MMC 和 `net user /domain` 的 COM 路径相同。在网络层面上,这些流量是正常的域活动,因为它*就是*正常的域活动,并且只需要一个普通的域用户即可。
两个客观的注意事项:
- **GPO 策略审计 (`KestrelPolicy.c`)** 是唯一一个超出 LDAP 范围的模块。GPO 设置以文件形式存在于 SYSVOL 中,因此它通过 SMB 从 `\\domain\SYSVOL` 读取 `Registry.pol`。这对任何域成员来说都是正常的——但这是一次文件共享读取,而不是 LDAP 查询。
- **流量大小和时间仍然很重要。** 对大型域执行完整的 `--all` 运行所产生的 LDAP 流量,比单台工作站一分钟内产生的流量还要多。如果融入正常流量很重要,请控制节奏和范围。
Kestrel 不会分片查询、随机化时间或隐藏自身。它看起来很正常,因为它做的是正常的事情——这就是设计初衷,也是其客观存在的局限性。
## 环境要求
- 加入域的 Windows 计算机
- 经过身份验证的域用户帐户(任何扫描都不需要提升的权限)
- Visual Studio 2019+ 及 Windows SDK
- 链接库:`activeds.lib`、`adsiid.lib`、`ws2_32.lib`、`advapi32.lib`、`bcrypt.lib`
## 构建
在 Visual Studio 中打开 `Kestrel.sln`,选择 **Release | x64**,然后构建。
要构建没有运行时 DLL 依赖的独立二进制文件:
项目属性 → C/C++ → 代码生成 → 运行时库 → **多线程 (/MT)**
## 用法
```
Kestrel.exe # run all modules (default)
Kestrel.exe --acl --groups # selective modules
Kestrel.exe --report C:\out\graph.html # build report (.html / .json / .yaml by extension)
Kestrel.exe --trust --gmsa --adcs # v0.7 audits
Kestrel.exe --from "CONTOSO\svc_sql" # attack paths FROM a principal
Kestrel.exe --verbose # trace output
Kestrel.exe --help
```
| 标志 | 模块 |
| -------------- | ------------------------------------------------------------ |
| `--adws` | ADWS 端点检测 |
| `--topology` | 通过 SPN 获取计算机拓扑 |
| `--delegation` | 委派风险 |
| `--laps` | LAPS 覆盖率 + 健康异常 |
| `--stale` | 过期的计算机 |
| `--acl` | ACL 边缘提取 |
| `--groups` | 可传递的组成员身份 |
| `--policy` | GPO 安全策略审计 (SYSVOL/SMB) |
| `--paths` | 图的攻击路径分析 |
| `--from
` | 从某主体 (SID/名称) 出发的路径;隐含 `--paths` |
| `--roast` | 可 Kerberoasting + 可 AS-REP Roasting |
| `--trust` | 域/林信任姿态 |
| `--gmsa` | gMSA 密码读取器枚举 |
| `--adcs` | ADCS 证书模板 / CA 审计 (ESC1-5/9) |
| `--gpp` | 从 SYSVOL 恢复 GPP cpassword (MS14-025) |
| `--report ` | 写入报告(根据扩展名选择 HTML / JSON / YAML) |
| `--acl-raw` | 禁用默认 ACL 基线(显示原始 ACL 边缘) |
如果没有模块标志,Kestrel 将运行所有内容。
## 模块
### v0.1 六项被动扫描 (`adws_scan.c`)
所有查询均为只读。不会向目标主机发送任何数据包。
| 模块 | 功能说明 |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **ADWS 端点检测** | 探测每个 DC 的 9389/TCP 端口。原始 TCP 连接,SO\_ERROR 验证,无 WCF 组帧。 |
| **计算机拓扑** | 通过基于 SPN 的服务推断获取完整的计算机清单。MSSQLSvc → SQL Server,WSMAN → WinRM,TERMSRV → RDP。一个 LDAP 查询即可覆盖整个域。 |
| **委派风险** | 区分三类:非约束委派 (TGT 转发)、约束委派 (msDS-AllowedToDelegateTo) 和协议转换 / S4U2Self (UAC 0x1000000)。分开报告——具有不同的风险特征。 |
| **LAPS 健康** | 仅读取旧版和 Windows LAPS 的过期时间戳(全局可读——而非密码)。除了托管/非托管覆盖率之外,它还会标记异常:已过期(轮换停滞)、未来日期(轮换被抑制——持久化信号)、双重架构以及从未托管。排除了 DC。 |
| **过期的计算机** | 使用 lastLogonTimestamp 作为主要参考——它会跨 DC 复制,而仅限于单台 DC 的 lastLogon 则不会。这两个值会并排显示。 |
| **敏感描述** | 读取用户的 `description` / `info` / `comment`(全局可读),并标记泄露的机密及角色/弱点提示——密码、“Domain Admin”、“DCSync”、“GMSA”、“LAPS”、“shadow credential”。管理员通常会在账户上直接标注其自身的弱点。 |
### v0.2 ACL 边缘提取 (`KestrelACL.c`)
枚举所有 AD 对象(user、group、computer、OU、domainDNS、container、GPO、builtinDomain)并提取 DACL 边缘。
扩展权限 GUID 到名称的映射是动态构建的,源自 `CN=Extended-Rights,CN=Configuration`——没有硬编码的 GUID 表。
分类的边缘类型:`GenericAll`、`WriteDACL`、`WriteOwner`、`GenericWrite`、`ExtendedRight`、`WriteProperty`、`CreateChild`、`DeleteChild`、`Self`。
两种读取模式:
- **方案 A** - 针对对象的 `IDirectoryObject` 绑定(在某些环境中需要提升的权限)
- **方案 B** - 直接从 LDAP 搜索列中读取 `nTSecurityDescriptor`(适用于任何经过身份验证的域用户,与 BloodHound 方法相同)
首先尝试方案 A。在首次拒绝访问时,Kestrel 会自动切换到方案 B 处理所有剩余对象。
一次 DCSync 权限扫描会找出在域头上拥有 `GetChanges` + `GetChangesAll` 权限的主体。
自 v0.8 起,**默认 ACL 基线** (`KestrelBaseline.c`) 减少了噪音:将每个对象的 ACE 与其类别的 `defaultSecurityDescriptor` 进行比较——对于 `adminCount=1` 的对象则与 AdminSDHolder 进行比较——这样只会报告管理员实际委派的权限,而不是每个对象与生俱来的默认权限。`--acl-raw` 可禁用此过滤器。
### v0.3 可传递的组成员身份 (`KestrelGroup.c`)
使用 `LDAP_MATCHING_RULE_IN_CHAIN` (OID `1.2.840.113556.1.4.1941`) 扩展高价值组。
每组对应一个 LDAP 查询。DC 在服务器端执行完整的递归遍历——无需客户端 BFS。
通过 **RID** 而非名称定位组:
| RID | 组 |
| ------- | ------------------------------------- |
| 512 | Domain Admins |
| 518 | Schema Admins |
| 519 | Enterprise Admins |
| 520 | Group Policy Creator Owners |
| 521 | Read-only Domain Controllers |
| 526 | Key Admins |
| 527 | Enterprise Key Admins |
| 548–551 | Account/Server/Print/Backup Operators |
扩展后,将组成员身份与 v0.2 中的 ACL 边缘进行交叉引用,以揭示攻击路径:`member → [via group] → EdgeType → target`。
### v0.4 内存中图 + 报告 (`KestrelReport.c`)
根据 ACL 边缘、组成员身份和委派构建单个内存中的有向图。节点以 SID 为键存储在开放寻址哈希表中;Tier-0 主体(Domain Admins、Enterprise Admins、DC、krbtgt)会被标记。
折叠进同一图中:
- **gMSA 读取边缘** - `CanReadGMSAPassword`(读取者 → gMSA),源自 v0.7。
- **可 Roasting 的节点标志** - 可 Kerberoasting / 可 AS-REP Roasting 的对象标记为节点属性。
导出为独立的交互式 **HTML** 报告(包含过滤和节点详情面板的 D3.js 力导向图)、**JSON** 和 **YAML** ——格式由输出扩展名决定。所有序列化均使用 `fputs` 编写,从不使用 `printf` 系列,以规避 MSVC `C4477` 在 CSS/JSON 输出中的格式化字符串陷阱。
### v0.5 攻击路径查找器 (`KestrelPath.c`)
在图上执行广度优先搜索 (BFS) —— 按跳数计算的最短路径。
- **反向(默认):** 谁能触达 tier-0 目标。
- **正向(`--from `):** 特定主体可以攻陷哪些目标。
采用紧凑的 CSR 邻接表表示形式和输出上限(针对每个目标和全局),以在大型域中保持可处理的规模。
### v0.5 GPO 安全策略审计 (`KestrelPolicy.c`)
唯一一个超出 LDAP 范围的模块(参见*特征*)。GPO 设置存在于 SYSVOL 上,因此该模块通过 SMB 读取 `Registry.pol` 并解析 `dSHeuristics`。标记 LLMNR、NBT-NS、WDigest、NTLMv1 和缺失的 LDAP 签名。
它还标记了 **Onelogon** (WOOT'26) 攻击面 —— 即 *Allow vulnerable Netlogon secure channel connections* 允许列表,也就是允许使用未签名/未密封 Netlogon 通道的账户(这是 2020 年 Zerologon 补丁留下的兼容性漏洞)。每个 GPO 的 `GptTmpl.inf` 都会被读取,并且其 `VulnerableChannelAllowList` SDDL 会被解码为受豁免的主体。仅限域 GPO 范围——直接写入 DC 本地注册表的允许列表需要远程注册表 RPC,而 Kestrel 不会这样做。
**SMB 签名**是根据每个 GPO 的 `GptTmpl.inf`(`RequireSecuritySignature`,服务器和客户端)进行校验的——这是用于阻断 NTLM 中继/反射类攻击 (CVE-2025-33073, CVE-2026-24294) 的控制措施。服务器端配置决定了最终结论;当未定义时结果为 `UNKNOWN,因为此时将应用操作系统的默认设置,而成员服务器 / Server 2025 默认不要求此设置。
**匿名 LDAP 姿态**直接从目录中读取:`dSHeuristics`(第 7 个字符,`fLDAPBlockAnonOps`)揭示是否启用了匿名 LDAP 绑定/搜索,并且会检查 **Pre-Windows 2000 Compatible Access** 组中是否包含 Anonymous Logon / Everyone 成员——这两者中的任何一个都会重新开启凭证枚举之前的攻击面。
### v0.6 可 Roasting 的账户 (`KestrelRoast.c`)
- **可 Kerberoasting** - 带有 SPN 的用户帐户(排除 krbtgt)。
- **可 AS-REP Roasting** - 带有 `DONT_REQ_PREAUTH` (UAC `0x400000`) 的账户。
仅检测——绝对不会请求任何票据。扫描结果也会作为节点属性折叠进图中。
### v0.7 域信任姿态 (`KestrelTrust.c`)
枚举 `trustedDomain` 对象并解码方向、类型和 `trustAttributes`。标记在**入站外部**信任上缺失 SID 过滤的情况(经典的 sIDHistory 注入攻击面)、跨信任的 TGT 委派以及 RC4。林内和林传递信任被排除在 SID 过滤检查之外——因为它们默认就会进行过滤,所以标记它们属于误报。
### v0.7 gMSA 密码读取器 (`KestrelGMSA.c`)
解析每个 gMSA 的 `msDS-GroupMSAMembership` DACL,并列出能够检索托管密码的非 SYSTEM 主体。读取者 → gMSA 的边缘将输入到图中(`CanReadGMSAPassword`)。
### v0.7 ADCS 姿态 (`KestrelADCS.c`)
从配置 NC 进行被动的证书模板和 CA 审计,只读模式,以普通用户身份执行:
| 类 | 条件 |
| ----- | ------------------------------------------------------------------------- |
| ESC1 | 登记者提供主题 (enrollee-supplies-subject) + 身份验证 EKU + 低权限可注册 + 无需批准/会签 |
| ESC2 | Any-Purpose(或无)EKU + 低权限可注册 |
| ESC3 | Certificate-Request-Agent EKU + 低权限可注册 |
| ESC4 | 模板对象可被宽泛的主体写入(报告权限 + SID) |
| ESC5 | CA / 注册服务对象可被宽泛的主体写入 |
| ESC9 | 身份验证模板上的 `NO_SECURITY_EXTENSION` |
扫描结果将与实际由 CA 发布的模板进行交叉引用——未发布的模板会被报告,但标记为 `published: no`。属性范围内的 WriteProperty 和默认锁定的模板会被排除在 ESC4 之外,以避免误报。
ESC6(CA 注册表标志)、ESC7(CA 角色 ACL)和 ESC8(Web 注册端点)被有意**排除在范围之外**:在被动 LDAP 读取中均无法观测到这三者。
### v0.7 GPP cpassword 恢复 (`KestrelGPP.c`)
通过 SMB 遍历 SYSVOL 并解析每个组策略首选项 XML(Groups、Services、ScheduledTasks、DataSources、Drives、Printers)以查找 `cpassword`——即 Microsoft 在 2014 年 (MS14-025) 公开了密钥的 AES 加密凭据。任何域用户都可以还原它们,因此它们会被解密并显示出来(连同账户和 GPO),以证明其可还原性并强制轮换。虽然大部分是遗留问题,但旧值会在 SYSVOL 上保留多年。足迹与策略审计相同(一次针对 SYSVOL 的 SMB 读取)。明文缓冲区会使用 `SecureZeroMemory` 进行清理。
### v0.8 默认 ACL 基线 (`KestrelBaseline.c`)
不是一项扫描,而是用于 ACL 模块的过滤器。它从两个权威的、普通用户的纯 LDAP 数据源——每个 `classSchema` 的 `defaultSecurityDescriptor`,以及 `AdminSDHolder` 的 DACL(针对 `adminCount=1` 的对象)——构建“预期”的 ACE 基线,然后抑制与其匹配的对象 ACE。剩下的就是真正的、由管理员引入的委派,而不是每个对象在诞生时继承的权限。`--acl-raw` 将其关闭以显示原始集合。
## 路线图
| 版本 | 状态 | 描述 |
| ------- | ------ | --------------------------------------------------------------------------- |
| v0.1 | ✅ | 五项被动 AD 扫描 |
| v0.2 | ✅ | ACL 边缘提取 + DCSync 权限 |
| v0.3 | ✅ | 通过 LDAP\_MATCHING\_RULE\_IN\_CHAIN 实现可传递的组成员身份 |
| v0.4 | ✅ | 基于 ACL + 成员身份 + 委派的内存图。支持 HTML / JSON / YAML。 |
| v0.5 | ✅ | BFS 路径查找器 + GPO 安全策略审计 |
| v0.6 | ✅ | 可 Kerberoasting + 可 AS-REP Roasting 检测 |
| v0.7 | ✅ | 信任姿态 · gMSA 密码读取器 · ADCS ESC1-5/9 · GPP cpassword |
| v0.8 | ✅ | 默认 ACL 基线(抑制委派噪音) · Onelogon 检测 |
| v0.9 | ✅ | LAPS 健康异常 · SMB 签名 (NTLM 中继) · 匿名 LDAP 姿态 · 描述泄露扫描 |
| - | 🔲 | `ms-DS-MachineAccountQuota` + RBCD 武器化丰富度 |
| - | 🔲 | 图表与报告中的信任 / ADCS 边缘 |
| - | 🔲 | 以 ADExplorer 快照作为离线输入源(随时间进行差异对比) |
## 截图
ADWS 扫描进行中…
[](/ssteelfactor-oss/Kestrel/blob/main/assets/ADWSScan.png)
检测过期 / 活动节点…
[](/ssteelfactor-oss/Kestrel/blob/main/assets/stall-active.png)
搜索域 SID…
[](/ssteelfactor-oss/Kestrel/blob/main/assets/DomainSID.png)
## 代码质量
**SAL 2.0 注解**应用于每个函数签名,并在编译时通过 PREfast (`/analyze`) 进行验证。在返回 HRESULT 的函数上使用 `_Must_inspect_result_`,并根据语义差异使用 `_Outptr_` 或 `_Out_`。
**单一 rootDSE 解析** - `defaultNamingContext` 和 `configurationNamingContext` 在启动时仅读取一次,并作为参数传递。没有冗余的 DC 往返。
**无运行时依赖**,当使用 `/MT` 构建时——单个可执行文件,目标机器上不需要 VCRUNTIME DLL。
## 相关项目
父项目:[NetEnum](https://github.com/ssteelfactor-oss/NetEnum) - 通过 ADSI/COM/LDAP 进行 AD 枚举。
## 作者
[@ssteelfactor-oss](https://github.com/ssteelfactor-oss)
安全研究与 COM/Windows 内部机制
标签:Active Directory, ADSI, Checkov, Plaso, Windows, 安全枚举, 客户端加密