mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 14:06:44 +08:00

- 删除整个legacy插件系统(7794行代码) - 完成所有插件向单文件架构迁移 - 移除19个插件的虚假Exploit功能,只保留真实利用: * Redis: 文件写入、SSH密钥注入、计划任务 * SSH: 命令执行 * MS17010: EternalBlue漏洞利用 - 统一插件接口,简化架构复杂度 - 清理临时文件和备份文件 重构效果: - 代码行数: -7794行 - 插件文件数: 从3文件架构→单文件架构 - 真实利用插件: 从22个→3个 - 架构复杂度: 大幅简化
466 lines
13 KiB
Go
466 lines
13 KiB
Go
package services
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/shadow1ng/fscan/common"
|
|
)
|
|
|
|
// NetBIOSPlugin NetBIOS名称服务扫描插件 - 收集Windows主机名和域信息
|
|
type NetBIOSPlugin struct {
|
|
name string
|
|
ports []int
|
|
}
|
|
|
|
// NewNetBIOSPlugin 创建NetBIOS插件
|
|
func NewNetBIOSPlugin() *NetBIOSPlugin {
|
|
return &NetBIOSPlugin{
|
|
name: "netbios",
|
|
ports: []int{137, 139}, // NetBIOS名称服务和会话服务端口
|
|
}
|
|
}
|
|
|
|
// GetName 实现Plugin接口
|
|
func (p *NetBIOSPlugin) GetName() string {
|
|
return p.name
|
|
}
|
|
|
|
// GetPorts 实现Plugin接口
|
|
func (p *NetBIOSPlugin) GetPorts() []int {
|
|
return p.ports
|
|
}
|
|
|
|
// Scan 执行NetBIOS扫描 - 收集Windows主机和域信息
|
|
func (p *NetBIOSPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
|
|
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
|
|
|
// 检查端口类型
|
|
if info.Ports != "137" && info.Ports != "139" {
|
|
return &ScanResult{
|
|
Success: false,
|
|
Service: "netbios",
|
|
Error: fmt.Errorf("NetBIOS插件仅支持137和139端口"),
|
|
}
|
|
}
|
|
|
|
var netbiosInfo *NetBIOSInfo
|
|
var err error
|
|
|
|
if info.Ports == "137" {
|
|
// UDP端口137 - NetBIOS名称服务
|
|
netbiosInfo, err = p.queryNetBIOSNames(info.Host)
|
|
} else {
|
|
// TCP端口139 - NetBIOS会话服务
|
|
netbiosInfo, err = p.queryNetBIOSSession(info.Host)
|
|
}
|
|
|
|
if err != nil {
|
|
return &ScanResult{
|
|
Success: false,
|
|
Service: "netbios",
|
|
Error: err,
|
|
}
|
|
}
|
|
|
|
if !netbiosInfo.Valid {
|
|
return &ScanResult{
|
|
Success: false,
|
|
Service: "netbios",
|
|
Error: fmt.Errorf("未发现有效的NetBIOS信息"),
|
|
}
|
|
}
|
|
|
|
// 记录NetBIOS发现信息
|
|
msg := fmt.Sprintf("NetBios %s", target)
|
|
if netbiosInfo.Summary() != "" {
|
|
msg += fmt.Sprintf(" %s", netbiosInfo.Summary())
|
|
}
|
|
common.LogSuccess(msg)
|
|
|
|
return &ScanResult{
|
|
Success: true,
|
|
Service: "netbios",
|
|
Banner: netbiosInfo.Summary(),
|
|
}
|
|
}
|
|
|
|
|
|
// NetBIOSInfo NetBIOS信息结构
|
|
type NetBIOSInfo struct {
|
|
Valid bool
|
|
ComputerName string
|
|
DomainName string
|
|
WorkstationService string
|
|
ServerService string
|
|
DomainControllers string
|
|
OSVersion string
|
|
NetBIOSComputerName string
|
|
NetBIOSDomainName string
|
|
}
|
|
|
|
// Summary 返回NetBIOS信息摘要
|
|
func (ni *NetBIOSInfo) Summary() string {
|
|
if !ni.Valid {
|
|
return ""
|
|
}
|
|
|
|
var parts []string
|
|
|
|
// 优先使用完整的计算机名
|
|
if ni.ComputerName != "" {
|
|
if ni.DomainName != "" && !strings.Contains(ni.ComputerName, ".") {
|
|
parts = append(parts, fmt.Sprintf("%s\\%s", ni.DomainName, ni.ComputerName))
|
|
} else {
|
|
parts = append(parts, ni.ComputerName)
|
|
}
|
|
} else {
|
|
// 使用服务名称
|
|
var name string
|
|
if ni.ServerService != "" {
|
|
name = ni.ServerService
|
|
} else if ni.WorkstationService != "" {
|
|
name = ni.WorkstationService
|
|
} else if ni.NetBIOSComputerName != "" {
|
|
name = ni.NetBIOSComputerName
|
|
}
|
|
|
|
if name != "" {
|
|
if ni.DomainName != "" {
|
|
parts = append(parts, fmt.Sprintf("%s\\%s", ni.DomainName, name))
|
|
} else if ni.NetBIOSDomainName != "" {
|
|
parts = append(parts, fmt.Sprintf("%s\\%s", ni.NetBIOSDomainName, name))
|
|
} else {
|
|
parts = append(parts, name)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 添加域控制器标识
|
|
if ni.DomainControllers != "" {
|
|
if len(parts) > 0 {
|
|
parts[0] = fmt.Sprintf("DC:%s", parts[0])
|
|
}
|
|
}
|
|
|
|
// 添加操作系统信息
|
|
if ni.OSVersion != "" {
|
|
parts = append(parts, ni.OSVersion)
|
|
}
|
|
|
|
return strings.Join(parts, " ")
|
|
}
|
|
|
|
// queryNetBIOSNames 查询NetBIOS名称服务(UDP 137)
|
|
func (p *NetBIOSPlugin) queryNetBIOSNames(host string) (*NetBIOSInfo, error) {
|
|
// NetBIOS名称查询数据包
|
|
queryPacket := []byte{
|
|
0x66, 0x66, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x20, 0x43, 0x4B, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
|
|
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
|
|
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x00, 0x00, 0x21, 0x00, 0x01,
|
|
}
|
|
|
|
target := fmt.Sprintf("%s:137", host)
|
|
conn, err := net.DialTimeout("udp", target, time.Duration(common.Timeout)*time.Second)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("连接NetBIOS名称服务失败: %v", err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
|
|
|
|
_, err = conn.Write(queryPacket)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("发送NetBIOS查询失败: %v", err)
|
|
}
|
|
|
|
response := make([]byte, 1024)
|
|
n, err := conn.Read(response)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("读取NetBIOS响应失败: %v", err)
|
|
}
|
|
|
|
return p.parseNetBIOSNames(response[:n])
|
|
}
|
|
|
|
// queryNetBIOSSession 查询NetBIOS会话服务(TCP 139)
|
|
func (p *NetBIOSPlugin) queryNetBIOSSession(host string) (*NetBIOSInfo, error) {
|
|
target := fmt.Sprintf("%s:139", host)
|
|
conn, err := net.DialTimeout("tcp", target, time.Duration(common.Timeout)*time.Second)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("连接NetBIOS会话服务失败: %v", err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
|
|
|
|
// 发送SMB协商数据包
|
|
smbNegotiate1 := []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,
|
|
}
|
|
|
|
_, err = conn.Write(smbNegotiate1)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("发送SMB协商1失败: %v", err)
|
|
}
|
|
|
|
response1 := make([]byte, 1024)
|
|
_, err = conn.Read(response1)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("读取SMB协商1响应失败: %v", err)
|
|
}
|
|
|
|
// 发送Session Setup请求
|
|
smbSessionSetup := []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,
|
|
}
|
|
|
|
_, err = conn.Write(smbSessionSetup)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("发送SMB Session Setup失败: %v", err)
|
|
}
|
|
|
|
response2 := make([]byte, 2048)
|
|
n, err := conn.Read(response2)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("读取SMB Session Setup响应失败: %v", err)
|
|
}
|
|
|
|
return p.parseNetBIOSSession(response2[:n])
|
|
}
|
|
|
|
// parseNetBIOSNames 解析NetBIOS名称查询响应
|
|
func (p *NetBIOSPlugin) parseNetBIOSNames(data []byte) (*NetBIOSInfo, error) {
|
|
info := &NetBIOSInfo{Valid: false}
|
|
|
|
if len(data) < 57 {
|
|
return info, fmt.Errorf("NetBIOS响应数据过短")
|
|
}
|
|
|
|
// 获取名称记录数量
|
|
numNames := int(data[56])
|
|
if numNames == 0 {
|
|
return info, fmt.Errorf("没有NetBIOS名称记录")
|
|
}
|
|
|
|
nameData := data[57:]
|
|
|
|
// 服务类型映射
|
|
uniqueNames := map[byte]string{
|
|
0x00: "WorkstationService",
|
|
0x03: "Messenger Service",
|
|
0x06: "RAS Server Service",
|
|
0x1F: "NetDDE Service",
|
|
0x20: "ServerService",
|
|
0x21: "RAS Client Service",
|
|
0x1D: "Master Browser",
|
|
0x1B: "Domain Master Browser",
|
|
}
|
|
|
|
groupNames := map[byte]string{
|
|
0x00: "DomainName",
|
|
0x1C: "DomainControllers",
|
|
0x1E: "Browser Service Elections",
|
|
}
|
|
|
|
info.Valid = true
|
|
|
|
// 解析每个名称记录
|
|
for i := 0; i < numNames && len(nameData) >= 18*(i+1); i++ {
|
|
offset := 18 * i
|
|
name := strings.TrimSpace(string(nameData[offset : offset+15]))
|
|
flagByte := nameData[offset+15]
|
|
|
|
if len(nameData) >= 18*(i+1) {
|
|
nameFlags := nameData[offset+16]
|
|
|
|
if nameFlags >= 128 {
|
|
// 组名称
|
|
if service, exists := groupNames[flagByte]; exists {
|
|
switch service {
|
|
case "DomainName":
|
|
info.DomainName = name
|
|
case "DomainControllers":
|
|
info.DomainControllers = name
|
|
}
|
|
}
|
|
} else {
|
|
// 唯一名称
|
|
if service, exists := uniqueNames[flagByte]; exists {
|
|
switch service {
|
|
case "WorkstationService":
|
|
info.WorkstationService = name
|
|
case "ServerService":
|
|
info.ServerService = name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return info, nil
|
|
}
|
|
|
|
// parseNetBIOSSession 解析NetBIOS会话响应
|
|
func (p *NetBIOSPlugin) parseNetBIOSSession(data []byte) (*NetBIOSInfo, error) {
|
|
info := &NetBIOSInfo{Valid: false}
|
|
|
|
if len(data) < 47 {
|
|
return info, fmt.Errorf("SMB响应数据过短")
|
|
}
|
|
|
|
info.Valid = true
|
|
|
|
// 解析OS版本信息
|
|
blobLength := int(data[43]) + int(data[44])*256
|
|
if len(data) >= 48+blobLength {
|
|
osVersion := data[47+blobLength:]
|
|
osText := p.cleanOSString(osVersion)
|
|
if osText != "" {
|
|
info.OSVersion = osText
|
|
}
|
|
}
|
|
|
|
// 查找NTLM数据
|
|
ntlmStart := bytes.Index(data, []byte("NTLMSSP"))
|
|
if ntlmStart != -1 && len(data) > ntlmStart+45 {
|
|
p.parseNTLMInfo(data[ntlmStart:], info)
|
|
}
|
|
|
|
return info, nil
|
|
}
|
|
|
|
// parseNTLMInfo 解析NTLM信息
|
|
func (p *NetBIOSPlugin) parseNTLMInfo(data []byte, info *NetBIOSInfo) {
|
|
if len(data) < 45 {
|
|
return
|
|
}
|
|
|
|
// 获取Target Info偏移和长度
|
|
targetInfoLength := int(data[40]) + int(data[41])*256
|
|
targetInfoOffset := int(data[44])
|
|
|
|
if targetInfoOffset+targetInfoLength > len(data) {
|
|
return
|
|
}
|
|
|
|
// 解析AV_PAIR结构
|
|
targetInfo := data[targetInfoOffset : targetInfoOffset+targetInfoLength]
|
|
offset := 0
|
|
|
|
for offset+4 <= len(targetInfo) {
|
|
avId := int(targetInfo[offset]) + int(targetInfo[offset+1])*256
|
|
avLen := int(targetInfo[offset+2]) + int(targetInfo[offset+3])*256
|
|
|
|
if avId == 0x0000 || offset+4+avLen > len(targetInfo) {
|
|
break
|
|
}
|
|
|
|
value := p.parseUnicodeString(targetInfo[offset+4 : offset+4+avLen])
|
|
|
|
switch avId {
|
|
case 0x0001: // NetBIOS computer name
|
|
info.NetBIOSComputerName = value
|
|
case 0x0002: // NetBIOS domain name
|
|
info.NetBIOSDomainName = value
|
|
case 0x0003: // DNS computer name
|
|
if info.ComputerName == "" {
|
|
info.ComputerName = value
|
|
}
|
|
case 0x0004: // DNS domain name
|
|
if info.DomainName == "" {
|
|
info.DomainName = value
|
|
}
|
|
}
|
|
|
|
offset += 4 + avLen
|
|
}
|
|
}
|
|
|
|
// cleanOSString 清理操作系统字符串
|
|
func (p *NetBIOSPlugin) cleanOSString(data []byte) string {
|
|
// 移除NULL字节并分割
|
|
cleaned := bytes.ReplaceAll(data, []byte{0x00, 0x00}, []byte{124})
|
|
cleaned = bytes.ReplaceAll(cleaned, []byte{0x00}, []byte{})
|
|
|
|
if len(cleaned) == 0 {
|
|
return ""
|
|
}
|
|
|
|
// 移除最后的分隔符
|
|
if cleaned[len(cleaned)-1] == 124 {
|
|
cleaned = cleaned[:len(cleaned)-1]
|
|
}
|
|
|
|
osText := string(cleaned)
|
|
parts := strings.Split(osText, "|")
|
|
if len(parts) > 0 {
|
|
return parts[0]
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// parseUnicodeString 解析Unicode字符串
|
|
func (p *NetBIOSPlugin) parseUnicodeString(data []byte) string {
|
|
if len(data)%2 != 0 {
|
|
return ""
|
|
}
|
|
|
|
var result []rune
|
|
for i := 0; i < len(data); i += 2 {
|
|
if i+1 >= len(data) {
|
|
break
|
|
}
|
|
// UTF-16LE编码
|
|
char := uint16(data[i]) | uint16(data[i+1])<<8
|
|
if char == 0 {
|
|
break
|
|
}
|
|
result = append(result, rune(char))
|
|
}
|
|
|
|
return string(result)
|
|
}
|
|
|
|
// byteToInt 字节转整数
|
|
func (p *NetBIOSPlugin) byteToInt(b byte) int {
|
|
return int(b)
|
|
}
|
|
|
|
// init 自动注册插件
|
|
func init() {
|
|
RegisterPlugin("netbios", func() Plugin {
|
|
return NewNetBIOSPlugin()
|
|
})
|
|
}
|