mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 14:06:44 +08:00
feat: 新增SMBInfo插件,增强SMB协议信息收集能力
- 新增smbinfo插件,专门用于SMB协议信息收集和操作系统检测 - 实现完整的NTLM Type 2消息解析,提取详细的系统信息 - 支持Windows版本识别、计算机名、域名等信息提取 - 采用标准插件输出格式,与其他插件保持一致 - 保留原始NetBIOS插件,两个插件功能互补 - 优化SMB协议数据包处理,提升兼容性和稳定性
This commit is contained in:
parent
daa7fb2dcb
commit
e3c14e9f8e
@ -34,7 +34,8 @@ import (
|
|||||||
_ "github.com/shadow1ng/fscan/plugins/legacy/elasticsearch" // 跨平台服务
|
_ "github.com/shadow1ng/fscan/plugins/legacy/elasticsearch" // 跨平台服务
|
||||||
_ "github.com/shadow1ng/fscan/plugins/legacy/findnet" // 网络发现
|
_ "github.com/shadow1ng/fscan/plugins/legacy/findnet" // 网络发现
|
||||||
_ "github.com/shadow1ng/fscan/plugins/legacy/ms17010" // Windows SMB漏洞(但扫描器可跨平台)
|
_ "github.com/shadow1ng/fscan/plugins/legacy/ms17010" // Windows SMB漏洞(但扫描器可跨平台)
|
||||||
_ "github.com/shadow1ng/fscan/plugins/legacy/netbios" // NetBIOS协议(主要Windows但可跨平台扫描)
|
_ "github.com/shadow1ng/fscan/plugins/legacy/netbios" // NetBIOS协议(主要Windows但可跨平台扫描)
|
||||||
|
_ "github.com/shadow1ng/fscan/plugins/legacy/smbinfo" // SMB信息收集(主要Windows但可跨平台扫描)
|
||||||
_ "github.com/shadow1ng/fscan/plugins/legacy/rdp" // RDP协议扫描(可跨平台)
|
_ "github.com/shadow1ng/fscan/plugins/legacy/rdp" // RDP协议扫描(可跨平台)
|
||||||
_ "github.com/shadow1ng/fscan/plugins/legacy/smb" // SMB协议扫描(可跨平台)
|
_ "github.com/shadow1ng/fscan/plugins/legacy/smb" // SMB协议扫描(可跨平台)
|
||||||
_ "github.com/shadow1ng/fscan/plugins/legacy/smb2" // SMBv2协议扫描(可跨平台)
|
_ "github.com/shadow1ng/fscan/plugins/legacy/smb2" // SMBv2协议扫描(可跨平台)
|
||||||
|
@ -9,35 +9,48 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ReadBytes 从连接读取数据直到EOF或错误
|
// ReadBytes 从连接读取数据直到EOF或错误 - 改进的SMB协议处理
|
||||||
func ReadBytes(conn net.Conn) ([]byte, error) {
|
func ReadBytes(conn net.Conn) ([]byte, error) {
|
||||||
size := 4096 // 缓冲区大小
|
// 首先读取NetBIOS头部(4字节)来确定消息长度
|
||||||
buf := make([]byte, size)
|
headerBuf := make([]byte, 4)
|
||||||
var result []byte
|
n, err := conn.Read(headerBuf)
|
||||||
var lastErr error
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("读取NetBIOS头部失败: %v", err)
|
||||||
|
}
|
||||||
|
if n != 4 {
|
||||||
|
return nil, fmt.Errorf("NetBIOS头部长度不足: %d", n)
|
||||||
|
}
|
||||||
|
|
||||||
// 循环读取数据
|
// 解析NetBIOS消息长度(大端序)
|
||||||
for {
|
messageLength := int(headerBuf[0])<<24 | int(headerBuf[1])<<16 | int(headerBuf[2])<<8 | int(headerBuf[3])
|
||||||
count, err := conn.Read(buf)
|
|
||||||
|
// 防止过大的消息长度(安全检查)
|
||||||
|
if messageLength > 1024*1024 { // 1MB限制
|
||||||
|
return nil, fmt.Errorf("消息长度过大: %d", messageLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果消息长度为0,只返回头部
|
||||||
|
if messageLength == 0 {
|
||||||
|
return headerBuf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取完整消息体
|
||||||
|
messageBuf := make([]byte, messageLength)
|
||||||
|
totalRead := 0
|
||||||
|
for totalRead < messageLength {
|
||||||
|
n, err := conn.Read(messageBuf[totalRead:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lastErr = err
|
return nil, fmt.Errorf("读取消息体失败(已读取%d/%d字节): %v", totalRead, messageLength, err)
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
result = append(result, buf[0:count]...)
|
|
||||||
|
|
||||||
// 如果读取的数据小于缓冲区,说明已经读完
|
|
||||||
if count < size {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
totalRead += n
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果读到了数据,则忽略错误
|
// 返回完整消息(头部+消息体)
|
||||||
if len(result) > 0 {
|
result := make([]byte, 0, 4+messageLength)
|
||||||
return result, nil
|
result = append(result, headerBuf...)
|
||||||
}
|
result = append(result, messageBuf...)
|
||||||
|
|
||||||
return result, lastErr
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 默认AES加密密钥
|
// 默认AES加密密钥
|
||||||
|
@ -15,9 +15,9 @@ func NewNetBiosPlugin() base.Plugin {
|
|||||||
Author: "fscan-team",
|
Author: "fscan-team",
|
||||||
Description: "NetBIOS信息收集和主机名解析",
|
Description: "NetBIOS信息收集和主机名解析",
|
||||||
Category: "service",
|
Category: "service",
|
||||||
Ports: []int{139, 445}, // NetBIOS端口
|
Ports: []int{137, 139}, // NetBIOS端口
|
||||||
Protocols: []string{"tcp", "udp"},
|
Protocols: []string{"tcp", "udp"},
|
||||||
Tags: []string{"netbios", "information-gathering", "hostname", "smb"},
|
Tags: []string{"netbios", "information-gathering", "hostname"},
|
||||||
}
|
}
|
||||||
|
|
||||||
// 适配器选项
|
// 适配器选项
|
||||||
@ -25,10 +25,10 @@ func NewNetBiosPlugin() base.Plugin {
|
|||||||
CheckBruteFlag: false, // NetBIOS信息收集不依赖暴力破解标志
|
CheckBruteFlag: false, // NetBIOS信息收集不依赖暴力破解标志
|
||||||
IsVulnPlugin: false, // 这不是漏洞检测插件
|
IsVulnPlugin: false, // 这不是漏洞检测插件
|
||||||
IsInfoPlugin: true, // 这是信息收集插件
|
IsInfoPlugin: true, // 这是信息收集插件
|
||||||
CustomPorts: []int{139, 445}, // NetBIOS/SMB端口
|
CustomPorts: []int{137, 139}, // NetBIOS端口
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建适配器,直接使用老版本的NetBIOS函数
|
// 创建适配器,使用NetBIOS函数
|
||||||
return adapters.NewLegacyPlugin(metadata, LegacyPlugins.NetBIOS, options)
|
return adapters.NewLegacyPlugin(metadata, LegacyPlugins.NetBIOS, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,9 +41,9 @@ func init() {
|
|||||||
Author: "fscan-team",
|
Author: "fscan-team",
|
||||||
Description: "NetBIOS信息收集和主机名解析",
|
Description: "NetBIOS信息收集和主机名解析",
|
||||||
Category: "service",
|
Category: "service",
|
||||||
Ports: []int{139, 445},
|
Ports: []int{137, 139},
|
||||||
Protocols: []string{"tcp", "udp"},
|
Protocols: []string{"tcp", "udp"},
|
||||||
Tags: []string{"netbios", "information-gathering", "hostname", "smb"},
|
Tags: []string{"netbios", "information-gathering", "hostname"},
|
||||||
}
|
}
|
||||||
|
|
||||||
factory := base.NewSimplePluginFactory(metadata, func() base.Plugin {
|
factory := base.NewSimplePluginFactory(metadata, func() base.Plugin {
|
||||||
|
701
plugins/legacy/smbinfo.go
Normal file
701
plugins/legacy/smbinfo.go
Normal file
@ -0,0 +1,701 @@
|
|||||||
|
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)
|
||||||
|
}
|
54
plugins/legacy/smbinfo/plugin.go
Normal file
54
plugins/legacy/smbinfo/plugin.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package smbinfo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/shadow1ng/fscan/plugins/adapters"
|
||||||
|
"github.com/shadow1ng/fscan/plugins/base"
|
||||||
|
LegacyPlugins "github.com/shadow1ng/fscan/plugins/legacy"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewSMBInfoPlugin 创建SMB信息收集插件
|
||||||
|
func NewSMBInfoPlugin() base.Plugin {
|
||||||
|
// 插件元数据
|
||||||
|
metadata := &base.PluginMetadata{
|
||||||
|
Name: "smbinfo",
|
||||||
|
Version: "1.0.0",
|
||||||
|
Author: "fscan-team",
|
||||||
|
Description: "SMB协议信息收集和操作系统检测",
|
||||||
|
Category: "service",
|
||||||
|
Ports: []int{139, 445}, // SMB端口
|
||||||
|
Protocols: []string{"tcp"},
|
||||||
|
Tags: []string{"smb", "information-gathering", "os-detection", "ntlm"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 适配器选项
|
||||||
|
options := &adapters.LegacyPluginOptions{
|
||||||
|
CheckBruteFlag: false, // SMB信息收集不依赖暴力破解标志
|
||||||
|
IsVulnPlugin: false, // 这不是漏洞检测插件
|
||||||
|
IsInfoPlugin: true, // 这是信息收集插件
|
||||||
|
CustomPorts: []int{139, 445}, // SMB端口
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建适配器,使用SMBInfo函数
|
||||||
|
return adapters.NewLegacyPlugin(metadata, LegacyPlugins.SMBInfo, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// init 自动注册SMBInfo插件
|
||||||
|
func init() {
|
||||||
|
// 创建插件工厂
|
||||||
|
metadata := &base.PluginMetadata{
|
||||||
|
Name: "smbinfo",
|
||||||
|
Version: "1.0.0",
|
||||||
|
Author: "fscan-team",
|
||||||
|
Description: "SMB协议信息收集和操作系统检测",
|
||||||
|
Category: "service",
|
||||||
|
Ports: []int{139, 445},
|
||||||
|
Protocols: []string{"tcp"},
|
||||||
|
Tags: []string{"smb", "information-gathering", "os-detection", "ntlm"},
|
||||||
|
}
|
||||||
|
|
||||||
|
factory := base.NewSimplePluginFactory(metadata, func() base.Plugin {
|
||||||
|
return NewSMBInfoPlugin()
|
||||||
|
})
|
||||||
|
|
||||||
|
base.GlobalPluginRegistry.Register("smbinfo", factory)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user