ZakiPedio/BridgeHead
GitHub: ZakiPedio/BridgeHead
纯 C++20 实现的 ADWS 协议栈静态库,无需 .NET/WCF/HTTP 即可通过 9389 端口直接访问和操作 Active Directory。
Stars: 28 | Forks: 0
# BridgeHead
**BridgeHead** 是一个 C++20 静态库,直接通过 TCP 实现了完整的 **Active Directory Web Services (ADWS)** 协议栈。它以 AD bridgehead 服务器(目录流量流经的网关)命名,为你的 C++ 代码提供了与 PowerShell 的 `Get-ADUser` 和 `Get-ADComputer` 底层使用的相同的 9389 端口低级访问权限。
## 目录
- [协议栈](#protocol-stack)
- [快速开始](#quick-start)
- [API 参考](#api-reference)
- [构建](#build)
- [集成](#integration)
- [平台支持](#platform-support)
- [依赖项](#dependencies)
- [已知限制](#known-limitations)
- [协议参考](#protocol-references)
- [许可证](#license)
## 协议栈
传输层通过通用的 `bridgehead::transport::IByteStream` 接口封装其下层协议。`NbfseCodec` 是一个编解码器工具,由 `AdwsClient` 调用,用于在组帧前后对 SOAP 消息进行编码/解码:
```
AdwsClient WS-Enumeration + WS-Transfer [MS-ADDM]
├── NbfseCodec .NET Binary Format for SOAP [MC-NBFSE] (encode/decode)
└── NmfFramer .NET Message Framing [MC-NMF] (send/receive frames)
└── NnsSession .NET NegotiateStream [MS-NNS]
└── TcpSocket raw TCP/IP
```
使用者的入口点是 `bridgehead::adws::AdwsClient`。
## 快速开始
### 查询,枚举所有用户
```
#include "bridgehead/adws/AdwsClient.hpp"
// NTLM (works on any host)
auto client = bridgehead::adws::AdwsClient::EnumerationClient(
"192.168.1.10", // DC IP or hostname
"DC01.corp.local", // DC FQDN, used in NMF Via header and Kerberos SPN
"CORP", // NetBIOS domain name
"Administrator", // username
"Passw0rd" // password
);
// Kerberos (username hidden on the wire; requires DC reachable on port 88)
auto client = bridgehead::adws::AdwsClient::EnumerationClient(
"192.168.1.10", "DC01.corp.local", "CORP",
"Administrator", "Passw0rd",
bridgehead::adws::AuthPackage::Kerberos
);
auto users = client.Query(
"(objectClass=user)",
{"sAMAccountName", "distinguishedName", "memberOf"}
);
for (auto& obj : users)
std::cout << obj.FirstValue("sAMAccountName") << '\n';
```
### 流式处理大型结果集
```
client.Enumerate(
"(objectClass=computer)",
{"dNSHostName", "operatingSystem"},
"", // empty = domain root base DN
[](const bridgehead::adws::LdapObject& obj) {
std::cout << obj.FirstValue("dNSHostName") << '\n';
return true; // return false to stop early (sends wsen:Release)
}
);
```
### 范围和分页
```
// OneLevel scope, 50 objects per Pull round-trip
client.Query(
"(objectClass=user)",
{"sAMAccountName"},
"OU=Admins,DC=corp,DC=local",
50,
bridgehead::adws::SearchScope::OneLevel
);
```
### 二进制属性,objectGUID, objectSid
```
auto objs = client.Query("(objectClass=user)", {"objectGUID", "objectSid"});
for (auto& obj : objs) {
if (auto* b = obj.FirstBytes("objectGUID"))
std::cout << bridgehead::adws::ParseGuid(*b) << '\n'; // {XXXXXXXX-...}
if (auto* b = obj.FirstBytes("objectSid"))
std::cout << bridgehead::adws::ParseSid(*b) << '\n'; // S-1-5-...
}
```
### 安全描述符解码
```
#include "bridgehead/adws/SecurityDescriptor.hpp"
auto objs = client.Query("(objectClass=user)", {"nTSecurityDescriptor"});
if (auto* raw = objs[0].FirstBytes("nTSecurityDescriptor")) {
auto sd = bridgehead::adws::ParseSecurityDescriptor(*raw);
std::cout << "Owner: " << sd.ownerSid << '\n';
for (auto& ace : sd.dacl.aces)
std::cout << " type=" << (int)ace.type
<< " mask=0x" << std::hex << ace.mask
<< " sid=" << ace.sid << '\n';
}
```
### 写入属性 (Resource endpoint)
```
auto rc = bridgehead::adws::AdwsClient::ResourceClient(
"192.168.1.10", "DC01.corp.local", "CORP", "Administrator", "Passw0rd");
// Modify attributes
rc.Put("CN=Alice,OU=Users,DC=corp,DC=local", {
{"description", {"managed by bridgehead"}},
{"telephoneNumber", {"555-1234"}},
});
// Clear an attribute (both values and bytes empty = delete)
rc.Put("CN=Alice,OU=Users,DC=corp,DC=local", {
{"telephoneNumber", {}},
});
// Read back
auto obj = rc.Get("CN=Alice,OU=Users,DC=corp,DC=local",
{"description", "telephoneNumber"});
// Delete object
rc.Delete("CN=TempUser,OU=Users,DC=corp,DC=local");
```
### LDAP 修改类型, Add / Replace / Delete
`Put` 默认为 `Replace`(覆盖所有现有值)。在多值属性上需要进行细粒度控制时,请使用 `ModifyOperation`:
```
using bridgehead::adws::LdapModification;
using bridgehead::adws::ModifyOperation;
rc.Put("CN=Alice,OU=Users,DC=corp,DC=local", {
// Append a value to an existing multi-valued attribute
LdapModification{"otherTelephone", {"555-9999"}, {}, ModifyOperation::Add},
// Remove one specific value (leave others intact)
LdapModification{"otherTelephone", {"555-0000"}, {}, ModifyOperation::Delete},
// Replace is the default, explicit here for clarity
LdapModification{"description", {"updated"}, {}, ModifyOperation::Replace},
});
```
### 移动 / 重命名
```
// Move to a different OU
rc.Move(
"CN=Alice,OU=OldOU,DC=corp,DC=local", // current DN
"CN=Alice,OU=NewOU,DC=corp,DC=local" // new DN
);
// Rename in place (same parent, new CN)
rc.Move(
"CN=Alice,OU=Users,DC=corp,DC=local",
"CN=AliceSmith,OU=Users,DC=corp,DC=local"
);
```
### 写入二进制属性
在 `LdapModification` 的 `bytes` 字段中提供二进制值。它们会在线上自动进行 base64 编码:
```
std::vector thumbnail = loadFile("photo.jpg");
rc.Put("CN=Alice,OU=Users,DC=corp,DC=local", {
LdapModification{"thumbnailPhoto", {}, {thumbnail}},
});
```
### 创建对象 (ResourceFactory endpoint)
```
auto rf = bridgehead::adws::AdwsClient::ResourceFactoryClient(
"192.168.1.10", "DC01.corp.local", "CORP", "Administrator", "Passw0rd");
rf.Create(
"CN=NewUser,OU=Users,DC=corp,DC=local",
"user",
{{"sAMAccountName", {"newuser"}}, {"userAccountControl", {"512"}}}
);
```
### 异步操作
```
#include "bridgehead/adws/AdwsClientAsync.hpp"
auto ac = bridgehead::adws::AdwsClientAsync::EnumerationClient(
"192.168.1.10", "DC01.corp.local", "CORP", "Administrator", "Passw0rd");
auto future = ac.QueryAsync("(objectClass=user)", {"sAMAccountName"});
// ... do other work while the query runs ...
auto users = future.get(); // blocks until complete; re-throws any exception
```
对响应缓慢的 DC 设置超时:
```
if (future.wait_for(std::chrono::seconds(5)) == std::future_status::timeout) {
// query is still running
}
```
所有操作都有异步变体:`QueryAsync`, `EnumerateAsync`, `GetAsync`, `PutAsync`, `DeleteAsync`, `CreateAsync`, `MoveAsync`。
### 连接池(高频 / 多线程工作负载)
```
#include "bridgehead/adws/AdwsClientPool.hpp"
// Enumeration pool, Query / Enumerate
bridgehead::adws::AdwsClientPool pool({
.host = "192.168.1.10", .fqdn = "DC01.corp.local",
.domain = "CORP", .username = "Administrator", .password = "Passw0rd",
.maxSize = 4, // up to 4 concurrent authenticated sessions
});
auto users = pool.Query("(objectClass=user)", {"sAMAccountName"});
// Resource pool, Get / Put / Delete
bridgehead::adws::AdwsClientPool resPool({
.host = "192.168.1.10", .fqdn = "DC01.corp.local",
.domain = "CORP", .username = "Administrator", .password = "Passw0rd",
.maxSize = 4,
.endpoint = bridgehead::adws::PoolEndpoint::Resource,
});
auto obj = resPool.Get("CN=Alice,OU=Users,DC=corp,DC=local", {"mail"});
resPool.Put("CN=Alice,OU=Users,DC=corp,DC=local", {{"mail", {"alice@corp.local"}}});
// ResourceFactory pool, Create
bridgehead::adws::AdwsClientPool rfPool({
.host = "192.168.1.10", .fqdn = "DC01.corp.local",
.domain = "CORP", .username = "Administrator", .password = "Passw0rd",
.endpoint = bridgehead::adws::PoolEndpoint::ResourceFactory,
});
rfPool.Create("CN=NewUser,OU=Users,DC=corp,DC=local", "user",
{{"sAMAccountName", {"newuser"}}});
```
## API 参考
所有公共头文件都位于 `include/bridgehead/` 下。完整的 API 文档可以使用 Doxygen 生成(参见 [构建](#build))。
### `bridgehead::adws::AdwsClient`
| 方法 | Endpoint | 描述 |
|--------|----------|-------------|
| `EnumerationClient(host, fqdn, domain, user, pass [, auth, timeoutMs, opTimeoutMs, limits])` | `/Enumeration` | 工厂,连接并认证 |
| `ResourceClient(...)` | `/Resource` | 用于 Get / Put / Delete / Move 的工厂 |
| `ResourceFactoryClient(...)` | `/ResourceFactory` | 用于 Create 的工厂 |
| `Query(filter, attrs [, baseDN, maxElems, scope])` | Enumeration | 将所有结果收集到 `vector` 中 |
| `Enumerate(filter, attrs, baseDN, callback [, maxElems, scope])` | Enumeration | 通过回调流式传输结果 |
| `Get(dn, attrs)` | Resource | 读取单个对象的属性 |
| `Put(dn, modifications)` | Resource | 修改属性 |
| `Delete(dn)` | Resource | 删除对象 |
| `Create(dn, objectClass, attrs)` | ResourceFactory | 创建新对象 |
| `Move(dn, newDn)` | Resource | 移动或重命名现有对象 |
### `bridgehead::adws::AdwsClientAsync`
基于 `std::future` 的异步封装器。具有与 `AdwsClient` 相同的工厂方法;每个操作返回一个 `std::future`:
```
std::future> f = ac.QueryAsync(...);
std::future e = ac.EnumerateAsync(filter, attrs, baseDN, callback);
std::future g = ac.GetAsync(...);
std::future h = ac.PutAsync(...);
std::future i = ac.DeleteAsync(...);
std::future j = ac.CreateAsync(...);
std::future k = ac.MoveAsync(...);
```
对于跨多个连接的并发查询,请使用 `AdwsClientPool`。
### `bridgehead::adws::AdwsClientPool`
线程安全的预认证会话池。连接采用延迟创建,并在调用间复用。
```
pool.IdleCount(); // sessions currently idle
pool.TotalCount(); // idle + checked-out
pool.MaxSize(); // configured maximum pool size
pool.Move(dn, newDn); // Move/rename (Resource pool)
```
### `bridgehead::adws::SearchScope`
```
enum class SearchScope { Base, OneLevel, Subtree /*default*/ };
```
### `bridgehead::adws::LdapObject` / `LdapAttribute`
```
struct LdapAttribute {
std::string name;
std::string syntax; // LdapSyntax OID, empty if absent
std::vector values; // text values (raw base64 for binary)
std::vector> bytes; // decoded binary; parallel to values
};
struct LdapObject {
std::vector attributes; // all returned attributes
const LdapAttribute* Find(const std::string& name) const; // case-insensitive
std::string FirstValue(const std::string& name) const;
const std::vector* FirstBytes(const std::string& name) const;
};
```
### 二进制辅助工具
```
std::string ParseGuid(const std::vector& bytes); // → "{XXXXXXXX-XXXX-...}"
std::string ParseSid (const std::vector& bytes); // → "S-1-5-..."
```
### `FilterValue` 和过滤器构建辅助工具
`FilterValue` 是一个类型安全的包装器,在构造时会转义 RFC 4515 §3 的元字符(`\`, `*`, `(`, `)`, NUL)。将其与构建器辅助工具结合使用,可以从结构上杜绝 LDAP 注入:
```
// UNSAFE, raw string concatenation, easy to forget escaping
client.Query("(sAMAccountName=" + username + ")", ...);
// SAFE, FilterValue escapes on construction; FilterEq composes the assertion
FilterValue user = username;
client.Query(FilterEq("sAMAccountName", user), ...);
// Compose complex filters
client.Query(
FilterAnd({
FilterEq("objectClass", FilterValue::Raw("user")), // Raw() for safe literals
FilterOr({
FilterEq("sAMAccountName", user),
FilterEq("mail", FilterValue(email)),
}),
FilterNot(FilterPresent("userAccountControl")),
}), {"sAMAccountName", "mail"}
);
```
当你需要手动构建过滤器字符串时,`EscapeLdapFilter(str)` 仍可作为较低层级的原语使用。
### `DnValue` 和 DN 构建辅助工具
模式与 `FilterValue` 相同,但用于 Distinguished Name RDN 值 (RFC 4514 §2.4)。转义 `,`, `+`, `"`, `\`, `<`, `>`, `;`, NUL,以及开头/结尾的 `#` 和空格:
```
// UNSAFE, comma injection breaks the DN structure
rc.Get("CN=" + username + ",OU=Users,DC=corp,DC=local", attrs);
// SAFE
DnValue cn = username; // auto-escaped
auto dn = BuildDn({DnAttr("CN", cn), "OU=Users", "DC=corp", "DC=local"});
rc.Get(dn, attrs);
// DnValue::Raw() for values you control
auto dn2 = BuildDn({DnAttr("CN", DnValue::Raw("Service Account")), "OU=SvcAccounts", "DC=corp", "DC=local"});
```
### `bridgehead::adws::SecurityDescriptor`
```
SecurityDescriptor ParseSecurityDescriptor(const std::vector& bytes);
struct SecurityDescriptor {
uint16_t control; // SdControl::* flags
std::string ownerSid;
std::string groupSid;
bool hasDacl; Acl dacl;
bool hasSacl; Acl sacl;
};
struct Acl { std::vector aces; };
struct Ace {
uint8_t type; // AceType::*
uint8_t flags; // AceFlags::*
uint32_t mask;
std::string sid;
std::string objectType; // GUID string, object ACEs only
std::string inheritedObjectType; // GUID string, object ACEs only
};
```
常量命名空间:`AceType::*`, `AceFlags::*`, `SdControl::*`。
### `bridgehead::ConnectionError` / `AuthenticationError` / `ProtocolError`
定义在 `include/bridgehead/Exceptions.hpp` 中(由 `AdwsClient.hpp` 传递包含)。这三者都继承自 `std::runtime_error`:
| 类型 | 抛出时机 |
|------|-------------|
| `ConnectionError` | TCP 层级故障:拒绝、超时、I/O 错误 |
| `AuthenticationError` | NTLM/Kerberos 协商失败 |
| `ProtocolError` | 任何协议层的格式错误服务器响应 |
```
try {
auto client = bridgehead::adws::AdwsClient::EnumerationClient(...);
auto users = client.Query(...);
} catch (const bridgehead::ConnectionError& e) {
// unreachable DC, wrong port, timeout
} catch (const bridgehead::AuthenticationError& e) {
// bad credentials, KDC unreachable
} catch (const bridgehead::ProtocolError& e) {
// unexpected server response
} catch (const bridgehead::adws::SoapFault& e) {
// DC returned a SOAP fault (e.g. invalid filter)
}
// or coarse-grained:
// } catch (const std::runtime_error& e) { ... }
```
### `bridgehead::adws::SoapFault`
当 DC 返回任何 `s:Fault` 响应时抛出,而不是抛出普通的 `std::runtime_error`:
```
struct SoapFault : std::runtime_error {
std::string code; // e.g. "Sender"
std::string subcode; // e.g. "InvalidEnumerationContext", empty if absent
std::string reason; // human-readable text
};
```
### `bridgehead::adws::LdapModification`
由 `Put` 和 `Create` 使用:
```
enum class ModifyOperation { Replace /*default*/, Add, Delete };
struct LdapModification {
std::string name;
std::vector values; // text values
std::vector> bytes; // binary values (base64-encoded on the wire)
ModifyOperation operation = ModifyOperation::Replace;
};
```
当 `values` 和 `bytes` 均为空时,无论 `operation` 为何,该属性都会被清除/删除。
### `bridgehead::adws::ProtocolLimits`
所有工厂方法、`AdwsClientAsync` 工厂和 `AdwsClientPool::Config::limits` 的可选最后参数。所有字段都有安全的默认值,仅在你有特定需求时更改它们:
```
struct ProtocolLimits {
uint32_t nmfMaxFrameBytes = 64 * 1024 * 1024; // max NMF frame (default 64 MiB)
uint32_t nnsMaxPayloadBytes = 16 * 1024 * 1024; // max NNS packet (default 16 MB)
int tcpKeepaliveIdleSec = 60; // idle seconds before first probe
int tcpKeepaliveIntervalSec = 10; // seconds between probes
int tcpKeepaliveProbeCount = 5; // probes before declaring dead (POSIX only)
};
```
仅在读取具有异常大二进制属性的对象(例如,包含许多 ACE 的 `nTSecurityDescriptor`,或大型 `thumbnailPhoto`)时,才提高 `nmfMaxFrameBytes` / `nnsMaxPayloadBytes`。在空闲连接会被积极断开的云/NAT 环境中,应降低 keepalive 字段。
命名构造函数辅助工具(内联静态工厂):
```
LdapModification::Replace(name, values) // Replace with text values (default)
LdapModification::ReplaceBinary(name, bytes) // Replace with binary values
LdapModification::Append(name, values) // Add to multi-valued attribute
LdapModification::Remove(name, values={}) // Remove specific values (or all)
LdapModification::Clear(name) // Delete the attribute entirely
```
## 构建
**要求:** CMake 3.25+ 和支持 C++20 的编译器(MSVC 2022+, GCC 12+, Clang 15+)。
```
# 配置并构建 (仅单元测试,无需实时 AD)
cmake -B build -A x64
cmake --build build --config Release
# 运行单元测试
ctest --test-dir build -C Release --output-on-failure
# 包含集成测试 (需要实时 Domain Controller)
cmake -B build -A x64 -DBRIDGEHEAD_INTEGRATION_TESTS=ON
cmake --build build --config Release
./build/tests/Release/bridgehead_integration_tests.exe
# 使用 CLI 工具 (adws_list,从命令行浏览 AD 对象)
cmake -B build -A x64 -DBRIDGEHEAD_BUILD_TOOLS=ON
cmake --build build --config Release --target adws_list
./build/tools/Release/adws_list.exe --host --fqdn --domain --user
# 生成 API 参考文档 (需要 Doxygen)
cmake --build build --target docs
# 或者直接:
doxygen Doxyfile
# 输出:docs/doxygen/html/index.html
```
### CMake 选项
| 选项 | 默认值 | 描述 |
|--------|---------|-------------|
| `BRIDGEHEAD_BUILD_TESTS` | `ON` | 构建单元测试套件 |
| `BRIDGEHEAD_INTEGRATION_TESTS` | `OFF` | 构建集成测试(需要在线 DC) |
| `BRIDGEHEAD_BUILD_TOOLS` | `OFF` | 构建 `adws_list` CLI 工具 |
## 集成
### 选项 A, CMake 子目录(无需安装)
```
add_subdirectory(bridgehead)
target_link_libraries(my_target PRIVATE bridgehead::bridgehead)
```
### 选项 B, 已安装包(`find_package`)
```
cmake --install build --prefix /usr/local # or any install prefix
```
```
find_package(bridgehead REQUIRED)
target_link_libraries(my_target PRIVATE bridgehead::bridgehead)
```
`pugixml`, `ws2_32`, 和 `secur32` (Windows) / `gssapi_krb5` (Linux/macOS) 均通过传递方式被引入。
## 平台支持
| 平台 | 认证后端 | 状态 |
|----------|-------------|--------|
| Windows | SSPI (`secur32.dll`), NTLM 和 Kerberos | **正常工作** |
| Linux / macOS | GSSAPI (`libgssapi_krb5`), SPNEGO / Kerberos | 已编译;尚未进行集成测试 |
`AuthPackage::Ntlm` 和 `AuthPackage::Kerberos` 都能在 Windows 上工作。Kerberos 在传输过程中隐藏了用户名,在生产环境中是首选;当 KDC(端口 88)不可达时,NTLM 是回退方案。
在 Linux/macOS 上,GSSAPI 后端使用带有 Kerberos 的 SPNEGO。如需 NTLM 支持,请安装 `gss-ntlmssp` 插件:
```
apt install libgss-ntlmssp # Debian / Ubuntu
dnf install gssntlmssp # Fedora / RHEL
```
如果没有该插件,服务器将协商使用 Kerberos。显式的用户/密码凭据使用 `gss_acquire_cred_with_password`(MIT Kerberos 1.9+);传递空字符串以使用默认凭据缓存(`kinit`)。GSSAPI 路径编译无误且设计正确,但尚未针对在线 DC 进行端到端验证,请将 Linux/macOS 支持视为 Beta 版本。
## 依赖项
在配置时通过 `cmake/Dependencies.cmake` 自动获取,无需手动安装:
| 库 | 版本 | 用途 |
|---------|---------|---------|
| [Catch2](https://github.com/catchorg/Catch2) | v3.5.2 | 单元测试框架(仅限测试目标) |
| [pugixml](https://github.com/zeux/pugixml) | v1.14 | XML DOM(仅用于 ADWS 响应解析) |
无 OpenSSL。无 Asio。无 Boost。
## 已知限制
- **Linux/macOS 上的 NTLM** 需要 `gss-ntlmssp` GSSAPI 插件(参见 [平台支持](#platform-support))。没有它,服务器将协商使用 Kerberos。Windows 上的 NTLM 通过 SSPI 原生工作。
- **pugixml DOM**,每个 SOAP 响应在返回任何结果之前都会被完全解析为内存中的 XML 树。这受到 `maxElements` 页面大小的限制(默认每次 Pull 256 个对象),因此完整的结果集永远不会一次性保存在内存中。只有在使用非常大的页面大小或包含大型二进制属性的对象(例如,具有许多 ACE 的对象上的 `nTSecurityDescriptor`)时,内存才会成为问题。SAX/流式方法可以消除这种情况,但目前尚未计划。
## 协议参考
- **[MS-ADDM]**, Active Directory Web Services: Data Model and Common Elements
- **[MS-NNS]**, .NET NegotiateStream Protocol
- **[MC-NMF]**, .NET Message Framing
- **[MC-NBFSE]**, .NET Binary Format: SOAP Extension
- **[MS-DTYP]**, Windows Data Types (SECURITY_DESCRIPTOR, ACL, ACE, SID, GUID)
- **[MS-ADTS]**, Active Directory Technical Specification (LDAP filters / attributes)
所有规范均可从 [Microsoft 开放规范文档](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-winprotlp/) 获取。
## 特别感谢
特别感谢 IBM X-Force 和 SoaPy 项目提供的灵感、研究以及公开发布的工作,这些都对本项目有所帮助。我还要感谢 Logan Goins,他是 IBM X-Force Red SoaPy 的创建者,正是他的努力和洞察力使那项工作成为可能。
## 许可证
详情请见 [LICENSE](LICENSE)。
标签:Active Directory, ADWS, Bash脚本, C++20, Conpot, HTTP, LDAP替代, NTLM, Plaso, SOAP, TCP/IP, Windows安全, 协议栈, 原生开发, 域渗透, 并发处理, 无.NET, 横向移动, 电子数据取证, 目录服务, 编程规范, 网络编程, 静态库