diff --git a/plugins/services/README.md b/plugins/services/README.md index a79df5e..2b369b9 100644 --- a/plugins/services/README.md +++ b/plugins/services/README.md @@ -28,6 +28,10 @@ - `ldap.go` - LDAP目录服务扫描 - `rsync.go` - Rsync文件同步服务扫描 +### Windows服务 +- `findnet.go` - Windows网络发现插件 (RPC端点映射) +- `smbinfo.go` - SMB协议信息收集插件 + ### 其他服务 - `vnc.go` - VNC远程桌面服务扫描 - `cassandra.go` - Apache Cassandra数据库扫描 diff --git a/plugins/services/findnet.go b/plugins/services/findnet.go new file mode 100644 index 0000000..aae7f64 --- /dev/null +++ b/plugins/services/findnet.go @@ -0,0 +1,378 @@ +package services + +import ( + "bytes" + "context" + "encoding/hex" + "fmt" + "net" + "regexp" + "strconv" + "strings" + "time" + "unicode" + + "github.com/shadow1ng/fscan/common" +) + +// FindNetPlugin Windows网络发现插件 - 通过RPC端点映射服务收集网络信息 +type FindNetPlugin struct { + name string + ports []int +} + +// NewFindNetPlugin 创建FindNet插件 +func NewFindNetPlugin() *FindNetPlugin { + return &FindNetPlugin{ + name: "findnet", + ports: []int{135}, // RPC端点映射器端口 + } +} + +// GetName 实现Plugin接口 +func (p *FindNetPlugin) GetName() string { + return p.name +} + +// GetPorts 实现Plugin接口 +func (p *FindNetPlugin) GetPorts() []int { + return p.ports +} + +// Scan 执行FindNet扫描 - Windows网络信息收集 +func (p *FindNetPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { + target := fmt.Sprintf("%s:%s", info.Host, info.Ports) + + // 检查是否为RPC端口 + if info.Ports != "135" { + return &ScanResult{ + Success: false, + Service: "findnet", + Error: fmt.Errorf("FindNet插件仅支持RPC端口135"), + } + } + + // 建立连接 + conn, err := net.DialTimeout("tcp", target, time.Duration(common.Timeout)*time.Second) + if err != nil { + return &ScanResult{ + Success: false, + Service: "findnet", + Error: fmt.Errorf("连接RPC端口失败: %v", err), + } + } + defer conn.Close() + + // 设置超时 + conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)) + + // 执行RPC网络发现 + networkInfo, err := p.performNetworkDiscovery(conn) + if err != nil { + return &ScanResult{ + Success: false, + Service: "findnet", + Error: err, + } + } + + // 记录发现的网络信息 + if networkInfo.Valid { + msg := fmt.Sprintf("NetInfo %s", target) + if networkInfo.Hostname != "" { + msg += fmt.Sprintf(" [%s]", networkInfo.Hostname) + } + if len(networkInfo.IPv4Addrs) > 0 || len(networkInfo.IPv6Addrs) > 0 { + msg += fmt.Sprintf(" %d interfaces", len(networkInfo.IPv4Addrs)+len(networkInfo.IPv6Addrs)) + } + common.LogSuccess(msg) + } + + return &ScanResult{ + Success: networkInfo.Valid, + Service: "findnet", + Banner: networkInfo.Summary(), + } +} + +// Exploit 执行FindNet利用操作 - 详细网络信息收集 +func (p *FindNetPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds Credential) *ExploitResult { + target := fmt.Sprintf("%s:%s", info.Host, info.Ports) + common.LogSuccess(fmt.Sprintf("FindNet利用开始: %s", target)) + + var output strings.Builder + output.WriteString(fmt.Sprintf("=== FindNet网络发现结果 - %s ===\n", target)) + + // 建立连接进行详细扫描 + conn, err := net.DialTimeout("tcp", target, time.Duration(common.Timeout)*time.Second) + if err != nil { + output.WriteString(fmt.Sprintf("\n[连接失败] %v\n", err)) + return &ExploitResult{ + Success: false, + Output: output.String(), + Error: err, + } + } + defer conn.Close() + + conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)) + + // 执行网络发现 + networkInfo, err := p.performNetworkDiscovery(conn) + if err != nil { + output.WriteString(fmt.Sprintf("\n[网络发现失败] %v\n", err)) + return &ExploitResult{ + Success: false, + Output: output.String(), + Error: err, + } + } + + if !networkInfo.Valid { + output.WriteString("\n[网络信息] 未发现有效的网络信息\n") + return &ExploitResult{ + Success: false, + Output: output.String(), + } + } + + // 输出详细网络信息 + if networkInfo.Hostname != "" { + output.WriteString(fmt.Sprintf("\n[主机名] %s\n", networkInfo.Hostname)) + } + + if len(networkInfo.IPv4Addrs) > 0 { + output.WriteString(fmt.Sprintf("\n[IPv4地址] (共%d个)\n", len(networkInfo.IPv4Addrs))) + for _, addr := range networkInfo.IPv4Addrs { + output.WriteString(fmt.Sprintf(" %s\n", addr)) + } + } + + if len(networkInfo.IPv6Addrs) > 0 { + output.WriteString(fmt.Sprintf("\n[IPv6地址] (共%d个)\n", len(networkInfo.IPv6Addrs))) + for _, addr := range networkInfo.IPv6Addrs { + output.WriteString(fmt.Sprintf(" %s\n", addr)) + } + } + + common.LogSuccess(fmt.Sprintf("FindNet利用完成: %s", target)) + + return &ExploitResult{ + Success: true, + Output: output.String(), + } +} + +// NetworkInfo 网络信息结构 +type NetworkInfo struct { + Valid bool + Hostname string + IPv4Addrs []string + IPv6Addrs []string +} + +// Summary 返回网络信息摘要 +func (ni *NetworkInfo) Summary() string { + if !ni.Valid { + return "网络发现失败" + } + + var parts []string + if ni.Hostname != "" { + parts = append(parts, fmt.Sprintf("主机名: %s", ni.Hostname)) + } + if len(ni.IPv4Addrs) > 0 { + parts = append(parts, fmt.Sprintf("IPv4: %d个", len(ni.IPv4Addrs))) + } + if len(ni.IPv6Addrs) > 0 { + parts = append(parts, fmt.Sprintf("IPv6: %d个", len(ni.IPv6Addrs))) + } + + if len(parts) == 0 { + return "网络信息收集完成" + } + return strings.Join(parts, ", ") +} + +// RPC数据包定义 +var ( + rpcBuffer1, _ = hex.DecodeString("05000b03100000004800000001000000b810b810000000000100000000000100c4fefc9960521b10bbcb00aa0021347a00000000045d888aeb1cc9119fe808002b10486002000000") + rpcBuffer2, _ = hex.DecodeString("050000031000000018000000010000000000000000000500") + rpcBuffer3, _ = hex.DecodeString("0900ffff0000") +) + +// performNetworkDiscovery 执行RPC网络发现 +func (p *FindNetPlugin) performNetworkDiscovery(conn net.Conn) (*NetworkInfo, error) { + // 发送第一个RPC请求 + if _, err := conn.Write(rpcBuffer1); err != nil { + return nil, fmt.Errorf("发送RPC请求1失败: %v", err) + } + + // 读取响应 + reply := make([]byte, 4096) + if _, err := conn.Read(reply); err != nil { + return nil, fmt.Errorf("读取RPC响应1失败: %v", err) + } + + // 发送第二个RPC请求 + if _, err := conn.Write(rpcBuffer2); err != nil { + return nil, fmt.Errorf("发送RPC请求2失败: %v", err) + } + + // 读取网络信息响应 + n, err := conn.Read(reply) + if err != nil || n < 42 { + return nil, fmt.Errorf("读取RPC响应2失败: %v", err) + } + + // 解析响应数据 + responseData := reply[42:] + + // 查找响应结束标记 + for i := 0; i < len(responseData)-5; i++ { + if bytes.Equal(responseData[i:i+6], rpcBuffer3) { + responseData = responseData[:i-4] + break + } + } + + // 解析网络信息 + return p.parseNetworkInfo(responseData), nil +} + +// parseNetworkInfo 解析RPC响应中的网络信息 +func (p *FindNetPlugin) parseNetworkInfo(data []byte) *NetworkInfo { + info := &NetworkInfo{ + Valid: false, + IPv4Addrs: []string{}, + IPv6Addrs: []string{}, + } + + encodedStr := hex.EncodeToString(data) + + // 解析主机名 + var hostName string + for i := 0; i < len(encodedStr)-4; i += 4 { + if encodedStr[i:i+4] == "0000" { + break + } + hostName += encodedStr[i : i+4] + } + + if hostName != "" { + name := p.hexUnicodeToString(hostName) + if p.isValidHostname(name) { + info.Hostname = name + info.Valid = true + } + } + + // 用于去重的地址集合 + seenAddresses := make(map[string]bool) + + // 解析网络信息 + netInfo := strings.Replace(encodedStr, "0700", "", -1) + segments := strings.Split(netInfo, "000000") + + // 处理每个网络地址段 + for _, segment := range segments { + if len(segment) == 0 { + continue + } + + if len(segment)%2 != 0 { + segment = segment + "0" + } + + addrBytes, err := hex.DecodeString(segment) + if err != nil { + continue + } + + addr := p.cleanAndValidateAddress(addrBytes) + if addr != "" && !seenAddresses[addr] { + seenAddresses[addr] = true + info.Valid = true + + if strings.Contains(addr, ":") { + info.IPv6Addrs = append(info.IPv6Addrs, addr) + } else if net.ParseIP(addr) != nil { + info.IPv4Addrs = append(info.IPv4Addrs, addr) + } + } + } + + return info +} + +// hexUnicodeToString 将十六进制Unicode字符串转换为普通字符串 +func (p *FindNetPlugin) hexUnicodeToString(src string) string { + if len(src)%4 != 0 { + src += strings.Repeat("0", 4-len(src)%4) + } + + var result strings.Builder + for i := 0; i < len(src); i += 4 { + if i+4 > len(src) { + break + } + + charCode, err := strconv.ParseInt(src[i+2:i+4]+src[i:i+2], 16, 32) + if err != nil { + continue + } + + if unicode.IsPrint(rune(charCode)) { + result.WriteRune(rune(charCode)) + } + } + + return result.String() +} + +// isValidHostname 检查是否为有效主机名 +func (p *FindNetPlugin) isValidHostname(name string) bool { + if len(name) == 0 || len(name) > 255 { + return false + } + + validHostname := regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]$`) + return validHostname.MatchString(name) +} + +// isValidNetworkAddress 检查是否为有效网络地址 +func (p *FindNetPlugin) isValidNetworkAddress(addr string) bool { + // 检查是否为IPv4或IPv6 + if ip := net.ParseIP(addr); ip != nil { + return true + } + + // 检查是否为有效主机名 + return p.isValidHostname(addr) +} + +// cleanAndValidateAddress 清理并验证地址 +func (p *FindNetPlugin) cleanAndValidateAddress(data []byte) string { + // 转换为字符串并清理不可打印字符 + addr := strings.Map(func(r rune) rune { + if unicode.IsPrint(r) { + return r + } + return -1 + }, string(data)) + + // 移除前后空白 + addr = strings.TrimSpace(addr) + + if p.isValidNetworkAddress(addr) { + return addr + } + return "" +} + +// init 自动注册插件 +func init() { + RegisterPlugin("findnet", func() Plugin { + return NewFindNetPlugin() + }) +} \ No newline at end of file diff --git a/plugins/services/smbinfo.go b/plugins/services/smbinfo.go new file mode 100644 index 0000000..1082be4 --- /dev/null +++ b/plugins/services/smbinfo.go @@ -0,0 +1,683 @@ +package services + +import ( + "bytes" + "context" + "encoding/hex" + "fmt" + "net" + "strings" + "time" + + "github.com/shadow1ng/fscan/common" +) + +// SMBInfoPlugin SMB协议信息收集插件 - 收集操作系统和NTLM信息 +type SMBInfoPlugin struct { + name string + ports []int +} + +// NewSMBInfoPlugin 创建SMBInfo插件 +func NewSMBInfoPlugin() *SMBInfoPlugin { + return &SMBInfoPlugin{ + name: "smbinfo", + ports: []int{139, 445}, // SMB端口 + } +} + +// GetName 实现Plugin接口 +func (p *SMBInfoPlugin) GetName() string { + return p.name +} + +// GetPorts 实现Plugin接口 +func (p *SMBInfoPlugin) GetPorts() []int { + return p.ports +} + +// Scan 执行SMBInfo扫描 - SMB信息收集 +func (p *SMBInfoPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { + target := fmt.Sprintf("%s:%s", info.Host, info.Ports) + + // 检查端口 + if info.Ports != "445" && info.Ports != "139" { + return &ScanResult{ + Success: false, + Service: "smbinfo", + Error: fmt.Errorf("SMBInfo插件仅支持139和445端口"), + } + } + + // 建立连接 + conn, err := net.DialTimeout("tcp", target, time.Duration(common.Timeout)*time.Second) + if err != nil { + return &ScanResult{ + Success: false, + Service: "smbinfo", + Error: fmt.Errorf("连接失败: %v", err), + } + } + defer conn.Close() + + conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)) + + // 执行SMB信息收集 + smbInfo, err := p.collectSMBInfo(conn, target) + if err != nil { + return &ScanResult{ + Success: false, + Service: "smbinfo", + Error: err, + } + } + + // 记录SMB信息发现 + if smbInfo.Valid { + msg := fmt.Sprintf("SMBInfo %s", target) + if smbInfo.OSVersion != "" { + msg += fmt.Sprintf(" [%s]", smbInfo.OSVersion) + } + if smbInfo.ComputerName != "" { + msg += fmt.Sprintf(" %s", smbInfo.ComputerName) + } + msg += fmt.Sprintf(" %s", smbInfo.Protocol) + common.LogSuccess(msg) + } + + return &ScanResult{ + Success: smbInfo.Valid, + Service: "smbinfo", + Banner: smbInfo.Summary(), + } +} + +// Exploit 执行SMBInfo利用操作 - 详细SMB信息收集 +func (p *SMBInfoPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds Credential) *ExploitResult { + target := fmt.Sprintf("%s:%s", info.Host, info.Ports) + common.LogSuccess(fmt.Sprintf("SMBInfo利用开始: %s", target)) + + var output strings.Builder + output.WriteString(fmt.Sprintf("=== SMB信息收集结果 - %s ===\n", target)) + + // 建立连接进行详细信息收集 + conn, err := net.DialTimeout("tcp", target, time.Duration(common.Timeout)*time.Second) + if err != nil { + output.WriteString(fmt.Sprintf("\n[连接失败] %v\n", err)) + return &ExploitResult{ + Success: false, + Output: output.String(), + Error: err, + } + } + defer conn.Close() + + conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)) + + // 收集SMB信息 + smbInfo, err := p.collectSMBInfo(conn, target) + if err != nil { + output.WriteString(fmt.Sprintf("\n[SMB信息收集失败] %v\n", err)) + return &ExploitResult{ + Success: false, + Output: output.String(), + Error: err, + } + } + + if !smbInfo.Valid { + output.WriteString("\n[SMB信息] 未发现有效的SMB信息\n") + return &ExploitResult{ + Success: false, + Output: output.String(), + } + } + + // 输出详细SMB信息 + output.WriteString(fmt.Sprintf("\n[协议版本] %s\n", smbInfo.Protocol)) + + if smbInfo.ComputerName != "" { + output.WriteString(fmt.Sprintf("\n[计算机名] %s\n", smbInfo.ComputerName)) + } + + if smbInfo.DomainName != "" { + output.WriteString(fmt.Sprintf("\n[域名] %s\n", smbInfo.DomainName)) + } + + if smbInfo.OSVersion != "" { + output.WriteString(fmt.Sprintf("\n[操作系统] %s\n", smbInfo.OSVersion)) + } + + if smbInfo.NativeOS != "" { + output.WriteString(fmt.Sprintf("\n[本机OS] %s\n", smbInfo.NativeOS)) + } + + if smbInfo.NativeLM != "" { + output.WriteString(fmt.Sprintf("\n[本机LM] %s\n", smbInfo.NativeLM)) + } + + if len(smbInfo.NTLMFlags) > 0 { + output.WriteString(fmt.Sprintf("\n[NTLM标志] %s\n", strings.Join(smbInfo.NTLMFlags, ", "))) + } + + common.LogSuccess(fmt.Sprintf("SMBInfo利用完成: %s", target)) + + return &ExploitResult{ + Success: true, + Output: output.String(), + } +} + +// SMBInfo SMB信息结构 +type SMBInfo struct { + Valid bool + Protocol string // SMB1 或 SMB2 + ComputerName string + DomainName string + OSVersion string + NativeOS string + NativeLM string + NTLMFlags []string +} + +// Summary 返回SMB信息摘要 +func (si *SMBInfo) Summary() string { + if !si.Valid { + return "SMB信息收集失败" + } + + var parts []string + parts = append(parts, si.Protocol) + + if si.OSVersion != "" { + parts = append(parts, si.OSVersion) + } + + if si.ComputerName != "" { + parts = append(parts, si.ComputerName) + } + + return strings.Join(parts, " ") +} + +// SMB协议数据包定义 +var ( + smbv1Negotiate1 = []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, + } + + smbv1SessionSetup = []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, + } + + smbv2Negotiate1 = []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, + } + + smbv2SessionSetup1 = []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, + } +) + +// collectSMBInfo 收集SMB信息 +func (p *SMBInfoPlugin) collectSMBInfo(conn net.Conn, target string) (*SMBInfo, error) { + // 首先尝试SMBv1协商 + _, err := conn.Write(smbv1Negotiate1) + if err != nil { + return nil, fmt.Errorf("发送SMBv1协商包失败: %v", err) + } + + // 读取SMBv1协商响应 + r1, err := p.readBytes(conn) + if err != nil { + common.LogDebug(fmt.Sprintf("读取SMBv1协商响应失败: %v", err)) + } + + // 检查是否支持SMBv1 + if len(r1) > 0 { + // SMBv1路径 + return p.handleSMBv1(conn, target) + } else { + // SMBv2路径 + return p.handleSMBv2(target) + } +} + +// handleSMBv1 处理SMBv1协议信息收集 +func (p *SMBInfoPlugin) handleSMBv1(conn net.Conn, target string) (*SMBInfo, error) { + // 发送Session Setup请求 + _, err := conn.Write(smbv1SessionSetup) + if err != nil { + return nil, fmt.Errorf("发送SMBv1 Session Setup失败: %v", err) + } + + ret, err := p.readBytes(conn) + if err != nil || len(ret) < 45 { + return nil, fmt.Errorf("读取SMBv1 Session Setup响应失败: %v", err) + } + + info := &SMBInfo{ + Valid: true, + Protocol: "SMBv1", + } + + // 解析blob信息 + blobLength := p.bytesToUint16(ret[43:45]) + blobCount := p.bytesToUint16(ret[45:47]) + + if int(blobCount) > len(ret) { + return info, nil + } + + gssNative := ret[47:] + offNTLM := bytes.Index(gssNative, []byte("NTLMSSP")) + if offNTLM == -1 { + return info, nil + } + + // 提取native OS和LM信息 + native := gssNative[int(blobLength):blobCount] + ss := strings.Split(string(native), "\x00\x00") + + if len(ss) > 0 { + info.NativeOS = p.trimName(ss[0]) + } + if len(ss) > 1 { + info.NativeLM = p.trimName(ss[1]) + } + + // 解析NTLM信息 + bs := gssNative[offNTLM:blobLength] + p.parseNTLMChallenge(bs, info) + + return info, nil +} + +// handleSMBv2 处理SMBv2协议信息收集 +func (p *SMBInfoPlugin) handleSMBv2(target string) (*SMBInfo, error) { + // 重新建立连接处理SMBv2 + conn2, err := net.DialTimeout("tcp", target, time.Duration(common.Timeout)*time.Second) + if err != nil { + return nil, fmt.Errorf("SMBv2连接失败: %v", err) + } + defer conn2.Close() + + conn2.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)) + + // 发送SMBv2协商包 + _, err = conn2.Write(smbv2Negotiate1) + if err != nil { + return nil, fmt.Errorf("发送SMBv2协商包失败: %v", err) + } + + r2, err := p.readBytes(conn2) + if err != nil { + return nil, fmt.Errorf("读取SMBv2协商响应失败: %v", err) + } + + // 构建NTLM数据包 + var ntlmData []byte + if len(r2) > 70 && hex.EncodeToString(r2[70:71]) == "03" { + flags := []byte{0x15, 0x82, 0x08, 0xa0} + ntlmData = p.getNTLMSSPData(flags) + } else { + flags := []byte{0x05, 0x80, 0x08, 0xa0} + ntlmData = p.getNTLMSSPData(flags) + } + + // 发送Session Setup + _, err = conn2.Write(smbv2SessionSetup1) + if err != nil { + return nil, fmt.Errorf("发送SMBv2 Session Setup失败: %v", err) + } + + _, err = p.readBytes(conn2) + if err != nil { + return nil, fmt.Errorf("读取SMBv2 Session Setup响应失败: %v", err) + } + + // 发送NTLM协商包 + _, err = conn2.Write(ntlmData) + if err != nil { + return nil, fmt.Errorf("发送SMBv2 NTLM包失败: %v", err) + } + + ret, err := p.readBytes(conn2) + if err != nil { + return nil, fmt.Errorf("读取SMBv2 NTLM响应失败: %v", err) + } + + ntlmOff := bytes.Index(ret, []byte("NTLMSSP")) + if ntlmOff == -1 { + return &SMBInfo{Valid: true, Protocol: "SMBv2"}, nil + } + + info := &SMBInfo{ + Valid: true, + Protocol: "SMBv2", + } + + p.parseNTLMChallenge(ret[ntlmOff:], info) + return info, nil +} + +// readBytes 从连接读取NetBIOS消息 +func (p *SMBInfoPlugin) readBytes(conn net.Conn) ([]byte, error) { + // 读取NetBIOS头部(4字节) + 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 (p *SMBInfoPlugin) parseNTLMChallenge(data []byte, info *SMBInfo) { + if len(data) < 32 { + return + } + + // 检查NTLM签名 + if !bytes.Equal(data[0:8], []byte("NTLMSSP\x00")) { + return + } + + // 检查消息类型 + if len(data) < 12 { + return + } + messageType := p.bytesToUint32(data[8:12]) + if messageType != 2 { + return + } + + // 解析Target Name + if len(data) >= 20 { + targetLength := p.bytesToUint16(data[12:14]) + targetOffset := p.bytesToUint32(data[16:20]) + + if targetLength > 0 && int(targetOffset) < len(data) && int(targetOffset+uint32(targetLength)) <= len(data) { + targetName := p.parseUnicodeString(data[targetOffset : targetOffset+uint32(targetLength)]) + if targetName != "" { + info.DomainName = targetName + } + } + } + + // 解析Flags + if len(data) >= 24 { + flags := p.bytesToUint32(data[20:24]) + info.NTLMFlags = p.parseNTLMFlags(flags) + } + + // 解析Target Info (AV_PAIR结构) + if len(data) >= 52 { + targetInfoLength := p.bytesToUint16(data[40:42]) + targetInfoOffset := p.bytesToUint32(data[44:48]) + + if targetInfoLength > 0 && int(targetInfoOffset) < len(data) && + int(targetInfoOffset+uint32(targetInfoLength)) <= len(data) { + targetInfoData := data[targetInfoOffset : targetInfoOffset+uint32(targetInfoLength)] + p.parseTargetInfo(targetInfoData, info) + } + } + + // 解析OS版本信息 + if len(data) >= 56 { + flags := p.bytesToUint32(data[20:24]) + // NTLMSSP_NEGOTIATE_VERSION = 0x02000000 + if flags&0x02000000 != 0 && len(data) >= 56 { + p.parseOSVersion(data[48:56], info) + } + } +} + +// parseTargetInfo 解析Target Information +func (p *SMBInfoPlugin) parseTargetInfo(data []byte, info *SMBInfo) { + offset := 0 + + for offset+4 <= len(data) { + avId := p.bytesToUint16(data[offset : offset+2]) + avLen := p.bytesToUint16(data[offset+2 : offset+4]) + + if avId == 0x0000 { // MsvAvEOL + break + } + + if offset+4+int(avLen) > len(data) { + break + } + + value := data[offset+4 : offset+4+int(avLen)] + + switch avId { + case 0x0001: // MsvAvNbComputerName + computerName := p.parseUnicodeString(value) + if computerName != "" { + info.ComputerName = computerName + } + case 0x0002: // MsvAvNbDomainName + if info.DomainName == "" { + domainName := p.parseUnicodeString(value) + if domainName != "" { + info.DomainName = domainName + } + } + case 0x0003: // MsvAvDnsComputerName + if info.ComputerName == "" { + dnsComputerName := p.parseUnicodeString(value) + if dnsComputerName != "" { + info.ComputerName = dnsComputerName + } + } + } + + offset += 4 + int(avLen) + } +} + +// parseOSVersion 解析操作系统版本 +func (p *SMBInfoPlugin) parseOSVersion(data []byte, info *SMBInfo) { + if len(data) < 8 { + return + } + + majorVersion := data[0] + minorVersion := data[1] + buildNumber := p.bytesToUint16(data[2:4]) + + // Windows版本映射 + 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 (p *SMBInfoPlugin) bytesToUint16(b []byte) uint16 { + if len(b) < 2 { + return 0 + } + return uint16(b[0]) | uint16(b[1])<<8 +} + +func (p *SMBInfoPlugin) 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 (p *SMBInfoPlugin) trimName(s string) string { + return strings.Trim(strings.TrimSpace(s), "\x00") +} + +func (p *SMBInfoPlugin) 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 + } + // UTF-16LE: 低字节在前 + r := uint16(data[i]) | uint16(data[i+1])<<8 + if r == 0 { + break + } + runes = append(runes, rune(r)) + } + return string(runes) +} + +func (p *SMBInfoPlugin) 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 (p *SMBInfoPlugin) getNTLMSSPData(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, + } +} + +// init 自动注册插件 +func init() { + RegisterPlugin("smbinfo", func() Plugin { + return NewSMBInfoPlugin() + }) +} \ No newline at end of file