fscan/plugins/legacy/smbinfo.go
ZacharyZcR e3c14e9f8e feat: 新增SMBInfo插件,增强SMB协议信息收集能力
- 新增smbinfo插件,专门用于SMB协议信息收集和操作系统检测
- 实现完整的NTLM Type 2消息解析,提取详细的系统信息
- 支持Windows版本识别、计算机名、域名等信息提取
- 采用标准插件输出格式,与其他插件保持一致
- 保留原始NetBIOS插件,两个插件功能互补
- 优化SMB协议数据包处理,提升兼容性和稳定性
2025-08-12 23:06:01 +08:00

701 lines
21 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package Plugins
import (
"bytes"
"encoding/hex"
"fmt"
"net"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/output"
)
// SMBInfo 主函数 - 基于最原始参考代码实现
func SMBInfo(info *common.HostInfo) error {
if info.Ports != "445" && info.Ports != "139" {
return fmt.Errorf("SMBInfo插件仅支持139和445端口")
}
realhost := fmt.Sprintf("%s:%s", info.Host, info.Ports)
conn, err := net.DialTimeout("tcp", realhost, time.Duration(common.Timeout)*time.Second)
if err != nil {
return fmt.Errorf("连接失败: %v", err)
}
defer conn.Close()
// 发送SMBv1第一个协商包
_, err = conn.Write(SMBInfoNegotiateSMBv1Data1)
if err != nil {
return fmt.Errorf("发送SMBv1协商包失败: %v", err)
}
r1, err := ReadBytes(conn)
if err != nil {
common.LogDebug(fmt.Sprintf("读取SMBv1协商响应失败: %v", err))
}
var result string
// ff534d42 SMBv1的标示
// fe534d42 SMBv2的标示
// 先发送探测SMBv1的payload不支持的SMBv1的时候返回为空然后尝试发送SMBv2的探测数据包
if len(r1) > 0 {
// SMBv1 路径
result = handleSMBv1(conn, info)
} else {
// SMBv2 路径
result = handleSMBv2(realhost, info)
}
// 显示和保存结果
if result != "" {
displaySMBInfo(info, result, len(r1) > 0)
saveSMBInfo(info, result)
} else {
// 即使没有详细信息也显示基本连接信息
displayBasicSMBInfo(info)
}
return nil
}
// handleSMBv1 处理SMBv1协议
func handleSMBv1(conn net.Conn, info *common.HostInfo) string {
// 发送第二个SMBv1包
_, err := conn.Write(SMBInfoNegotiateSMBv1Data2)
if err != nil {
common.LogDebug(fmt.Sprintf("发送SMBv1 Session Setup失败: %v", err))
return ""
}
ret, err := ReadBytes(conn)
if err != nil || len(ret) < 45 {
common.LogDebug(fmt.Sprintf("读取SMBv1 Session Setup响应失败: %v", err))
return ""
}
// 解析blob信息
blob_length := uint16(bytesToUint16(ret[43:45]))
blob_count := uint16(bytesToUint16(ret[45:47]))
if int(blob_count) > len(ret) {
common.LogDebug("blob_count超出数据范围")
return ""
}
gss_native := ret[47:]
off_ntlm := bytes.Index(gss_native, []byte("NTLMSSP"))
if off_ntlm == -1 {
common.LogDebug("未找到NTLMSSP数据")
return ""
}
// 提取native OS和LM信息
native := gss_native[int(blob_length):blob_count]
ss := strings.Split(string(native), "\x00\x00")
var nativeOS, nativeLM string
if len(ss) > 0 {
nativeOS = trimName(ss[0])
}
if len(ss) > 1 {
nativeLM = trimName(ss[1])
}
// 解析NTLM信息
bs := gss_native[off_ntlm:blob_length]
ntlmInfo := parseNTLMChallenge(bs)
// 组合结果
result := ntlmInfo
if nativeOS != "" {
result += fmt.Sprintf("NativeOS: %s\n", nativeOS)
}
if nativeLM != "" {
result += fmt.Sprintf("NativeLM: %s\n", nativeLM)
}
return result
}
// handleSMBv2 处理SMBv2协议
func handleSMBv2(realhost string, info *common.HostInfo) string {
conn2, err := net.DialTimeout("tcp", realhost, time.Duration(common.Timeout)*time.Second)
if err != nil {
common.LogDebug(fmt.Sprintf("SMBv2连接失败: %v", err))
return ""
}
defer conn2.Close()
// 发送SMBv2第一个协商包
_, err = conn2.Write(SMBInfoNegotiateSMBv2Data1)
if err != nil {
common.LogDebug(fmt.Sprintf("发送SMBv2协商包失败: %v", err))
return ""
}
r2, err := ReadBytes(conn2)
if err != nil {
common.LogDebug(fmt.Sprintf("读取SMBv2协商响应失败: %v", err))
return ""
}
// 根据响应构建NTLM数据包
var ntlmSSPNegotiatev2Data []byte
if len(r2) > 70 && hex.EncodeToString(r2[70:71]) == "03" {
flags := []byte{0x15, 0x82, 0x08, 0xa0}
ntlmSSPNegotiatev2Data = getNTLMSSPNegotiateData(flags)
} else {
flags := []byte{0x05, 0x80, 0x08, 0xa0}
ntlmSSPNegotiatev2Data = getNTLMSSPNegotiateData(flags)
}
// 发送第二个SMBv2包
_, err = conn2.Write(SMBInfoNegotiateSMBv2Data2)
if err != nil {
common.LogDebug(fmt.Sprintf("发送SMBv2第二包失败: %v", err))
return ""
}
_, err = ReadBytes(conn2)
if err != nil {
common.LogDebug(fmt.Sprintf("读取SMBv2第二包响应失败: %v", err))
return ""
}
// 发送NTLM协商包
_, err = conn2.Write(ntlmSSPNegotiatev2Data)
if err != nil {
common.LogDebug(fmt.Sprintf("发送SMBv2 NTLM包失败: %v", err))
return ""
}
ret, err := ReadBytes(conn2)
if err != nil {
common.LogDebug(fmt.Sprintf("读取SMBv2 NTLM响应失败: %v", err))
return ""
}
ntlmOff := bytes.Index(ret, []byte("NTLMSSP"))
if ntlmOff == -1 {
common.LogDebug("SMBv2响应中未找到NTLMSSP数据")
return ""
}
return parseNTLMChallenge(ret[ntlmOff:])
}
// 原始参考代码中的数据包定义
var SMBInfoNegotiateSMBv1Data1 = []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,
}
var SMBInfoNegotiateSMBv1Data2 = []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,
}
var SMBInfoNegotiateSMBv2Data1 = []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,
}
var SMBInfoNegotiateSMBv2Data2 = []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,
}
func getNTLMSSPNegotiateData(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,
}
}
// 辅助函数
func bytesToUint16(b []byte) uint16 {
return uint16(b[0]) | uint16(b[1])<<8
}
func trimName(s string) string {
return strings.Trim(strings.TrimSpace(s), "\x00")
}
// NTLM AV_PAIR类型常量
const (
MsvAvEOL = 0x0000 // End of list
MsvAvNbComputerName = 0x0001 // NetBIOS computer name
MsvAvNbDomainName = 0x0002 // NetBIOS domain name
MsvAvDnsComputerName = 0x0003 // DNS computer name
MsvAvDnsDomainName = 0x0004 // DNS domain name
MsvAvDnsTreeName = 0x0005 // DNS forest name
MsvAvFlags = 0x0006 // Server flags
MsvAvTimestamp = 0x0007 // Server timestamp
MsvAvSingleHost = 0x0008 // Single host data
MsvAvTargetName = 0x0009 // Target name
MsvAvChannelBindings = 0x000A // Channel bindings
)
// parseNTLMChallenge 解析NTLM Type 2 (Challenge) 消息
func parseNTLMChallenge(data []byte) string {
if len(data) < 32 {
return ""
}
var result strings.Builder
// 检查NTLM签名 "NTLMSSP\x00"
if !bytes.Equal(data[0:8], []byte("NTLMSSP\x00")) {
return ""
}
// 检查消息类型 (应该是 Type 2 = 0x00000002)
if len(data) < 12 {
return ""
}
messageType := bytesToUint32(data[8:12])
if messageType != 2 {
common.LogDebug(fmt.Sprintf("非Type 2 NTLM消息, 类型: %d", messageType))
return ""
}
result.WriteString("Protocol: NTLM Type 2 (Challenge)\n")
// 解析Target Name (偏移12-20, 8字节的Security Buffer)
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 != "" {
result.WriteString(fmt.Sprintf("Target Name: %s\n", targetName))
}
}
}
// 解析Flags (偏移20-24)
if len(data) >= 24 {
flags := bytesToUint32(data[20:24])
parseNTLMFlags(flags, &result)
}
// 解析Challenge (偏移24-32, 8字节)
if len(data) >= 32 {
challenge := data[24:32]
result.WriteString(fmt.Sprintf("Server Challenge: %s\n", hex.EncodeToString(challenge)))
}
// 解析Target Info (AV_PAIR结构) - 偏移44开始的Security Buffer
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, &result)
}
}
// 解析OS版本信息 (如果存在, 偏移48-56)
if len(data) >= 56 {
// 检查是否包含版本信息 (通过flags判断)
if len(data) >= 24 {
flags := bytesToUint32(data[20:24])
// NTLMSSP_NEGOTIATE_VERSION = 0x02000000
if flags&0x02000000 != 0 && len(data) >= 56 {
parseOSVersion(data[48:56], &result)
}
}
}
return result.String()
}
// parseUnicodeString 解析UTF-16LE编码的字符串
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
}
// UTF-16LE: 低字节在前
r := uint16(data[i]) | uint16(data[i+1])<<8
if r == 0 {
break
}
runes = append(runes, rune(r))
}
return string(runes)
}
// parseNTLMFlags 解析NTLM标志位
func parseNTLMFlags(flags uint32, result *strings.Builder) {
flagNames := map[uint32]string{
0x00000001: "NEGOTIATE_UNICODE",
0x00000002: "NEGOTIATE_OEM",
0x00000004: "REQUEST_TARGET",
0x00000010: "NEGOTIATE_SIGN",
0x00000020: "NEGOTIATE_SEAL",
0x00000040: "NEGOTIATE_DATAGRAM",
0x00000080: "NEGOTIATE_LM_KEY",
0x00000200: "NEGOTIATE_NTLM",
0x00001000: "NEGOTIATE_DOMAIN_SUPPLIED",
0x00002000: "NEGOTIATE_WORKSTATION_SUPPLIED",
0x00004000: "NEGOTIATE_LOCAL_CALL",
0x00008000: "NEGOTIATE_ALWAYS_SIGN",
0x00010000: "TARGET_TYPE_DOMAIN",
0x00020000: "TARGET_TYPE_SERVER",
0x00040000: "TARGET_TYPE_SHARE",
0x00080000: "NEGOTIATE_EXTENDED_SESSIONSECURITY",
0x00100000: "NEGOTIATE_IDENTIFY",
0x02000000: "NEGOTIATE_VERSION",
0x20000000: "NEGOTIATE_128",
0x40000000: "NEGOTIATE_KEY_EXCH",
0x80000000: "NEGOTIATE_56",
}
var activeFlags []string
for flag, name := range flagNames {
if flags&flag != 0 {
activeFlags = append(activeFlags, name)
}
}
if len(activeFlags) > 0 {
result.WriteString(fmt.Sprintf("NTLM Flags: %s\n", strings.Join(activeFlags, ", ")))
}
}
// parseTargetInfo 解析NTLM Target Information (AV_PAIR结构)
func parseTargetInfo(data []byte, result *strings.Builder) {
offset := 0
for offset+4 <= len(data) {
// 读取AV_PAIR结构: AvId (2字节) + AvLen (2字节) + Value (AvLen字节)
avId := bytesToUint16(data[offset:offset+2])
avLen := bytesToUint16(data[offset+2:offset+4])
if avId == MsvAvEOL {
break // 列表结束
}
if offset+4+int(avLen) > len(data) {
break // 数据不足
}
value := data[offset+4:offset+4+int(avLen)]
switch avId {
case MsvAvNbComputerName:
computerName := parseUnicodeString(value)
if computerName != "" {
result.WriteString(fmt.Sprintf("NetBIOS Computer Name: %s\n", computerName))
}
case MsvAvNbDomainName:
domainName := parseUnicodeString(value)
if domainName != "" {
result.WriteString(fmt.Sprintf("NetBIOS Domain Name: %s\n", domainName))
}
case MsvAvDnsComputerName:
dnsComputerName := parseUnicodeString(value)
if dnsComputerName != "" {
result.WriteString(fmt.Sprintf("DNS Computer Name: %s\n", dnsComputerName))
}
case MsvAvDnsDomainName:
dnsDomainName := parseUnicodeString(value)
if dnsDomainName != "" {
result.WriteString(fmt.Sprintf("DNS Domain Name: %s\n", dnsDomainName))
}
case MsvAvDnsTreeName:
forestName := parseUnicodeString(value)
if forestName != "" {
result.WriteString(fmt.Sprintf("DNS Forest Name: %s\n", forestName))
}
case MsvAvFlags:
if len(value) >= 4 {
serverFlags := bytesToUint32(value[0:4])
parseServerFlags(serverFlags, result)
}
case MsvAvTimestamp:
if len(value) >= 8 {
timestamp := bytesToUint64(value[0:8])
// Windows FILETIME: 100纳秒间隔自1601年1月1日
if timestamp > 0 {
// 转换为Unix时间戳 (简化版本)
unixTime := int64((timestamp - 116444736000000000) / 10000000)
if unixTime > 0 {
t := time.Unix(unixTime, 0)
result.WriteString(fmt.Sprintf("Server Timestamp: %s\n", t.Format(time.RFC3339)))
}
}
}
case MsvAvTargetName:
targetName := parseUnicodeString(value)
if targetName != "" {
result.WriteString(fmt.Sprintf("Target SPN: %s\n", targetName))
}
}
offset += 4 + int(avLen)
}
}
// parseServerFlags 解析服务器标志
func parseServerFlags(flags uint32, result *strings.Builder) {
var serverFlags []string
if flags&0x00000001 != 0 {
serverFlags = append(serverFlags, "CONSTRAINED_AUTHENTICATION")
}
if flags&0x00000002 != 0 {
serverFlags = append(serverFlags, "MIC_PROVIDED")
}
if flags&0x00000004 != 0 {
serverFlags = append(serverFlags, "UNTRUSTED_SPN_SOURCE")
}
if len(serverFlags) > 0 {
result.WriteString(fmt.Sprintf("Server Flags: %s\n", strings.Join(serverFlags, ", ")))
}
}
// parseOSVersion 解析操作系统版本信息
func parseOSVersion(data []byte, result *strings.Builder) {
if len(data) < 8 {
return
}
majorVersion := data[0]
minorVersion := data[1]
buildNumber := bytesToUint16(data[2:4])
reserved := bytesToUint32(data[4:8])
// 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)
}
result.WriteString(fmt.Sprintf("OS Version: %s (Build %d)\n", osName, buildNumber))
if reserved != 0 {
result.WriteString(fmt.Sprintf("OS Reserved: 0x%08X\n", reserved))
}
}
// bytesToUint32 将字节数组转换为32位无符号整数 (小端序)
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
}
// bytesToUint64 将字节数组转换为64位无符号整数 (小端序)
func bytesToUint64(b []byte) uint64 {
if len(b) < 8 {
return 0
}
return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 |
uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56
}
// displaySMBInfo 显示SMB信息
func displaySMBInfo(hostInfo *common.HostInfo, info string, isSMBv1 bool) {
target := fmt.Sprintf("%s:%s", hostInfo.Host, hostInfo.Ports)
smbVersion := "SMB2"
if isSMBv1 {
smbVersion = "SMB1"
}
// 提取操作系统信息用于主显示行
osInfo := extractOSInfo(info)
computerName := extractComputerName(info)
var successMsg string
if osInfo != "" && computerName != "" {
successMsg = fmt.Sprintf("SMBInfo %s [%s] %s %s", target, osInfo, computerName, smbVersion)
} else if osInfo != "" {
successMsg = fmt.Sprintf("SMBInfo %s [%s] %s", target, osInfo, smbVersion)
} else if computerName != "" {
successMsg = fmt.Sprintf("SMBInfo %s %s %s", target, computerName, smbVersion)
} else {
successMsg = fmt.Sprintf("SMBInfo %s %s", target, smbVersion)
}
common.LogSuccess(successMsg)
}
// extractOSInfo 从信息中提取操作系统信息
func extractOSInfo(info string) string {
lines := strings.Split(info, "\n")
for _, line := range lines {
if strings.Contains(line, "OS Version:") {
parts := strings.Split(line, ":")
if len(parts) > 1 {
osVersion := strings.TrimSpace(parts[1])
// 简化OS版本显示提取主要版本号
if strings.Contains(osVersion, "Windows") {
if strings.Contains(osVersion, "Build") {
parts := strings.Split(osVersion, " (Build")
if len(parts) > 0 {
return strings.TrimSpace(parts[0])
}
}
return osVersion
}
return osVersion
}
}
if strings.Contains(line, "Windows版本:") {
parts := strings.Split(line, ":")
if len(parts) > 1 {
return strings.TrimSpace(parts[1])
}
}
if strings.Contains(line, "NativeOS:") {
parts := strings.Split(line, ":")
if len(parts) > 1 {
return strings.TrimSpace(parts[1])
}
}
}
return ""
}
// extractComputerName 从信息中提取计算机名
func extractComputerName(info string) string {
lines := strings.Split(info, "\n")
for _, line := range lines {
if strings.Contains(line, "NetBIOS Computer Name:") {
parts := strings.Split(line, ":")
if len(parts) > 1 {
return strings.TrimSpace(parts[1])
}
}
if strings.Contains(line, "NetBIOS计算机名:") {
parts := strings.Split(line, ":")
if len(parts) > 1 {
return strings.TrimSpace(parts[1])
}
}
if strings.Contains(line, "DNS Computer Name:") {
parts := strings.Split(line, ":")
if len(parts) > 1 {
return strings.TrimSpace(parts[1])
}
}
if strings.Contains(line, "DNS计算机名:") {
parts := strings.Split(line, ":")
if len(parts) > 1 {
return strings.TrimSpace(parts[1])
}
}
}
return ""
}
// displayBasicSMBInfo 显示基本SMB信息
func displayBasicSMBInfo(hostInfo *common.HostInfo) {
target := fmt.Sprintf("%s:%s", hostInfo.Host, hostInfo.Ports)
msg := fmt.Sprintf("SMBInfo %s SMB service detected", target)
common.LogSuccess(msg)
}
// saveSMBInfo 保存SMB信息
func saveSMBInfo(hostInfo *common.HostInfo, info string) {
result := &output.ScanResult{
Time: time.Now(),
Type: output.TypeService,
Target: hostInfo.Host,
Status: "open",
Details: map[string]interface{}{
"port": hostInfo.Ports,
"protocol": "smb",
"info": info,
},
}
common.SaveResult(result)
}