mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-22 03:10:42 +08:00
* add CVE-2026-24061 detect logic (#562) * add CVE-2026-24061 detect logic * fix(telnet): 修复 errcheck 警告,统一错误处理风格 --------- Co-authored-by: ZacharyZcR <[email protected]> * fix: 修复 Hub 广播 data race 和端口扫描潜在死锁,清理死代码 - hub.go: broadcast 路径 RLock 改 Lock,修复并发 delete/close 竞争 - port_scan.go: pool.Invoke 失败时释放 wg 和 semaphore,防止死锁 - web_scanner.go: 删除只写不读的 fingerprintCache - webtitle.go: 移除对已删除 SetFingerprints 的调用 - keylogger.go: 删除未使用的 stopChan 和 isRunning 字段 * refactor: context 穿透扫描生命周期,修复长驻插件阻塞和 Web Stop 无效 - RunScan 接受 context.Context,创建可取消上下文并穿透到所有策略和插件 - 长驻插件(forwardshell/socks5proxy/reverseshell)不再进入 scan WaitGroup, 通过 ctx.Done() 管理生命周期,解除 wg.Wait() 死锁 - Web Stop API 从 stopChan 改为 context.CancelFunc,取消信号真正传播到扫描链路 - ExecuteScanTasks 和 executeScanTask 支持 context 取消检查,停止分发新任务 - CLI 模式传 context.Background(),行为完全不变 * fix: 修复 Web Stop 信号等待阻塞和 SMB 响应解析越界 panic - scanner.go: 长驻插件等待信号时同时监听 ctx.Done(),Web Stop 可正常返回 - smb_protocol.go: 响应长度检查修正为 47,远端偏移量全部做边界校验 * fix: POC 扫描接入调用方 context,修复 cachedPocPath 竞争和 ProxyStats data race - webscan/web_scan.go: WebScan 接受 ctx 参数,替换 context.Background(); sync.Once 改为 sync.Mutex 保护 POC 加载,消除 cachedPocPath 并发写竞争 - webtitle.go: ctx 从 Scan 穿透到 identifyFingerprintsMulti → triggerPocScan → WebScan - webpoc.go: 传递 ctx 到 WebScan - proxy/types.go: ProxyStats 增加 sync.Mutex - proxy/manager.go: LastConnectTime/LastError/AverageConnectTime 读写加锁 * fix: 修复 ProxyStats 含 mutex 导致的 copylocks 告警 Stats() 方法改为手动构造副本,避免值拷贝复制 sync.Mutex * fix: 补全 HTTP/TLS proxy stats 加锁,修复 RPC/SMB 解析越界和 POC 加载逻辑 - httpdialer.go/tlsdialer.go: LastError/LastConnectTime/AverageConnectTime 加 mutex - findnet.go: RPC 响应结束标记位置 < 4 时跳过截断,防止负数切片 panic - ms17010.go: SMB 会话响应最小长度改为 45,sessionSetupResponse 加长度校验 - web_scan.go: POC 加载失败时不标记 pocLoaded,允许后续重试 - Eval.go: DNSLog 配置去掉 sync.Once,允许多次扫描更新配置 * fix: Web 全局状态同步、字典文件错误提示、长驻插件连接可取消 - scan.go: Web API 构建 config/state 后同步到全局实例 - config_builder.go: 用户名/密码/URL 文件读取失败时输出错误日志 - reverseshell.go: 读命令设 1s 超时,超时后检查 ctx 实现可取消 - forwardshell.go: handleClient 接受 ctx,取消时关闭连接解除阻塞 - socks5proxy.go: handleClient 接受 ctx,取消时关闭连接解除 IO 阻塞 * refactor: 引入 ScanSession,替代全局状态穿透扫描管道 (Phase 1-3) - 新增 common/session.go: ScanSession 结构体封装 Config/State/Params/Dialer - RunScan/Strategy/ExecuteScanTasks/executeScanTask 全部接收 session - Plugin 接口从 Scan(ctx, info, config, state) 改为 Scan(ctx, info, session) - 48 个插件实现统一更新签名 - Web API 构建 ScanSession 传给 RunScan - CLI 模式通过 Initialize() 创建 session * refactor: 全量替换 WrapperTcpWithTimeout 为 session.DialTCP (Phase 4) - core/port_scan.go: EnhancedPortScan/connectWithRetry/scanSinglePort 接入 session - core/service_probe.go: SmartPortInfoScanner 持有 session,重连走 session.DialTCP - core/icmp.go: CheckLive/tcpProbeAlive 接入 session - 17 个 service 插件: 内部 helper 函数全部穿透 ctx+session - 移除插件中冗余的手动 TCP 计数(DialTCP 内部已处理) - plugins/core 下已无 WrapperTcpWithTimeout/SafeTCPDial 调用残留 * refactor: 清除 core/plugins 全局状态依赖,ProgressManager 缓存引用 (Phase 5) - core/alive_scanner.go: GetFlagVars() → session.Params - core/service_scanner.go: GetFlagVars() → session.Params 和 config.Target.Ports - common/progress_manager.go: 缓存 State 和 NoColor 到字段,不再运行时读全局 - common/output_api.go: SaveResult 改用 GetGlobalConfig().Output.DisableSave - common/network.go: WrapperTcpWithTimeout 标记 Deprecated - core/ 和 plugins/ 下已无全局状态调用残留 * fix: 修复 dialer timeout 锁死、CVE 检测绕过 session 和误报问题 * fix: 修复 pocDNSLog data race,穿透 ctx 到全链路,消除残余 net.DialTimeout 绕过 * perf: CVE-2026-24061 检测改并发执行,消除硬 sleep 用 deadline 替代 * feat: 项目缓存系统,跨扫描合并资产,缓存 host:port 避免漏报 * perf: 三阶段性能优化,ICMP 并发提升+TCP 并行探测,端口扫描退避调整,服务探测超时减半 * fix: 修复凭据测试清理 goroutine 无限阻塞导致的 goroutine 泄漏 * fix: 凭据测试连续网络错误短路、resultChan 缓冲防阻塞、timer 泄漏修复 * perf: 大规模扫描网段预筛,按 /24 探活跳过空子网,B 段扫描从 2h+ 降至 2min * fix: 网段预筛从抽样改全覆盖,每台主机发 1 个探测包,消除漏报 * perf: 网段预筛增加网关启发式,.1/.254 多端口优先探测,命中即跳过逐主机兜底 * fix: MSSQL 连接加 encrypt=disable 修复无 TLS 环境扫描失败,Web API 参数校验负数 * feat: Release 增加 armv5 架构支持 * chore: bump version to 2.1.3 * fix: 锁定 golangci-lint 版本为 v2.12.1 修复 CI checksum 校验失败 * fix: golangci-lint 改用 go install 安装,绕过上游安装脚本 checksum 校验问题 * feat: -silent 模式输出 NDJSON 到 stdout,支持 AI agent 管道消费 - 新增 StdoutNDJSONWriter,silent 模式下每条扫描结果实时输出一行 JSON - LogWithProgress 层拦截人类可读日志,绕过 logger sync.Once 初始化时序问题 - 支持 fscan -h xxx -silent | jq 管道用法 * fix rdp invalid random panic (#573) * restore ms17010 legacy detection and exploit (#574) * fix ms17010 legacy packet decoding (#574) * fix csv web title output (#575) * fix web result protocol output (#577) * feat: add -ntp flag to disable TCP supplementary probe * fix: skip TCP supplementary probe in icmp mode * feat: add -debug flag with file logging to fscan_debug.log * fix: resolve golangci-lint errcheck and staticcheck warnings * fix: skip proxy deep verification for SOCKS5 connections (#579) SOCKS5 protocol validates connection reachability at protocol level, deep verification was incorrectly rejecting non-banner services like SMB(445), RPC(139) and Kerberos(88). * fix: exclude timeout from scan failure rate calculation (#578) Timeout is a normal scan result when firewalls drop packets, not a scan failure. Only resource exhaustion errors count toward failure rate. * feat: flatten NDJSON output for AI agent consumption and add SKILL.md * perf: 端口扫描自适应超时,基于 RTT 采样动态调整连接超时 * perf: 四项扫描性能优化 - SO_LINGER=0 快速释放连接,减少 TIME_WAIT 堆积 - 服务探测超时自适应,RTT 采样约束读超时上限 - 端口扫描结果流式传递,pipeline 并行端口扫描和插件执行 - ICMP 批量预构建包和地址,减少发送循环开销 * perf: 六项性能优化 - DNS 解析缓存:sync.Map 缓存避免重复系统调用 - 凭据测试 TCP 预检:不可达目标直接跳过全部凭据 - Web 探测 HTTP Client 复用:全局共享连接池 - 端口扫描 Bloom Filter 去重:替代 map 降低内存 - 进度条 atomic 累加 + 50ms 节流渲染:消除锁竞争 - 服务探针预解码:Init 时预编译,运行时零解码开销 * refactor: replace bloom filter with map for deduplication Bloom filter has false positive risk which can silently drop valid scan results. Map provides exact deduplication with negligible memory overhead at the scale of open ports (typically thousands, not millions). * fix: credential TCP precheck bypass proxy and pipeline goroutine leak - Skip TCP precheck when proxy is enabled, net.DialTimeout cannot reach targets behind SOCKS5/HTTP proxy - Drain stream channel on ctx cancellation to prevent EnhancedPortScan goroutine from blocking on a full channel * fix: stream channel 提前返回未关闭导致 goroutine 泄漏,服务探测超时下限 500ms * fix: resolve golangci-lint errcheck and staticcheck warnings --------- Co-authored-by: r00t <[email protected]>
957 lines
25 KiB
Go
957 lines
25 KiB
Go
//go:build plugin_smb || !plugin_selective
|
|
|
|
package services
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io"
|
|
iofs "io/fs"
|
|
"net"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/hirochachacha/go-smb2"
|
|
"github.com/shadow1ng/fscan/common"
|
|
"github.com/stacktitan/smb/smb"
|
|
)
|
|
|
|
// SMBProtocol SMB协议版本
|
|
type SMBProtocol int
|
|
|
|
const (
|
|
SMBProtocolUnknown SMBProtocol = iota
|
|
SMBProtocol1
|
|
SMBProtocol2
|
|
)
|
|
|
|
func (p SMBProtocol) String() string {
|
|
switch p {
|
|
case SMBProtocol1:
|
|
return "SMBv1"
|
|
case SMBProtocol2:
|
|
return "SMBv2"
|
|
default:
|
|
return "Unknown"
|
|
}
|
|
}
|
|
|
|
// SMBTarget 目标信息(一次探测,到处使用)
|
|
type SMBTarget struct {
|
|
Protocol SMBProtocol
|
|
ComputerName string
|
|
DomainName string
|
|
OSVersion string
|
|
NativeOS string
|
|
NativeLM string
|
|
NTLMFlags []string
|
|
Vulnerable *SMBVuln
|
|
}
|
|
|
|
// SMBVuln 漏洞信息
|
|
type SMBVuln struct {
|
|
CVE20200796 bool // SMB Ghost
|
|
}
|
|
|
|
// Summary 返回SMB信息摘要
|
|
func (t *SMBTarget) Summary() string {
|
|
var parts []string
|
|
parts = append(parts, t.Protocol.String())
|
|
|
|
if t.OSVersion != "" {
|
|
parts = append(parts, t.OSVersion)
|
|
}
|
|
|
|
if t.ComputerName != "" {
|
|
parts = append(parts, t.ComputerName)
|
|
}
|
|
|
|
return strings.Join(parts, " ")
|
|
}
|
|
|
|
// SMB协议数据包定义
|
|
var (
|
|
smbv1NegotiatePacket = []byte{
|
|
0x00, 0x00, 0x00, 0x85, 0xFF, 0x53, 0x4D, 0x42, 0x72, 0x00, 0x00, 0x00, 0x00, 0x18, 0x53, 0xC8,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x00, 0x02, 0x50, 0x43, 0x20, 0x4E, 0x45, 0x54, 0x57, 0x4F,
|
|
0x52, 0x4B, 0x20, 0x50, 0x52, 0x4F, 0x47, 0x52, 0x41, 0x4D, 0x20, 0x31, 0x2E, 0x30, 0x00, 0x02,
|
|
0x4C, 0x41, 0x4E, 0x4D, 0x41, 0x4E, 0x31, 0x2E, 0x30, 0x00, 0x02, 0x57, 0x69, 0x6E, 0x64, 0x6F,
|
|
0x77, 0x73, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x57, 0x6F, 0x72, 0x6B, 0x67, 0x72, 0x6F, 0x75, 0x70,
|
|
0x73, 0x20, 0x33, 0x2E, 0x31, 0x61, 0x00, 0x02, 0x4C, 0x4D, 0x31, 0x2E, 0x32, 0x58, 0x30, 0x30,
|
|
0x32, 0x00, 0x02, 0x4C, 0x41, 0x4E, 0x4D, 0x41, 0x4E, 0x32, 0x2E, 0x31, 0x00, 0x02, 0x4E, 0x54,
|
|
0x20, 0x4C, 0x4D, 0x20, 0x30, 0x2E, 0x31, 0x32, 0x00,
|
|
}
|
|
|
|
smbv1SessionSetupPacket = []byte{
|
|
0x00, 0x00, 0x01, 0x0A, 0xFF, 0x53, 0x4D, 0x42, 0x73, 0x00, 0x00, 0x00, 0x00, 0x18, 0x07, 0xC8,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE,
|
|
0x00, 0x00, 0x40, 0x00, 0x0C, 0xFF, 0x00, 0x0A, 0x01, 0x04, 0x41, 0x32, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD4, 0x00, 0x00, 0xA0, 0xCF, 0x00, 0x60,
|
|
0x48, 0x06, 0x06, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x02, 0xA0, 0x3E, 0x30, 0x3C, 0xA0, 0x0E, 0x30,
|
|
0x0C, 0x06, 0x0A, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x02, 0x0A, 0xA2, 0x2A, 0x04,
|
|
0x28, 0x4E, 0x54, 0x4C, 0x4D, 0x53, 0x53, 0x50, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x82, 0x08,
|
|
0xA2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x05, 0x02, 0xCE, 0x0E, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x57, 0x00, 0x69, 0x00, 0x6E, 0x00,
|
|
0x64, 0x00, 0x6F, 0x00, 0x77, 0x00, 0x73, 0x00, 0x20, 0x00, 0x53, 0x00, 0x65, 0x00, 0x72, 0x00,
|
|
0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x20, 0x00, 0x32, 0x00, 0x30, 0x00, 0x30, 0x00, 0x33, 0x00,
|
|
0x20, 0x00, 0x33, 0x00, 0x37, 0x00, 0x39, 0x00, 0x30, 0x00, 0x20, 0x00, 0x53, 0x00, 0x65, 0x00,
|
|
0x72, 0x00, 0x76, 0x00, 0x69, 0x00, 0x63, 0x00, 0x65, 0x00, 0x20, 0x00, 0x50, 0x00, 0x61, 0x00,
|
|
0x63, 0x00, 0x6B, 0x00, 0x20, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x00, 0x69, 0x00,
|
|
0x6E, 0x00, 0x64, 0x00, 0x6F, 0x00, 0x77, 0x00, 0x73, 0x00, 0x20, 0x00, 0x53, 0x00, 0x65, 0x00,
|
|
0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x20, 0x00, 0x32, 0x00, 0x30, 0x00, 0x30, 0x00,
|
|
0x33, 0x00, 0x20, 0x00, 0x35, 0x00, 0x2E, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
}
|
|
|
|
smbv2NegotiatePacket = []byte{
|
|
0x00, 0x00, 0x00, 0x45, 0xFF, 0x53, 0x4D, 0x42, 0x72, 0x00,
|
|
0x00, 0x00, 0x00, 0x18, 0x01, 0x48, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,
|
|
0xAC, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x02,
|
|
0x4E, 0x54, 0x20, 0x4C, 0x4D, 0x20, 0x30, 0x2E, 0x31, 0x32,
|
|
0x00, 0x02, 0x53, 0x4D, 0x42, 0x20, 0x32, 0x2E, 0x30, 0x30,
|
|
0x32, 0x00, 0x02, 0x53, 0x4D, 0x42, 0x20, 0x32, 0x2E, 0x3F,
|
|
0x3F, 0x3F, 0x00,
|
|
}
|
|
|
|
smbv2SessionSetupPacket = []byte{
|
|
0x00, 0x00, 0x00, 0x68, 0xFE, 0x53, 0x4D, 0x42, 0x40, 0x00,
|
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00,
|
|
0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x02,
|
|
}
|
|
|
|
// SMB Ghost (CVE-2020-0796) 检测数据包
|
|
smbGhostPacket = "\x00" +
|
|
"\x00\x00\xc0" +
|
|
"\xfeSMB@\x00" +
|
|
"\x00\x00" +
|
|
"\x00\x00" +
|
|
"\x00\x00" +
|
|
"\x00\x00" +
|
|
"\x1f\x00" +
|
|
"\x00\x00\x00\x00" +
|
|
"\x00\x00\x00\x00" +
|
|
"\x00\x00\x00\x00" +
|
|
"\x00\x00\x00\x00" +
|
|
"\x00\x00\x00\x00" +
|
|
"\x00\x00\x00\x00" +
|
|
"\x00\x00\x00\x00" +
|
|
"\x00\x00\x00\x00" +
|
|
"\x00\x00\x00\x00" +
|
|
"\x00\x00\x00\x00" +
|
|
"\x00\x00\x00\x00" +
|
|
"\x00\x00\x00\x00" +
|
|
"$\x00" +
|
|
"\x08\x00" +
|
|
"\x01\x00" +
|
|
"\x00\x00" +
|
|
"\x7f\x00\x00\x00" +
|
|
"\x00\x00\x00\x00" +
|
|
"\x00\x00\x00\x00" +
|
|
"\x00\x00\x00\x00" +
|
|
"\x00\x00\x00\x00" +
|
|
"x\x00" +
|
|
"\x00\x00" +
|
|
"\x02\x00" +
|
|
"\x00\x00" +
|
|
"\x02\x02" +
|
|
"\x10\x02" +
|
|
"\x22\x02" +
|
|
"$\x02" +
|
|
"\x00\x03" +
|
|
"\x02\x03" +
|
|
"\x10\x03" +
|
|
"\x11\x03" +
|
|
"\x00\x00\x00\x00" +
|
|
"\x01\x00" +
|
|
"&\x00" +
|
|
"\x00\x00\x00\x00" +
|
|
"\x01\x00" +
|
|
"\x20\x00" +
|
|
"\x01\x00" +
|
|
"\x00\x00\x00\x00" +
|
|
"\x00\x00\x00\x00" +
|
|
"\x00\x00\x00\x00" +
|
|
"\x00\x00\x00\x00" +
|
|
"\x00\x00\x00\x00" +
|
|
"\x00\x00\x00\x00" +
|
|
"\x00\x00\x00\x00" +
|
|
"\x00\x00\x00\x00" +
|
|
"\x00\x00" +
|
|
"\x03\x00" +
|
|
"\x0e\x00" +
|
|
"\x00\x00\x00\x00" +
|
|
"\x01\x00" +
|
|
"\x00\x00" +
|
|
"\x01\x00\x00\x00" +
|
|
"\x01\x00" +
|
|
"\x00\x00" +
|
|
"\x00\x00\x00\x00"
|
|
)
|
|
|
|
// probeTarget 探测目标SMB信息(协议版本、系统信息)
|
|
func probeTarget(ctx context.Context, host string, port int, timeout time.Duration, session *common.ScanSession) (*SMBTarget, error) {
|
|
target := fmt.Sprintf("%s:%d", host, port)
|
|
|
|
conn, err := session.DialTCP(ctx, "tcp", target, timeout)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("连接失败: %w", err)
|
|
}
|
|
defer func() { _ = conn.Close() }()
|
|
|
|
_ = conn.SetDeadline(time.Now().Add(timeout))
|
|
|
|
// 首先尝试SMBv1协商
|
|
_, err = conn.Write(smbv1NegotiatePacket)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("发送SMBv1协商包失败: %w", err)
|
|
}
|
|
|
|
// 读取SMBv1协商响应
|
|
r1, err := readSMBMessage(conn)
|
|
if err != nil {
|
|
common.LogDebug(fmt.Sprintf("读取SMBv1协商响应失败: %v", err))
|
|
}
|
|
|
|
// 检查是否支持SMBv1
|
|
if len(r1) > 0 {
|
|
return probeSMBv1(conn, target, timeout)
|
|
}
|
|
|
|
// SMBv2路径
|
|
return probeSMBv2(ctx, target, timeout, session)
|
|
}
|
|
|
|
// probeSMBv1 处理SMBv1协议信息收集
|
|
func probeSMBv1(conn net.Conn, target string, timeout time.Duration) (*SMBTarget, error) {
|
|
// 发送Session Setup请求
|
|
_, err := conn.Write(smbv1SessionSetupPacket)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("发送SMBv1 Session Setup失败: %w", err)
|
|
}
|
|
|
|
ret, err := readSMBMessage(conn)
|
|
if err != nil || len(ret) < 47 {
|
|
return nil, fmt.Errorf("读取SMBv1 Session Setup响应失败: %w", err)
|
|
}
|
|
|
|
info := &SMBTarget{
|
|
Protocol: SMBProtocol1,
|
|
}
|
|
|
|
// 解析blob信息
|
|
blobLength := int(bytesToUint16(ret[43:45]))
|
|
blobCount := int(bytesToUint16(ret[45:47]))
|
|
|
|
gssNative := ret[47:]
|
|
gssLen := len(gssNative)
|
|
|
|
// 校验远端返回的偏移量
|
|
if blobLength > gssLen || blobCount > gssLen || blobLength > blobCount {
|
|
return info, nil
|
|
}
|
|
|
|
offNTLM := bytes.Index(gssNative, []byte("NTLMSSP"))
|
|
if offNTLM == -1 {
|
|
return info, nil
|
|
}
|
|
|
|
// 提取native OS和LM信息
|
|
native := gssNative[blobLength:blobCount]
|
|
ss := strings.Split(string(native), "\x00\x00")
|
|
|
|
if len(ss) > 0 {
|
|
info.NativeOS = trimSMBString(ss[0])
|
|
}
|
|
if len(ss) > 1 {
|
|
info.NativeLM = trimSMBString(ss[1])
|
|
}
|
|
|
|
// 解析NTLM信息
|
|
if offNTLM <= blobLength {
|
|
bs := gssNative[offNTLM:blobLength]
|
|
parseNTLMChallenge(bs, info)
|
|
}
|
|
|
|
return info, nil
|
|
}
|
|
|
|
// probeSMBv2 处理SMBv2协议信息收集
|
|
func probeSMBv2(ctx context.Context, target string, timeout time.Duration, session *common.ScanSession) (*SMBTarget, error) {
|
|
conn2, err := session.DialTCP(ctx, "tcp", target, timeout)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("SMBv2连接失败: %w", err)
|
|
}
|
|
defer func() { _ = conn2.Close() }()
|
|
|
|
_ = conn2.SetDeadline(time.Now().Add(timeout))
|
|
|
|
// 发送SMBv2协商包
|
|
_, err = conn2.Write(smbv2NegotiatePacket)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("发送SMBv2协商包失败: %w", err)
|
|
}
|
|
|
|
r2, err := readSMBMessage(conn2)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("读取SMBv2协商响应失败: %w", err)
|
|
}
|
|
|
|
// 构建NTLM数据包
|
|
var ntlmData []byte
|
|
if len(r2) > 70 && hex.EncodeToString(r2[70:71]) == "03" {
|
|
flags := []byte{0x15, 0x82, 0x08, 0xa0}
|
|
ntlmData = buildNTLMSSPData(flags)
|
|
} else {
|
|
flags := []byte{0x05, 0x80, 0x08, 0xa0}
|
|
ntlmData = buildNTLMSSPData(flags)
|
|
}
|
|
|
|
// 发送Session Setup
|
|
_, err = conn2.Write(smbv2SessionSetupPacket)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("发送SMBv2 Session Setup失败: %w", err)
|
|
}
|
|
|
|
_, err = readSMBMessage(conn2)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("读取SMBv2 Session Setup响应失败: %w", err)
|
|
}
|
|
|
|
// 发送NTLM协商包
|
|
_, err = conn2.Write(ntlmData)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("发送SMBv2 NTLM包失败: %w", err)
|
|
}
|
|
|
|
ret, err := readSMBMessage(conn2)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("读取SMBv2 NTLM响应失败: %w", err)
|
|
}
|
|
|
|
ntlmOff := bytes.Index(ret, []byte("NTLMSSP"))
|
|
if ntlmOff == -1 {
|
|
return &SMBTarget{Protocol: SMBProtocol2}, nil
|
|
}
|
|
|
|
info := &SMBTarget{
|
|
Protocol: SMBProtocol2,
|
|
}
|
|
|
|
parseNTLMChallenge(ret[ntlmOff:], info)
|
|
return info, nil
|
|
}
|
|
|
|
// checkSMBGhost 检测CVE-2020-0796漏洞
|
|
func checkSMBGhost(ctx context.Context, host string, timeout time.Duration, session *common.ScanSession) bool {
|
|
addr := fmt.Sprintf("%s:445", host)
|
|
|
|
conn, err := session.DialTCP(ctx, "tcp", addr, timeout)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
defer func() { _ = conn.Close() }()
|
|
|
|
if err = conn.SetDeadline(time.Now().Add(timeout)); err != nil {
|
|
return false
|
|
}
|
|
|
|
if _, err = conn.Write([]byte(smbGhostPacket)); err != nil {
|
|
return false
|
|
}
|
|
|
|
buff := make([]byte, 1024)
|
|
n, err := conn.Read(buff)
|
|
if err != nil || n == 0 {
|
|
return false
|
|
}
|
|
|
|
// 检测CVE-2020-0796特征
|
|
if bytes.Contains(buff[:n], []byte("Public")) &&
|
|
len(buff[:n]) >= 76 &&
|
|
bytes.Equal(buff[72:74], []byte{0x11, 0x03}) &&
|
|
bytes.Equal(buff[74:76], []byte{0x02, 0x00}) {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// SMBAuthenticator 统一认证接口
|
|
type SMBAuthenticator interface {
|
|
Authenticate(ctx context.Context, host string, port int, cred Credential, domain string, timeout time.Duration, session *common.ScanSession) (*AuthResult, error)
|
|
ListShares(ctx context.Context, host string, port int, cred Credential, domain string, timeout time.Duration, session *common.ScanSession) ([]string, error)
|
|
}
|
|
|
|
// SMB1Authenticator SMB1认证器
|
|
type SMB1Authenticator struct{}
|
|
|
|
// Authenticate 执行SMB1认证
|
|
func (a *SMB1Authenticator) Authenticate(ctx context.Context, host string, port int, cred Credential, domain string, timeout time.Duration, session *common.ScanSession) (*AuthResult, error) {
|
|
options := smb.Options{
|
|
Host: host,
|
|
Port: port,
|
|
User: cred.Username,
|
|
Password: cred.Password,
|
|
Domain: domain,
|
|
Workstation: "",
|
|
}
|
|
|
|
timeoutCtx, cancel := context.WithTimeout(ctx, timeout)
|
|
defer cancel()
|
|
|
|
resultChan := make(chan *AuthResult, 1)
|
|
|
|
go func() {
|
|
session, err := smb.NewSession(options, false)
|
|
if err != nil {
|
|
resultChan <- &AuthResult{
|
|
Success: false,
|
|
ErrorType: classifySMBError(err),
|
|
Error: err,
|
|
}
|
|
return
|
|
}
|
|
|
|
if session.IsAuthenticated {
|
|
resultChan <- &AuthResult{
|
|
Success: true,
|
|
Conn: &smb1SessionWrapper{session},
|
|
ErrorType: ErrorTypeUnknown,
|
|
Error: nil,
|
|
}
|
|
} else {
|
|
session.Close()
|
|
resultChan <- &AuthResult{
|
|
Success: false,
|
|
ErrorType: ErrorTypeAuth,
|
|
Error: fmt.Errorf("认证失败:用户名或密码错误"),
|
|
}
|
|
}
|
|
}()
|
|
|
|
select {
|
|
case result := <-resultChan:
|
|
return result, nil
|
|
case <-timeoutCtx.Done():
|
|
go func() {
|
|
result := <-resultChan
|
|
if result != nil && result.Conn != nil {
|
|
_ = result.Conn.Close()
|
|
}
|
|
}()
|
|
return &AuthResult{
|
|
Success: false,
|
|
ErrorType: ErrorTypeNetwork,
|
|
Error: fmt.Errorf("连接超时"),
|
|
}, nil
|
|
case <-ctx.Done():
|
|
go func() {
|
|
result := <-resultChan
|
|
if result != nil && result.Conn != nil {
|
|
_ = result.Conn.Close()
|
|
}
|
|
}()
|
|
return &AuthResult{
|
|
Success: false,
|
|
ErrorType: ErrorTypeNetwork,
|
|
Error: ctx.Err(),
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
// ListShares 列举SMB共享(SMB1使用SMB2库列举)
|
|
func (a *SMB1Authenticator) ListShares(ctx context.Context, host string, port int, cred Credential, domain string, timeout time.Duration, session *common.ScanSession) ([]string, error) {
|
|
return listSMBSharesInternal(ctx, host, port, cred, domain, timeout, session)
|
|
}
|
|
|
|
// SMB2Authenticator SMB2认证器
|
|
type SMB2Authenticator struct{}
|
|
|
|
// Authenticate 执行SMB2认证
|
|
func (a *SMB2Authenticator) Authenticate(ctx context.Context, host string, port int, cred Credential, domain string, timeout time.Duration, session *common.ScanSession) (*AuthResult, error) {
|
|
timeoutCtx, cancel := context.WithTimeout(ctx, timeout)
|
|
defer cancel()
|
|
|
|
conn, err := session.DialTCP(ctx, "tcp", fmt.Sprintf("%s:%d", host, port), timeout)
|
|
if err != nil {
|
|
return &AuthResult{
|
|
Success: false,
|
|
ErrorType: classifySMBError(err),
|
|
Error: err,
|
|
}, nil
|
|
}
|
|
|
|
d := &smb2.Dialer{
|
|
Initiator: &smb2.NTLMInitiator{
|
|
User: cred.Username,
|
|
Password: cred.Password,
|
|
Domain: domain,
|
|
},
|
|
}
|
|
|
|
s, err := d.DialContext(timeoutCtx, conn)
|
|
if err != nil {
|
|
_ = conn.Close()
|
|
return &AuthResult{
|
|
Success: false,
|
|
ErrorType: classifySMBError(err),
|
|
Error: fmt.Errorf("SMB2认证失败: %w", err),
|
|
}, nil
|
|
}
|
|
|
|
// 尝试列举共享来验证认证成功
|
|
_, _ = s.ListSharenames()
|
|
|
|
return &AuthResult{
|
|
Success: true,
|
|
Conn: &smb2SessionWrapper{s, conn},
|
|
ErrorType: ErrorTypeUnknown,
|
|
Error: nil,
|
|
}, nil
|
|
}
|
|
|
|
// ListShares 列举SMB2共享
|
|
func (a *SMB2Authenticator) ListShares(ctx context.Context, host string, port int, cred Credential, domain string, timeout time.Duration, session *common.ScanSession) ([]string, error) {
|
|
return listSMBSharesInternal(ctx, host, port, cred, domain, timeout, session)
|
|
}
|
|
|
|
// listSMBSharesInternal 内部共享列举实现
|
|
func listSMBSharesInternal(ctx context.Context, host string, port int, cred Credential, domain string, timeout time.Duration, session *common.ScanSession) ([]string, error) {
|
|
target := net.JoinHostPort(host, strconv.Itoa(port))
|
|
|
|
conn, err := session.DialTCP(ctx, "tcp", target, timeout*2)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func() { _ = conn.Close() }()
|
|
|
|
d := &smb2.Dialer{
|
|
Initiator: &smb2.NTLMInitiator{
|
|
User: cred.Username,
|
|
Password: cred.Password,
|
|
Domain: domain,
|
|
},
|
|
}
|
|
|
|
s, err := d.Dial(conn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func() { _ = s.Logoff() }()
|
|
|
|
shares, err := s.ListSharenames()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var shareInfo []string
|
|
systemShares := map[string]bool{
|
|
"ADMIN$": true,
|
|
"C$": true,
|
|
"IPC$": true,
|
|
}
|
|
|
|
for _, shareName := range shares {
|
|
if systemShares[shareName] {
|
|
continue
|
|
}
|
|
|
|
fs, err := s.Mount(shareName)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
fileCount := 0
|
|
maxFiles := 10
|
|
_ = iofs.WalkDir(fs.DirFS("."), ".", func(path string, d iofs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
if path != "." && fileCount < maxFiles {
|
|
shareInfo = append(shareInfo, fmt.Sprintf(" [->] [%s] %s", shareName, path))
|
|
fileCount++
|
|
}
|
|
|
|
if fileCount >= maxFiles {
|
|
return iofs.SkipDir
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
_ = fs.Umount()
|
|
}
|
|
|
|
return shareInfo, nil
|
|
}
|
|
|
|
// smb1SessionWrapper 包装SMB1会话以实现io.Closer
|
|
type smb1SessionWrapper struct {
|
|
session *smb.Session
|
|
}
|
|
|
|
func (w *smb1SessionWrapper) Close() error {
|
|
w.session.Close()
|
|
return nil
|
|
}
|
|
|
|
// smb2SessionWrapper 包装SMB2会话以实现io.Closer
|
|
type smb2SessionWrapper struct {
|
|
session *smb2.Session
|
|
conn io.Closer
|
|
}
|
|
|
|
func (w *smb2SessionWrapper) Close() error {
|
|
_ = w.session.Logoff()
|
|
return w.conn.Close()
|
|
}
|
|
|
|
// classifySMBError 统一SMB错误分类
|
|
func classifySMBError(err error) ErrorType {
|
|
if err == nil {
|
|
return ErrorTypeUnknown
|
|
}
|
|
|
|
smbAuthErrors := []string{
|
|
// 通用认证错误
|
|
"invalid username",
|
|
"invalid password",
|
|
"authentication failed",
|
|
"logon failed",
|
|
"logon failure",
|
|
"access denied",
|
|
"permission denied",
|
|
"unauthorized",
|
|
"login failed",
|
|
"bad username",
|
|
"bad password",
|
|
"wrong password",
|
|
"incorrect password",
|
|
"invalid credentials",
|
|
"bad credentials",
|
|
"authentication error",
|
|
"auth failed",
|
|
"login denied",
|
|
"credential",
|
|
"user not found",
|
|
"invalid account",
|
|
"account locked",
|
|
"account disabled",
|
|
"password expired",
|
|
// SMB特定错误
|
|
"smb: authentication failed",
|
|
"smb: invalid user",
|
|
"smb: invalid password",
|
|
"smb: access denied",
|
|
"smb: logon failure",
|
|
"smb: bad password",
|
|
"smb: user unknown",
|
|
"smb: wrong password",
|
|
"smb: login failed",
|
|
"smb: unauthorized",
|
|
"smb2认证失败",
|
|
"ntlm authentication failed",
|
|
"ntlm auth failed",
|
|
// NT Status codes
|
|
"nt_status_logon_failure",
|
|
"nt_status_wrong_password",
|
|
"nt_status_no_such_user",
|
|
"nt_status_access_denied",
|
|
"nt_status_account_disabled",
|
|
"nt_status_account_locked_out",
|
|
"nt_status_password_expired",
|
|
"status_logon_failure",
|
|
"status_wrong_password",
|
|
"status_access_denied",
|
|
"status_invalid_parameter",
|
|
"status_no_such_user",
|
|
"status_account_locked_out",
|
|
"status_password_expired",
|
|
"status_account_disabled",
|
|
// 十六进制状态码
|
|
"0xc000006d",
|
|
"0xc0000022",
|
|
"0xc000006a",
|
|
"0xc0000064",
|
|
"0xc0000234",
|
|
}
|
|
|
|
return ClassifyError(err, smbAuthErrors, CommonNetworkErrors)
|
|
}
|
|
|
|
// readSMBMessage 从连接读取NetBIOS消息
|
|
func readSMBMessage(conn net.Conn) ([]byte, error) {
|
|
headerBuf := make([]byte, 4)
|
|
n, err := conn.Read(headerBuf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if n != 4 {
|
|
return nil, fmt.Errorf("NetBIOS头部长度不足: %d", n)
|
|
}
|
|
|
|
messageLength := int(headerBuf[0])<<24 | int(headerBuf[1])<<16 | int(headerBuf[2])<<8 | int(headerBuf[3])
|
|
|
|
if messageLength > 1024*1024 {
|
|
return nil, fmt.Errorf("消息长度过大: %d", messageLength)
|
|
}
|
|
|
|
if messageLength == 0 {
|
|
return headerBuf, nil
|
|
}
|
|
|
|
messageBuf := make([]byte, messageLength)
|
|
totalRead := 0
|
|
for totalRead < messageLength {
|
|
n, err := conn.Read(messageBuf[totalRead:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
totalRead += n
|
|
}
|
|
|
|
result := make([]byte, 0, 4+messageLength)
|
|
result = append(result, headerBuf...)
|
|
result = append(result, messageBuf...)
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// parseNTLMChallenge 解析NTLM Challenge消息
|
|
func parseNTLMChallenge(data []byte, info *SMBTarget) {
|
|
if len(data) < 32 {
|
|
return
|
|
}
|
|
|
|
if !bytes.Equal(data[0:8], []byte("NTLMSSP\x00")) {
|
|
return
|
|
}
|
|
|
|
if len(data) < 12 {
|
|
return
|
|
}
|
|
messageType := bytesToUint32(data[8:12])
|
|
if messageType != 2 {
|
|
return
|
|
}
|
|
|
|
// 解析Target Name
|
|
if len(data) >= 20 {
|
|
targetLength := bytesToUint16(data[12:14])
|
|
targetOffset := bytesToUint32(data[16:20])
|
|
|
|
if targetLength > 0 && int(targetOffset) < len(data) && int(targetOffset+uint32(targetLength)) <= len(data) {
|
|
targetName := parseUnicodeString(data[targetOffset : targetOffset+uint32(targetLength)])
|
|
if targetName != "" {
|
|
info.DomainName = targetName
|
|
}
|
|
}
|
|
}
|
|
|
|
// 解析Flags
|
|
if len(data) >= 24 {
|
|
flags := bytesToUint32(data[20:24])
|
|
info.NTLMFlags = parseNTLMFlags(flags)
|
|
}
|
|
|
|
// 解析Target Info (AV_PAIR结构)
|
|
if len(data) >= 52 {
|
|
targetInfoLength := bytesToUint16(data[40:42])
|
|
targetInfoOffset := bytesToUint32(data[44:48])
|
|
|
|
if targetInfoLength > 0 && int(targetInfoOffset) < len(data) &&
|
|
int(targetInfoOffset+uint32(targetInfoLength)) <= len(data) {
|
|
targetInfoData := data[targetInfoOffset : targetInfoOffset+uint32(targetInfoLength)]
|
|
parseTargetInfo(targetInfoData, info)
|
|
}
|
|
}
|
|
|
|
// 解析OS版本信息
|
|
if len(data) >= 56 {
|
|
flags := bytesToUint32(data[20:24])
|
|
if flags&0x02000000 != 0 && len(data) >= 56 {
|
|
parseOSVersion(data[48:56], info)
|
|
}
|
|
}
|
|
}
|
|
|
|
// parseTargetInfo 解析Target Information
|
|
func parseTargetInfo(data []byte, info *SMBTarget) {
|
|
offset := 0
|
|
|
|
for offset+4 <= len(data) {
|
|
avId := bytesToUint16(data[offset : offset+2])
|
|
avLen := bytesToUint16(data[offset+2 : offset+4])
|
|
|
|
if avId == 0x0000 {
|
|
break
|
|
}
|
|
|
|
if offset+4+int(avLen) > len(data) {
|
|
break
|
|
}
|
|
|
|
value := data[offset+4 : offset+4+int(avLen)]
|
|
|
|
switch avId {
|
|
case 0x0001: // MsvAvNbComputerName
|
|
computerName := parseUnicodeString(value)
|
|
if computerName != "" {
|
|
info.ComputerName = computerName
|
|
}
|
|
case 0x0002: // MsvAvNbDomainName
|
|
if info.DomainName == "" {
|
|
domainName := parseUnicodeString(value)
|
|
if domainName != "" {
|
|
info.DomainName = domainName
|
|
}
|
|
}
|
|
case 0x0003: // MsvAvDnsComputerName
|
|
if info.ComputerName == "" {
|
|
dnsComputerName := parseUnicodeString(value)
|
|
if dnsComputerName != "" {
|
|
info.ComputerName = dnsComputerName
|
|
}
|
|
}
|
|
}
|
|
|
|
offset += 4 + int(avLen)
|
|
}
|
|
}
|
|
|
|
// parseOSVersion 解析操作系统版本
|
|
func parseOSVersion(data []byte, info *SMBTarget) {
|
|
if len(data) < 8 {
|
|
return
|
|
}
|
|
|
|
majorVersion := data[0]
|
|
minorVersion := data[1]
|
|
buildNumber := bytesToUint16(data[2:4])
|
|
|
|
var osName string
|
|
switch {
|
|
case majorVersion == 10 && minorVersion == 0:
|
|
if buildNumber >= 22000 {
|
|
osName = "Windows 11"
|
|
} else {
|
|
osName = "Windows 10"
|
|
}
|
|
case majorVersion == 6 && minorVersion == 3:
|
|
osName = "Windows 8.1/Server 2012 R2"
|
|
case majorVersion == 6 && minorVersion == 2:
|
|
osName = "Windows 8/Server 2012"
|
|
case majorVersion == 6 && minorVersion == 1:
|
|
osName = "Windows 7/Server 2008 R2"
|
|
case majorVersion == 6 && minorVersion == 0:
|
|
osName = "Windows Vista/Server 2008"
|
|
case majorVersion == 5 && minorVersion == 2:
|
|
osName = "Windows XP x64/Server 2003"
|
|
case majorVersion == 5 && minorVersion == 1:
|
|
osName = "Windows XP"
|
|
case majorVersion == 5 && minorVersion == 0:
|
|
osName = "Windows 2000"
|
|
default:
|
|
osName = fmt.Sprintf("Windows %d.%d", majorVersion, minorVersion)
|
|
}
|
|
|
|
info.OSVersion = fmt.Sprintf("%s (Build %d)", osName, buildNumber)
|
|
}
|
|
|
|
// 辅助函数
|
|
func bytesToUint16(b []byte) uint16 {
|
|
if len(b) < 2 {
|
|
return 0
|
|
}
|
|
return uint16(b[0]) | uint16(b[1])<<8
|
|
}
|
|
|
|
func bytesToUint32(b []byte) uint32 {
|
|
if len(b) < 4 {
|
|
return 0
|
|
}
|
|
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
|
|
}
|
|
|
|
func trimSMBString(s string) string {
|
|
return strings.Trim(strings.TrimSpace(s), "\x00")
|
|
}
|
|
|
|
func parseUnicodeString(data []byte) string {
|
|
if len(data)%2 != 0 {
|
|
return ""
|
|
}
|
|
|
|
var runes []rune
|
|
for i := 0; i < len(data); i += 2 {
|
|
if i+1 >= len(data) {
|
|
break
|
|
}
|
|
r := uint16(data[i]) | uint16(data[i+1])<<8
|
|
if r == 0 {
|
|
break
|
|
}
|
|
runes = append(runes, rune(r))
|
|
}
|
|
return string(runes)
|
|
}
|
|
|
|
func parseNTLMFlags(flags uint32) []string {
|
|
flagNames := map[uint32]string{
|
|
0x00000001: "NEGOTIATE_UNICODE",
|
|
0x00000002: "NEGOTIATE_OEM",
|
|
0x00000004: "REQUEST_TARGET",
|
|
0x00000010: "NEGOTIATE_SIGN",
|
|
0x00000020: "NEGOTIATE_SEAL",
|
|
0x00000200: "NEGOTIATE_NTLM",
|
|
0x00080000: "NEGOTIATE_EXTENDED_SESSIONSECURITY",
|
|
0x02000000: "NEGOTIATE_VERSION",
|
|
0x20000000: "NEGOTIATE_128",
|
|
0x80000000: "NEGOTIATE_56",
|
|
}
|
|
|
|
var activeFlags []string
|
|
for flag, name := range flagNames {
|
|
if flags&flag != 0 {
|
|
activeFlags = append(activeFlags, name)
|
|
}
|
|
}
|
|
|
|
return activeFlags
|
|
}
|
|
|
|
func buildNTLMSSPData(flags []byte) []byte {
|
|
return []byte{
|
|
0x00, 0x00, 0x00, 0x9A, 0xFE, 0x53, 0x4D, 0x42, 0x40, 0x00,
|
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x00,
|
|
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x58, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x60, 0x40, 0x06, 0x06, 0x2B, 0x06, 0x01, 0x05,
|
|
0x05, 0x02, 0xA0, 0x36, 0x30, 0x34, 0xA0, 0x0E, 0x30, 0x0C,
|
|
0x06, 0x0A, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02,
|
|
0x02, 0x0A, 0xA2, 0x22, 0x04, 0x20, 0x4E, 0x54, 0x4C, 0x4D,
|
|
0x53, 0x53, 0x50, 0x00, 0x01, 0x00, 0x00, 0x00,
|
|
flags[0], flags[1], flags[2], flags[3],
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
}
|
|
}
|