纯 Nim 语言实现的 WinRM 客户端库,支持 NTLM/Kerberos 认证、PowerShell/CMD 远程执行、文件传输和内存中 .NET 程序集执行。
winrm.nim
Nim 原生 WinRM 客户端库
版本 1.0.0 ·
作者 Chokri Hammedi (blue0x1) ·
许可证 MIT
一个完整、无依赖的 WinRM 协议栈实现,包括 NTLM 认证、通过 GSSAPI 的 Kerberos、PSRP(PowerShell 远程处理协议)、WinRS(Windows 远程 Shell)、NTLM 消息加密、文件传输和 .NET 程序集执行。
## 法律声明
本库仅用于合法管理、安全测试以及对您拥有或已获得明确访问权限的系统进行研究。作者不对本软件被误用或造成的损害承担责任。
## 功能特性
| 领域 | 详情 |
| --- | --- |
| 认证 | NTLMv2 密码、NTLMv2 哈希传递、通过 `libgssapi_krb5` 的 Kerberos |
| 传输 | HTTP、HTTPS/TLS、NTLM 消息加密(密封) |
| PowerShell | PSRP 会话/runspace 管理、管道创建、输出解码 |
| CMD | WinRS shell 创建、命令执行、二进制输出 |
| 传输 | 带自适应重试的分块 Base64 上传、流式下载 |
| 内存中 | PowerShell 脚本导入、托管 .NET 程序集检测和执行 |
| 加密 | 原生 MD4、MD5、HMAC-MD5、RC4、NTLM 密钥派生(认证不使用 OpenSSL)|
| 协议 | SOAP/WS-Management 信封构造、SPNEGO/ASN.1 DER 编码 |
## 环境要求
| 组件 | 要求 |
| --- | --- |
| Nim | `>= 1.6.0` |
| Kerberos | `libgssapi_krb5.so.2`(Linux)或 `libgssapi_krb5.dylib`(macOS)|
| TLS | OpenSSL(`-d:ssl`)|
## 安装
### Nimble
```
nimble install winrm
```
### 手动安装
将 `winrm.nim` 复制到您的项目中并导入:
```
import winrm
```
## 快速开始
### NTLM 认证
```
import winrm
var client = newClient(
host = "192.168.1.10",
user = "CORP\\administrator",
pass = "Password123",
ntHash = "",
spn = "",
domain = "",
auth = amNtlm,
ssl = false,
port = 5985
)
warmSmartShell(client)
let output = runCmd(client, "whoami", isCmd = false)
echo output
deleteShell(client)
```
### 哈希传递
```
import winrm
var client = newClient(
host = "192.168.1.10",
user = "CORP\\administrator",
pass = "",
ntHash = "aad3b435b51404eeaad3b435b51404ee:0123456789abcdef0123456789abcdef",
spn = "",
domain = "",
auth = amNtlm,
ssl = false,
port = 5985
)
warmSmartShell(client)
echo runCmd(client, "hostname", isCmd = false)
deleteShell(client)
```
### Kerberos
```
import winrm
# Set KRB5CCNAME=FILE:/tmp/user.ccache before running
var client = newClient(
host = "dc01.corp.local",
user = "",
pass = "",
ntHash = "",
spn = "",
domain = "CORP.LOCAL",
auth = amKerberos,
ssl = false,
port = 5985
)
warmSmartShell(client)
echo runCmd(client, "Get-ADUser -Filter * | Select-Object Name", isCmd = false)
deleteShell(client)
```
### CMD 执行
```
warmSmartShell(client)
echo runCmdFast(client, "ipconfig /all", isCmd = true)
deleteShell(client)
```
### NTLM 加密(密封)
```
var client = newClient(
host = "192.168.1.10",
user = "CORP\\administrator",
pass = "Password123",
ntHash = "",
spn = "",
domain = "",
auth = amNtlm,
ssl = false,
port = 5985,
msgEnc = meAlways
)
```
## API 参考
### 类型
```
AuthMethod* = enum amNtlm, amKerberos
MessageEncryption* = enum meAuto, meAlways, meNever
WinRMClient* = object
host, username, password, ntHash, spn, domain: string
auth: AuthMethod
msgEnc: MessageEncryption
useSSL: bool
port: int
shellId*: string
remoteCwd*: string
cmdShellDenied*: bool
# ... internal fields
ChunkCallback* = proc(chunk: string)
PsrpDefragmenter* = object
```
### 错误类型
```
WinRMError* # Base error
WinRMAuthorizationError* # Authentication failure
InvalidShellError* # Shell no longer valid
WinRMWSManFault* # WS-Management fault (faultCode, faultDescription)
WinRMSoapFault* # SOAP fault (code, subcode, reason)
WinRMWMIError* # WMI error (errorCode, error)
WinRMHTTPTransportError* # HTTP transport error (statusCode)
```
### 客户端
| 过程 | 描述 |
| --- | --- |
| `newClient*(host, user, pass, ntHash, spn, domain, auth, ssl, port, msgEnc): WinRMClient` | 创建新的 WinRM 客户端 |
| `warmSmartShell*(c)` | 初始化最佳 shell(PSRP 或 WinRS)|
| `ensureShell*(c, waitOpened)` | 确保 PSRP shell/runspace 已就绪 |
| `deleteShell*(c)` | 关闭并清理活动的 shell |
| `closeNtlm*(c)` | 关闭 NTLM 套接字 |
| `resetTransport*(c)` | 重置传输状态以重新连接 |
### 命令执行
| 过程 | 描述 |
| --- | --- |
| `runCmd*(c, cmd, isCmd, mergeStreams): string` | 通过 PSRP 执行并流式输出 |
| `runCmdCollect*(c, cmd, isCmd, onChunk, mergeStreams): string` | 通过 PSRP 执行并收集完整输出 |
| `runCmdFast*(c, cmd, isCmd): string` | 通过 WinRS CMD shell 执行 |
| `runCmdFastCached*(c, cmd, isCmd, onChunk): string` | 通过缓存的 WinRS CMD shell 执行 |
| `runCmdFastOrPsrp*(c, cmd, isCmd): string` | 先尝试 WinRS,失败则回退到 PSRP |
### 文件传输
| 过程 | 描述 |
| --- | --- |
| `uploadFileStream*(c, data, setup, total)` | 以分块 Base64 片段上传数据 |
| `drawProgress*(label, current, total)` | 在终端渲染进度条 |
### 工具函数
| 过程 | 描述 |
| --- | --- |
| `genUuid*(): string` | 生成随机 UUID |
| `encodePs*(cmd): string` | 将 PowerShell 命令进行 Base64 编码(UTF-16LE)|
| `isManagedPe*(data): bool` | 检查二进制数据是否为托管 .NET PE |
| `isConnectionLostMessage*(msg): bool` | 检查错误是否表示连接丢失 |
| `psrpTextValue*(v): string` | 从 PSRP 值元素中提取文本 |
| `psrpHexDecode*(text): string` | 解码 PSRP 十六进制编码的 Unicode 文本 |
| `isRetryableFault*(faultCode): bool` | 检查 WS-Management 错误是否可重试 |
| `secToDur*(seconds): string` | 将秒数格式化为人类可读的时长 |
### PSRP
| 过程 | 描述 |
| --- | --- |
| `newPsrpDefragmenter*(): PsrpDefragmenter` | 创建 PSRP 消息解碎片器 |
| `defragment*(d, base64Data): (complete, msgType, data)` | 输入一个碎片并检查是否完成 |
| `fragmentMessage*(objectId, msg, maxSize): seq[string]` | 将 PSRP 消息分片为 Base64 块 |
### 常量
```
RESOURCE_URI_CMD* = "http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd"
RESOURCE_URI_POWERSHELL* = "http://schemas.microsoft.com/powershell/Microsoft.PowerShell"
WSManMaxEnvelope* = 153600
WSManOperationTimeout* = 60
```
## 架构
该库在一个文件中实现了完整的 WinRM 协议栈:
- **NTLM**:使用原生 MD4/MD5/HMAC-MD5/RC4 的 NTLMv2 协商/挑战/认证
- **Kerberos**:GSSAPI FFI 调用系统 `libgssapi_krb5`,使用 SPNEGO 包装
- **SOAP/WS-Man**:用于 shell 生命周期和命令执行的 XML 信封构造
- **PSRP**:PowerShell 远程处理协议,包含会话能力交换、runspace 管理、管道创建、消息分片/解碎片和输出解码
- **WinRS**:用于 CMD 执行的 Windows 远程 Shell
- **NTLM 密封**:用于 HTTP 传输的消息加密/解密和签名
- **Kerberos 包装**:GSS-API `gss_wrap`/`gss_unwrap` 用于消息保护
## 许可证
MIT。参见 [LICENSE](LICENSE)。