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 扫描进行中… [![Kestrel 输出](https://static.pigsec.cn/wp-content/uploads/repos/cas/16/1695a8a17078164699e99e83c7be7225ee4500e6c12ed9c4e5647be62c6529a8.png)](/ssteelfactor-oss/Kestrel/blob/main/assets/ADWSScan.png) 检测过期 / 活动节点… [![Kestrel 输出](https://static.pigsec.cn/wp-content/uploads/repos/cas/1b/1b0a3327ef6cd0c90b4d0e0b2c1e6670c12330f6e8884a53982e083394fc3ea5.png)](/ssteelfactor-oss/Kestrel/blob/main/assets/stall-active.png) 搜索域 SID… [![Kestrel 输出](https://static.pigsec.cn/wp-content/uploads/repos/cas/75/75920b821b1526dd52eb56c2b099c8ae62d8be947339e8c3fbcd08dd78a103f4.png)](/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, 安全枚举, 客户端加密