fscan/plugins/services/findnet.go
ZacharyZcR 8f54702c02 refactor: 精准修复插件系统三个设计问题
经Linus式架构审计,发现并修复插件系统中的具体问题:

## 核心修复

### 1. 消除local插件GetPorts()方法冗余
- 删除21个local插件中无意义的GetPorts()方法
- 简化local.Plugin接口:移除端口概念
- 理由:本地插件不涉及网络,端口概念完全多余

### 2. 消除web插件GetPorts()方法冗余
- 删除2个web插件中无用的GetPorts()方法
- 简化web.WebPlugin接口:专注智能HTTP检测
- 理由:Web插件使用动态HTTP检测,预定义端口无价值

### 3. 统一插件命名规范
- 统一所有插件接口使用Name()方法(符合Go惯例)
- 消除GetName()与Name()不一致问题
- 简化适配器:不再需要方法名转换

## 技术改进

接口精简:
- local插件:GetName() + GetPorts() → Name()
- web插件:GetName() + GetPorts() → Name()
- services插件:GetName() → Name()(保留GetPorts(),业务必需)

代码减少:
- 删除23个无用GetPorts()方法
- 重命名52个Name()方法
- 简化3个插件接口定义

## 影响范围

修改文件:55个插件文件
代码变更:-155行 +61行(净减少94行)
功能影响:零破坏性,保持所有业务逻辑不变

这是基于业务需求分析的精准重构,消除真正多余的部分,
保持系统架构合理性和向后兼容性。
2025-08-26 20:38:39 +08:00

312 lines
7.1 KiB
Go

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) Name() 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(),
}
}
// 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() {
// 使用高效注册方式:直接传递端口信息,避免实例创建
RegisterPluginWithPorts("findnet", func() Plugin {
return NewFindNetPlugin()
}, []int{135})
}