feat: 增加端口识别,修复插件总超时

This commit is contained in:
ZacharyZcR
2025-01-03 16:29:54 +08:00
parent a603e13d3b
commit a42ee523b0
27 changed files with 19707 additions and 479 deletions
+877
View File
@@ -0,0 +1,877 @@
package Core
import (
_ "embed"
"encoding/hex"
"fmt"
"github.com/shadow1ng/fscan/Common"
"regexp"
"strconv"
"strings"
)
//go:embed nmap-service-probes.txt
var ProbeString string
var v VScan // 改为VScan类型而不是指针
type VScan struct {
Exclude string
AllProbes []Probe
UdpProbes []Probe
Probes []Probe
ProbesMapKName map[string]Probe
}
type Probe struct {
Name string // 探测器名称
Data string // 探测数据
Protocol string // 协议
Ports string // 端口范围
SSLPorts string // SSL端口范围
TotalWaitMS int // 总等待时间
TCPWrappedMS int // TCP包装等待时间
Rarity int // 稀有度
Fallback string // 回退探测器名称
Matchs *[]Match // 匹配规则列表
}
type Match struct {
IsSoft bool // 是否为软匹配
Service string // 服务名称
Pattern string // 匹配模式
VersionInfo string // 版本信息格式
FoundItems []string // 找到的项目
PatternCompiled *regexp.Regexp // 编译后的正则表达式
}
type Directive struct {
DirectiveName string
Flag string
Delimiter string
DirectiveStr string
}
type Extras struct {
VendorProduct string
Version string
Info string
Hostname string
OperatingSystem string
DeviceType string
CPE string
}
func init() {
Common.LogDebug("开始初始化全局变量")
v = VScan{} // 直接初始化VScan结构体
v.Init()
// 获取并检查 NULL 探测器
if nullProbe, ok := v.ProbesMapKName["NULL"]; ok {
Common.LogDebug(fmt.Sprintf("成功获取NULL探测器,Data长度: %d", len(nullProbe.Data)))
null = &nullProbe
} else {
Common.LogDebug("警告: 未找到NULL探测器")
}
// 获取并检查 GenericLines 探测器
if commonProbe, ok := v.ProbesMapKName["GenericLines"]; ok {
Common.LogDebug(fmt.Sprintf("成功获取GenericLines探测器,Data长度: %d", len(commonProbe.Data)))
common = &commonProbe
} else {
Common.LogDebug("警告: 未找到GenericLines探测器")
}
Common.LogDebug("全局变量初始化完成")
}
// 解析指令语法,返回指令结构
func (p *Probe) getDirectiveSyntax(data string) (directive Directive) {
Common.LogDebug("开始解析指令语法,输入数据: " + data)
directive = Directive{}
// 查找第一个空格的位置
blankIndex := strings.Index(data, " ")
if blankIndex == -1 {
Common.LogDebug("未找到空格分隔符")
return directive
}
// 解析各个字段
directiveName := data[:blankIndex]
Flag := data[blankIndex+1 : blankIndex+2]
delimiter := data[blankIndex+2 : blankIndex+3]
directiveStr := data[blankIndex+3:]
directive.DirectiveName = directiveName
directive.Flag = Flag
directive.Delimiter = delimiter
directive.DirectiveStr = directiveStr
Common.LogDebug(fmt.Sprintf("指令解析结果: 名称=%s, 标志=%s, 分隔符=%s, 内容=%s",
directiveName, Flag, delimiter, directiveStr))
return directive
}
// 解析探测器信息
func (p *Probe) parseProbeInfo(probeStr string) {
Common.LogDebug("开始解析探测器信息,输入字符串: " + probeStr)
// 提取协议和其他信息
proto := probeStr[:4]
other := probeStr[4:]
// 验证协议类型
if !(proto == "TCP " || proto == "UDP ") {
errMsg := "探测器协议必须是 TCP 或 UDP"
Common.LogDebug("错误: " + errMsg)
panic(errMsg)
}
// 验证其他信息不为空
if len(other) == 0 {
errMsg := "nmap-service-probes - 探测器名称无效"
Common.LogDebug("错误: " + errMsg)
panic(errMsg)
}
// 解析指令
directive := p.getDirectiveSyntax(other)
// 设置探测器属性
p.Name = directive.DirectiveName
p.Data = strings.Split(directive.DirectiveStr, directive.Delimiter)[0]
p.Protocol = strings.ToLower(strings.TrimSpace(proto))
Common.LogDebug(fmt.Sprintf("探测器解析完成: 名称=%s, 数据=%s, 协议=%s",
p.Name, p.Data, p.Protocol))
}
// 从字符串解析探测器信息
func (p *Probe) fromString(data string) error {
Common.LogDebug("开始解析探测器字符串数据")
var err error
// 预处理数据
data = strings.TrimSpace(data)
lines := strings.Split(data, "\n")
if len(lines) == 0 {
return fmt.Errorf("输入数据为空")
}
probeStr := lines[0]
p.parseProbeInfo(probeStr)
// 解析匹配规则和其他配置
var matchs []Match
for _, line := range lines {
Common.LogDebug("处理行: " + line)
switch {
case strings.HasPrefix(line, "match "):
match, err := p.getMatch(line)
if err != nil {
Common.LogDebug("解析match失败: " + err.Error())
continue
}
matchs = append(matchs, match)
case strings.HasPrefix(line, "softmatch "):
softMatch, err := p.getSoftMatch(line)
if err != nil {
Common.LogDebug("解析softmatch失败: " + err.Error())
continue
}
matchs = append(matchs, softMatch)
case strings.HasPrefix(line, "ports "):
p.parsePorts(line)
case strings.HasPrefix(line, "sslports "):
p.parseSSLPorts(line)
case strings.HasPrefix(line, "totalwaitms "):
p.parseTotalWaitMS(line)
case strings.HasPrefix(line, "tcpwrappedms "):
p.parseTCPWrappedMS(line)
case strings.HasPrefix(line, "rarity "):
p.parseRarity(line)
case strings.HasPrefix(line, "fallback "):
p.parseFallback(line)
}
}
p.Matchs = &matchs
Common.LogDebug(fmt.Sprintf("解析完成,共有 %d 个匹配规则", len(matchs)))
return err
}
// 解析端口配置
func (p *Probe) parsePorts(data string) {
p.Ports = data[len("ports")+1:]
Common.LogDebug("解析端口: " + p.Ports)
}
// 解析SSL端口配置
func (p *Probe) parseSSLPorts(data string) {
p.SSLPorts = data[len("sslports")+1:]
Common.LogDebug("解析SSL端口: " + p.SSLPorts)
}
// 解析总等待时间
func (p *Probe) parseTotalWaitMS(data string) {
waitMS, err := strconv.Atoi(strings.TrimSpace(data[len("totalwaitms")+1:]))
if err != nil {
Common.LogDebug("解析总等待时间失败: " + err.Error())
return
}
p.TotalWaitMS = waitMS
Common.LogDebug(fmt.Sprintf("总等待时间: %d ms", waitMS))
}
// 解析TCP包装等待时间
func (p *Probe) parseTCPWrappedMS(data string) {
wrappedMS, err := strconv.Atoi(strings.TrimSpace(data[len("tcpwrappedms")+1:]))
if err != nil {
Common.LogDebug("解析TCP包装等待时间失败: " + err.Error())
return
}
p.TCPWrappedMS = wrappedMS
Common.LogDebug(fmt.Sprintf("TCP包装等待时间: %d ms", wrappedMS))
}
// 解析稀有度
func (p *Probe) parseRarity(data string) {
rarity, err := strconv.Atoi(strings.TrimSpace(data[len("rarity")+1:]))
if err != nil {
Common.LogDebug("解析稀有度失败: " + err.Error())
return
}
p.Rarity = rarity
Common.LogDebug(fmt.Sprintf("稀有度: %d", rarity))
}
// 解析回退配置
func (p *Probe) parseFallback(data string) {
p.Fallback = data[len("fallback")+1:]
Common.LogDebug("回退配置: " + p.Fallback)
}
// 判断是否为十六进制编码
func isHexCode(b []byte) bool {
matchRe := regexp.MustCompile(`\\x[0-9a-fA-F]{2}`)
return matchRe.Match(b)
}
// 判断是否为八进制编码
func isOctalCode(b []byte) bool {
matchRe := regexp.MustCompile(`\\[0-7]{1,3}`)
return matchRe.Match(b)
}
// 判断是否为结构化转义字符
func isStructCode(b []byte) bool {
matchRe := regexp.MustCompile(`\\[aftnrv]`)
return matchRe.Match(b)
}
// 判断是否为正则表达式特殊字符
func isReChar(n int64) bool {
reChars := `.*?+{}()^$|\`
for _, char := range reChars {
if n == int64(char) {
return true
}
}
return false
}
// 判断是否为其他转义序列
func isOtherEscapeCode(b []byte) bool {
matchRe := regexp.MustCompile(`\\[^\\]`)
return matchRe.Match(b)
}
// 从内容解析探测器规则
func (v *VScan) parseProbesFromContent(content string) {
Common.LogDebug("开始解析探测器规则文件内容")
var probes []Probe
var lines []string
// 过滤注释和空行
linesTemp := strings.Split(content, "\n")
for _, lineTemp := range linesTemp {
lineTemp = strings.TrimSpace(lineTemp)
if lineTemp == "" || strings.HasPrefix(lineTemp, "#") {
continue
}
lines = append(lines, lineTemp)
}
// 验证文件内容
if len(lines) == 0 {
errMsg := "读取nmap-service-probes文件失败: 内容为空"
Common.LogDebug("错误: " + errMsg)
panic(errMsg)
}
// 检查Exclude指令
excludeCount := 0
for _, line := range lines {
if strings.HasPrefix(line, "Exclude ") {
excludeCount++
}
if excludeCount > 1 {
errMsg := "nmap-service-probes文件中只允许有一个Exclude指令"
Common.LogDebug("错误: " + errMsg)
panic(errMsg)
}
}
// 验证第一行格式
firstLine := lines[0]
if !(strings.HasPrefix(firstLine, "Exclude ") || strings.HasPrefix(firstLine, "Probe ")) {
errMsg := "解析错误: 首行必须以\"Probe \"或\"Exclude \"开头"
Common.LogDebug("错误: " + errMsg)
panic(errMsg)
}
// 处理Exclude指令
if excludeCount == 1 {
v.Exclude = firstLine[len("Exclude")+1:]
lines = lines[1:]
Common.LogDebug("解析到Exclude规则: " + v.Exclude)
}
// 合并内容并分割探测器
content = "\n" + strings.Join(lines, "\n")
probeParts := strings.Split(content, "\nProbe")[1:]
// 解析每个探测器
for _, probePart := range probeParts {
probe := Probe{}
if err := probe.fromString(probePart); err != nil {
Common.LogDebug(fmt.Sprintf("解析探测器失败: %v", err))
continue
}
probes = append(probes, probe)
}
v.AllProbes = probes
Common.LogDebug(fmt.Sprintf("成功解析 %d 个探测器规则", len(probes)))
}
// 将探测器转换为名称映射
func (v *VScan) parseProbesToMapKName() {
Common.LogDebug("开始构建探测器名称映射")
v.ProbesMapKName = map[string]Probe{}
for _, probe := range v.AllProbes {
v.ProbesMapKName[probe.Name] = probe
Common.LogDebug("添加探测器映射: " + probe.Name)
}
}
// 设置使用的探测器
func (v *VScan) SetusedProbes() {
Common.LogDebug("开始设置要使用的探测器")
for _, probe := range v.AllProbes {
if strings.ToLower(probe.Protocol) == "tcp" {
if probe.Name == "SSLSessionReq" {
Common.LogDebug("跳过 SSLSessionReq 探测器")
continue
}
v.Probes = append(v.Probes, probe)
Common.LogDebug("添加TCP探测器: " + probe.Name)
// 特殊处理TLS会话请求
if probe.Name == "TLSSessionReq" {
sslProbe := v.ProbesMapKName["SSLSessionReq"]
v.Probes = append(v.Probes, sslProbe)
Common.LogDebug("为TLSSessionReq添加SSL探测器")
}
} else {
v.UdpProbes = append(v.UdpProbes, probe)
Common.LogDebug("添加UDP探测器: " + probe.Name)
}
}
Common.LogDebug(fmt.Sprintf("探测器设置完成,TCP: %d个, UDP: %d个",
len(v.Probes), len(v.UdpProbes)))
}
// 解析match指令获取匹配规则
func (p *Probe) getMatch(data string) (match Match, err error) {
Common.LogDebug("开始解析match指令:" + data)
match = Match{}
// 提取match文本并解析指令语法
matchText := data[len("match")+1:]
directive := p.getDirectiveSyntax(matchText)
// 分割文本获取pattern和版本信息
textSplited := strings.Split(directive.DirectiveStr, directive.Delimiter)
if len(textSplited) == 0 {
return match, fmt.Errorf("无效的match指令格式")
}
pattern := textSplited[0]
versionInfo := strings.Join(textSplited[1:], "")
// 解码并编译正则表达式
patternUnescaped, decodeErr := DecodePattern(pattern)
if decodeErr != nil {
Common.LogDebug("解码pattern失败: " + decodeErr.Error())
return match, decodeErr
}
patternUnescapedStr := string([]rune(string(patternUnescaped)))
patternCompiled, compileErr := regexp.Compile(patternUnescapedStr)
if compileErr != nil {
Common.LogDebug("编译正则表达式失败: " + compileErr.Error())
return match, compileErr
}
// 设置match对象属性
match.Service = directive.DirectiveName
match.Pattern = pattern
match.PatternCompiled = patternCompiled
match.VersionInfo = versionInfo
Common.LogDebug(fmt.Sprintf("解析match成功: 服务=%s, Pattern=%s",
match.Service, match.Pattern))
return match, nil
}
// 解析softmatch指令获取软匹配规则
func (p *Probe) getSoftMatch(data string) (softMatch Match, err error) {
Common.LogDebug("开始解析softmatch指令:" + data)
softMatch = Match{IsSoft: true}
// 提取softmatch文本并解析指令语法
matchText := data[len("softmatch")+1:]
directive := p.getDirectiveSyntax(matchText)
// 分割文本获取pattern和版本信息
textSplited := strings.Split(directive.DirectiveStr, directive.Delimiter)
if len(textSplited) == 0 {
return softMatch, fmt.Errorf("无效的softmatch指令格式")
}
pattern := textSplited[0]
versionInfo := strings.Join(textSplited[1:], "")
// 解码并编译正则表达式
patternUnescaped, decodeErr := DecodePattern(pattern)
if decodeErr != nil {
Common.LogDebug("解码pattern失败: " + decodeErr.Error())
return softMatch, decodeErr
}
patternUnescapedStr := string([]rune(string(patternUnescaped)))
patternCompiled, compileErr := regexp.Compile(patternUnescapedStr)
if compileErr != nil {
Common.LogDebug("编译正则表达式失败: " + compileErr.Error())
return softMatch, compileErr
}
// 设置softMatch对象属性
softMatch.Service = directive.DirectiveName
softMatch.Pattern = pattern
softMatch.PatternCompiled = patternCompiled
softMatch.VersionInfo = versionInfo
Common.LogDebug(fmt.Sprintf("解析softmatch成功: 服务=%s, Pattern=%s",
softMatch.Service, softMatch.Pattern))
return softMatch, nil
}
// 解码模式字符串,处理转义序列
func DecodePattern(s string) ([]byte, error) {
Common.LogDebug("开始解码pattern: " + s)
sByteOrigin := []byte(s)
// 处理十六进制、八进制和结构化转义序列
matchRe := regexp.MustCompile(`\\(x[0-9a-fA-F]{2}|[0-7]{1,3}|[aftnrv])`)
sByteDec := matchRe.ReplaceAllFunc(sByteOrigin, func(match []byte) (v []byte) {
var replace []byte
// 处理十六进制转义
if isHexCode(match) {
hexNum := match[2:]
byteNum, _ := strconv.ParseInt(string(hexNum), 16, 32)
if isReChar(byteNum) {
replace = []byte{'\\', uint8(byteNum)}
} else {
replace = []byte{uint8(byteNum)}
}
}
// 处理结构化转义字符
if isStructCode(match) {
structCodeMap := map[int][]byte{
97: []byte{0x07}, // \a 响铃
102: []byte{0x0c}, // \f 换页
116: []byte{0x09}, // \t 制表符
110: []byte{0x0a}, // \n 换行
114: []byte{0x0d}, // \r 回车
118: []byte{0x0b}, // \v 垂直制表符
}
replace = structCodeMap[int(match[1])]
}
// 处理八进制转义
if isOctalCode(match) {
octalNum := match[2:]
byteNum, _ := strconv.ParseInt(string(octalNum), 8, 32)
replace = []byte{uint8(byteNum)}
}
return replace
})
// 处理其他转义序列
matchRe2 := regexp.MustCompile(`\\([^\\])`)
sByteDec2 := matchRe2.ReplaceAllFunc(sByteDec, func(match []byte) (v []byte) {
if isOtherEscapeCode(match) {
return match
}
return match
})
Common.LogDebug("pattern解码完成")
return sByteDec2, nil
}
// ProbesRarity 用于按稀有度排序的探测器切片
type ProbesRarity []Probe
// Len 返回切片长度,实现 sort.Interface 接口
func (ps ProbesRarity) Len() int {
return len(ps)
}
// Swap 交换切片中的两个元素,实现 sort.Interface 接口
func (ps ProbesRarity) Swap(i, j int) {
ps[i], ps[j] = ps[j], ps[i]
}
// Less 比较函数,按稀有度升序排序,实现 sort.Interface 接口
func (ps ProbesRarity) Less(i, j int) bool {
return ps[i].Rarity < ps[j].Rarity
}
// Target 定义目标结构体
type Target struct {
IP string // 目标IP地址
Port int // 目标端口
Protocol string // 协议类型
}
// ContainsPort 检查指定端口是否在探测器的端口范围内
func (p *Probe) ContainsPort(testPort int) bool {
Common.LogDebug(fmt.Sprintf("检查端口 %d 是否在探测器端口范围内: %s", testPort, p.Ports))
// 检查单个端口
ports := strings.Split(p.Ports, ",")
for _, port := range ports {
port = strings.TrimSpace(port)
cmpPort, err := strconv.Atoi(port)
if err == nil && testPort == cmpPort {
Common.LogDebug(fmt.Sprintf("端口 %d 匹配单个端口", testPort))
return true
}
}
// 检查端口范围
for _, port := range ports {
port = strings.TrimSpace(port)
if strings.Contains(port, "-") {
portRange := strings.Split(port, "-")
if len(portRange) != 2 {
Common.LogDebug("无效的端口范围格式: " + port)
continue
}
start, err1 := strconv.Atoi(strings.TrimSpace(portRange[0]))
end, err2 := strconv.Atoi(strings.TrimSpace(portRange[1]))
if err1 != nil || err2 != nil {
Common.LogDebug(fmt.Sprintf("解析端口范围失败: %s", port))
continue
}
if testPort >= start && testPort <= end {
Common.LogDebug(fmt.Sprintf("端口 %d 在范围 %d-%d 内", testPort, start, end))
return true
}
}
}
Common.LogDebug(fmt.Sprintf("端口 %d 不在探测器端口范围内", testPort))
return false
}
// MatchPattern 使用正则表达式匹配响应内容
func (m *Match) MatchPattern(response []byte) bool {
// 将响应转换为字符串并进行匹配
responseStr := string([]rune(string(response)))
foundItems := m.PatternCompiled.FindStringSubmatch(responseStr)
if len(foundItems) > 0 {
m.FoundItems = foundItems
Common.LogDebug(fmt.Sprintf("匹配成功,找到 %d 个匹配项", len(foundItems)))
return true
}
return false
}
// ParseVersionInfo 解析版本信息并返回额外信息结构
func (m *Match) ParseVersionInfo(response []byte) Extras {
Common.LogDebug("开始解析版本信息")
var extras = Extras{}
// 替换版本信息中的占位符
foundItems := m.FoundItems[1:] // 跳过第一个完整匹配项
versionInfo := m.VersionInfo
for index, value := range foundItems {
dollarName := "$" + strconv.Itoa(index+1)
versionInfo = strings.Replace(versionInfo, dollarName, value, -1)
}
Common.LogDebug("替换后的版本信息: " + versionInfo)
// 定义解析函数
parseField := func(field, pattern string) string {
patterns := []string{
pattern + `/([^/]*)/`, // 斜线分隔
pattern + `\|([^|]*)\|`, // 竖线分隔
}
for _, p := range patterns {
if strings.Contains(versionInfo, pattern) {
regex := regexp.MustCompile(p)
if matches := regex.FindStringSubmatch(versionInfo); len(matches) > 1 {
Common.LogDebug(fmt.Sprintf("解析到%s: %s", field, matches[1]))
return matches[1]
}
}
}
return ""
}
// 解析各个字段
extras.VendorProduct = parseField("厂商产品", " p")
extras.Version = parseField("版本", " v")
extras.Info = parseField("信息", " i")
extras.Hostname = parseField("主机名", " h")
extras.OperatingSystem = parseField("操作系统", " o")
extras.DeviceType = parseField("设备类型", " d")
// 特殊处理CPE
if strings.Contains(versionInfo, " cpe:/") || strings.Contains(versionInfo, " cpe:|") {
cpePatterns := []string{`cpe:/([^/]*)`, `cpe:\|([^|]*)`}
for _, pattern := range cpePatterns {
regex := regexp.MustCompile(pattern)
if cpeName := regex.FindStringSubmatch(versionInfo); len(cpeName) > 0 {
if len(cpeName) > 1 {
extras.CPE = cpeName[1]
} else {
extras.CPE = cpeName[0]
}
Common.LogDebug("解析到CPE: " + extras.CPE)
break
}
}
}
return extras
}
// ToMap 将 Extras 转换为 map[string]string
func (e *Extras) ToMap() map[string]string {
Common.LogDebug("开始转换Extras为Map")
result := make(map[string]string)
// 定义字段映射
fields := map[string]string{
"vendor_product": e.VendorProduct,
"version": e.Version,
"info": e.Info,
"hostname": e.Hostname,
"os": e.OperatingSystem,
"device_type": e.DeviceType,
"cpe": e.CPE,
}
// 添加非空字段到结果map
for key, value := range fields {
if value != "" {
result[key] = value
Common.LogDebug(fmt.Sprintf("添加字段 %s: %s", key, value))
}
}
Common.LogDebug(fmt.Sprintf("转换完成,共有 %d 个字段", len(result)))
return result
}
func DecodeData(s string) ([]byte, error) {
if len(s) == 0 {
Common.LogDebug("输入数据为空")
return nil, fmt.Errorf("empty input")
}
Common.LogDebug(fmt.Sprintf("开始解码数据,长度: %d, 内容: %q", len(s), s))
sByteOrigin := []byte(s)
// 处理十六进制、八进制和结构化转义序列
matchRe := regexp.MustCompile(`\\(x[0-9a-fA-F]{2}|[0-7]{1,3}|[aftnrv])`)
sByteDec := matchRe.ReplaceAllFunc(sByteOrigin, func(match []byte) []byte {
// 处理十六进制转义
if isHexCode(match) {
hexNum := match[2:]
byteNum, err := strconv.ParseInt(string(hexNum), 16, 32)
if err != nil {
return match
}
return []byte{uint8(byteNum)}
}
// 处理结构化转义字符
if isStructCode(match) {
structCodeMap := map[int][]byte{
97: []byte{0x07}, // \a 响铃
102: []byte{0x0c}, // \f 换页
116: []byte{0x09}, // \t 制表符
110: []byte{0x0a}, // \n 换行
114: []byte{0x0d}, // \r 回车
118: []byte{0x0b}, // \v 垂直制表符
}
if replace, ok := structCodeMap[int(match[1])]; ok {
return replace
}
return match
}
// 处理八进制转义
if isOctalCode(match) {
octalNum := match[2:]
byteNum, err := strconv.ParseInt(string(octalNum), 8, 32)
if err != nil {
return match
}
return []byte{uint8(byteNum)}
}
Common.LogDebug(fmt.Sprintf("无法识别的转义序列: %s", string(match)))
return match
})
// 处理其他转义序列
matchRe2 := regexp.MustCompile(`\\([^\\])`)
sByteDec2 := matchRe2.ReplaceAllFunc(sByteDec, func(match []byte) []byte {
if len(match) < 2 {
return match
}
if isOtherEscapeCode(match) {
return []byte{match[1]}
}
return match
})
if len(sByteDec2) == 0 {
Common.LogDebug("解码后数据为空")
return nil, fmt.Errorf("decoded data is empty")
}
Common.LogDebug(fmt.Sprintf("解码完成,结果长度: %d, 内容: %x", len(sByteDec2), sByteDec2))
return sByteDec2, nil
}
// GetAddress 获取目标的完整地址(IP:端口)
func (t *Target) GetAddress() string {
addr := t.IP + ":" + strconv.Itoa(t.Port)
Common.LogDebug("获取目标地址: " + addr)
return addr
}
// trimBanner 处理和清理横幅数据
func trimBanner(buf []byte) string {
Common.LogDebug("开始处理横幅数据")
bufStr := string(buf)
// 特殊处理SMB协议
if strings.Contains(bufStr, "SMB") {
banner := hex.EncodeToString(buf)
if len(banner) > 0xa+6 && banner[0xa:0xa+6] == "534d42" { // "SMB" in hex
Common.LogDebug("检测到SMB协议数据")
plain := banner[0xa2:]
data, err := hex.DecodeString(plain)
if err != nil {
Common.LogDebug("SMB数据解码失败: " + err.Error())
return bufStr
}
// 解析domain
var domain string
var index int
for i, s := range data {
if s != 0 {
domain += string(s)
} else if i+1 < len(data) && data[i+1] == 0 {
index = i + 2
break
}
}
// 解析hostname
var hostname string
remainData := data[index:]
for i, h := range remainData {
if h != 0 {
hostname += string(h)
}
if i+1 < len(remainData) && remainData[i+1] == 0 {
break
}
}
smbBanner := fmt.Sprintf("hostname: %s domain: %s", hostname, domain)
Common.LogDebug("SMB横幅: " + smbBanner)
return smbBanner
}
}
// 处理常规数据
var src string
for _, ch := range bufStr {
if ch > 32 && ch < 125 {
src += string(ch)
} else {
src += " "
}
}
// 清理多余空白
re := regexp.MustCompile(`\s{2,}`)
src = re.ReplaceAllString(src, ".")
result := strings.TrimSpace(src)
Common.LogDebug("处理后的横幅: " + result)
return result
}
// Init 初始化VScan对象
func (v *VScan) Init() {
Common.LogDebug("开始初始化VScan")
v.parseProbesFromContent(ProbeString)
v.parseProbesToMapKName()
v.SetusedProbes()
Common.LogDebug("VScan初始化完成")
}
+472
View File
@@ -0,0 +1,472 @@
package Core
import (
"fmt"
"github.com/shadow1ng/fscan/Common"
"io"
"net"
"strings"
"time"
)
// 服务信息结构
type ServiceInfo struct {
Name string // 服务名称
Banner string // 服务横幅
Version string // 版本信息
Extras map[string]string // 额外信息
}
// Result 结构体
type Result struct {
Service Service
Banner string
Extras map[string]string
Send []byte // 发送的数据
Recv []byte // 接收到的数据
}
type Service struct {
Name string
Extras map[string]string
}
// 扫描器相关结构
type Info struct {
Address string
Port int
Conn net.Conn
Result Result
Found bool
}
type PortInfoScanner struct {
Address string
Port int
Conn net.Conn
Timeout time.Duration
info *Info
}
// 预定义探测器
var (
null = new(Probe)
common = new(Probe)
)
// NewPortInfoScanner 创建新的端口服务识别器
func NewPortInfoScanner(addr string, port int, conn net.Conn, timeout time.Duration) *PortInfoScanner {
return &PortInfoScanner{
Address: addr,
Port: port,
Conn: conn,
Timeout: timeout,
info: &Info{
Address: addr,
Port: port,
Conn: conn,
Result: Result{
Service: Service{},
},
},
}
}
// Identify 识别端口服务
func (s *PortInfoScanner) Identify() (*ServiceInfo, error) {
Common.LogDebug(fmt.Sprintf("开始识别服务 %s:%d", s.Address, s.Port))
s.info.PortInfo()
// 转换识别结果
serviceInfo := &ServiceInfo{
Name: s.info.Result.Service.Name,
Banner: s.info.Result.Banner,
Version: s.info.Result.Service.Extras["version"],
Extras: make(map[string]string),
}
// 复制额外信息
for k, v := range s.info.Result.Service.Extras {
serviceInfo.Extras[k] = v
}
Common.LogDebug(fmt.Sprintf("服务识别完成 %s:%d => %s", s.Address, s.Port, serviceInfo.Name))
return serviceInfo, nil
}
// PortInfo 用于获取端口服务信息
func (i *Info) PortInfo() {
// 首次尝试读取响应
if response, err := i.Read(); err == nil && len(response) > 0 {
Common.LogDebug(fmt.Sprintf("收到初始响应: %d 字节", len(response)))
// 依次使用 null 和 common 探测器检查响应
Common.LogDebug("尝试使用基础探测器(null/common)检查响应")
if i.tryProbes(response, []*Probe{null, common}) {
Common.LogDebug("基础探测器匹配成功")
return
}
Common.LogDebug("基础探测器未匹配")
} else if err != nil {
Common.LogDebug(fmt.Sprintf("读取初始响应失败: %v", err))
}
// 记录已使用的探测器
usedProbes := make(map[string]struct{})
// 处理特定端口映射的探测
Common.LogDebug(fmt.Sprintf("尝试使用端口 %d 的专用探测器", i.Port))
if i.processPortMapProbes(usedProbes) {
Common.LogDebug("端口专用探测器匹配成功")
return
}
Common.LogDebug("端口专用探测器未匹配")
// 使用默认探测器进行检测
Common.LogDebug("尝试使用默认探测器列表")
if i.processDefaultProbes(usedProbes) {
Common.LogDebug("默认探测器匹配成功")
return
}
Common.LogDebug("默认探测器未匹配")
// 如果未能识别服务,标记为未知
if strings.TrimSpace(i.Result.Service.Name) == "" {
Common.LogDebug("未识别出服务,标记为 unknown")
i.Result.Service.Name = "unknown"
}
}
// tryProbes 尝试使用给定的探测器列表检查响应
func (i *Info) tryProbes(response []byte, probes []*Probe) bool {
for _, probe := range probes {
Common.LogDebug(fmt.Sprintf("尝试探测器: %s", probe.Name))
i.GetInfo(response, probe)
if i.Found {
Common.LogDebug(fmt.Sprintf("探测器 %s 匹配成功", probe.Name))
return true
}
}
return false
}
// processPortMapProbes 处理端口映射中的探测器
func (i *Info) processPortMapProbes(usedProbes map[string]struct{}) bool {
if len(Common.PortMap[i.Port]) == 0 {
Common.LogDebug(fmt.Sprintf("端口 %d 没有专用探测器", i.Port))
return false
}
for _, name := range Common.PortMap[i.Port] {
Common.LogDebug(fmt.Sprintf("尝试端口专用探测器: %s", name))
usedProbes[name] = struct{}{}
probe := v.ProbesMapKName[name]
// 解码探测数据
probeData, err := DecodeData(probe.Data)
if err != nil || len(probeData) == 0 {
Common.LogDebug(fmt.Sprintf("探测器 %s 数据解码失败", name))
continue
}
// 建立连接获取响应
Common.LogDebug(fmt.Sprintf("发送探测数据: %d 字节", len(probeData)))
if response := i.Connect(probeData); len(response) > 0 {
Common.LogDebug(fmt.Sprintf("收到响应: %d 字节", len(response)))
// 使用当前探测器检查响应
i.GetInfo(response, &probe)
if i.Found {
return true
}
// 根据探测器类型进行额外检查
switch name {
case "GenericLines":
if i.tryProbes(response, []*Probe{null}) {
return true
}
case "NULL":
continue
default:
if i.tryProbes(response, []*Probe{common}) {
return true
}
}
}
}
return false
}
// processDefaultProbes 处理默认探测器
func (i *Info) processDefaultProbes(usedProbes map[string]struct{}) bool {
failCount := 0
const maxFailures = 10
for _, name := range Common.DefaultMap {
// 跳过已使用的探测器
if _, used := usedProbes[name]; used {
continue
}
probe := v.ProbesMapKName[name]
probeData, err := DecodeData(probe.Data)
if err != nil || len(probeData) == 0 {
continue
}
// 建立连接获取响应
response := i.Connect(probeData)
if len(response) == 0 {
failCount++
if failCount > maxFailures {
return false
}
continue
}
// 使用当前探测器检查响应
i.GetInfo(response, &probe)
if i.Found {
return true
}
// 根据探测器类型进行额外检查
switch name {
case "GenericLines":
if i.tryProbes(response, []*Probe{null}) {
return true
}
case "NULL":
continue
default:
if i.tryProbes(response, []*Probe{common}) {
return true
}
}
// 尝试使用端口映射中的其他探测器
if len(Common.PortMap[i.Port]) > 0 {
for _, mappedName := range Common.PortMap[i.Port] {
usedProbes[mappedName] = struct{}{}
mappedProbe := v.ProbesMapKName[mappedName]
i.GetInfo(response, &mappedProbe)
if i.Found {
return true
}
}
}
}
return false
}
// GetInfo 分析响应数据并获取服务信息
func (i *Info) GetInfo(response []byte, probe *Probe) {
Common.LogDebug(fmt.Sprintf("开始分析响应数据,长度: %d", len(response)))
// 响应数据长度检查
if len(response) <= 0 {
Common.LogDebug("响应数据为空")
return
}
result := &i.Result
var (
softMatch Match
softFound bool
)
Common.LogDebug(fmt.Sprintf("处理探测器 %s 的主要匹配规则", probe.Name))
// 处理主要匹配规则
if matched, match := i.processMatches(response, probe.Matchs); matched {
Common.LogDebug("找到硬匹配")
return
} else if match != nil {
Common.LogDebug("找到软匹配")
softFound = true
softMatch = *match
}
// 处理回退匹配规则
if probe.Fallback != "" {
Common.LogDebug(fmt.Sprintf("尝试回退匹配: %s", probe.Fallback))
if fbProbe, ok := v.ProbesMapKName[probe.Fallback]; ok {
if matched, match := i.processMatches(response, fbProbe.Matchs); matched {
Common.LogDebug("回退匹配成功")
return
} else if match != nil {
Common.LogDebug("找到回退软匹配")
softFound = true
softMatch = *match
}
}
}
// 处理未找到匹配的情况
if !i.Found {
Common.LogDebug("未找到硬匹配,处理未匹配情况")
i.handleNoMatch(response, result, softFound, softMatch)
}
}
// processMatches 处理匹配规则集
func (i *Info) processMatches(response []byte, matches *[]Match) (bool, *Match) {
Common.LogDebug(fmt.Sprintf("开始处理匹配规则,共 %d 条", len(*matches)))
var softMatch *Match
for _, match := range *matches {
if !match.MatchPattern(response) {
continue
}
if !match.IsSoft {
Common.LogDebug(fmt.Sprintf("找到硬匹配: %s", match.Service))
i.handleHardMatch(response, &match)
return true, nil
} else if softMatch == nil {
Common.LogDebug(fmt.Sprintf("找到软匹配: %s", match.Service))
tmpMatch := match
softMatch = &tmpMatch
}
}
return false, softMatch
}
// handleHardMatch 处理硬匹配结果
func (i *Info) handleHardMatch(response []byte, match *Match) {
Common.LogDebug(fmt.Sprintf("处理硬匹配结果: %s", match.Service))
result := &i.Result
extras := match.ParseVersionInfo(response)
extrasMap := extras.ToMap()
result.Service.Name = match.Service
result.Extras = extrasMap
result.Banner = trimBanner(response)
result.Service.Extras = extrasMap
// 特殊处理 microsoft-ds 服务
if result.Service.Name == "microsoft-ds" {
Common.LogDebug("特殊处理 microsoft-ds 服务")
result.Service.Extras["hostname"] = result.Banner
}
i.Found = true
Common.LogDebug(fmt.Sprintf("服务识别结果: %s, Banner: %s", result.Service.Name, result.Banner))
}
// handleNoMatch 处理未找到匹配的情况
func (i *Info) handleNoMatch(response []byte, result *Result, softFound bool, softMatch Match) {
Common.LogDebug("处理未匹配情况")
result.Banner = trimBanner(response)
if !softFound {
// 尝试识别 HTTP 服务
if strings.Contains(result.Banner, "HTTP/") ||
strings.Contains(result.Banner, "html") {
Common.LogDebug("识别为HTTP服务")
result.Service.Name = "http"
} else {
Common.LogDebug("未知服务")
result.Service.Name = "unknown"
}
} else {
Common.LogDebug("使用软匹配结果")
extras := softMatch.ParseVersionInfo(response)
result.Service.Extras = extras.ToMap()
result.Service.Name = softMatch.Service
i.Found = true
Common.LogDebug(fmt.Sprintf("软匹配服务: %s", result.Service.Name))
}
}
// Connect 发送数据并获取响应
func (i *Info) Connect(msg []byte) []byte {
i.Write(msg)
reply, _ := i.Read()
return reply
}
const WrTimeout = 5 // 默认超时时间(秒)
// Write 写入数据到连接
func (i *Info) Write(msg []byte) error {
if i.Conn == nil {
return nil
}
// 设置写入超时
i.Conn.SetWriteDeadline(time.Now().Add(time.Second * time.Duration(WrTimeout)))
// 写入数据
_, err := i.Conn.Write(msg)
if err != nil && strings.Contains(err.Error(), "close") {
i.Conn.Close()
// 重试连接
i.Conn, err = net.DialTimeout("tcp4", fmt.Sprintf("%s:%d", i.Address, i.Port), time.Duration(6)*time.Second)
if err == nil {
i.Conn.SetWriteDeadline(time.Now().Add(time.Second * time.Duration(WrTimeout)))
_, err = i.Conn.Write(msg)
}
}
// 记录发送的数据
if err == nil {
i.Result.Send = msg
}
return err
}
// Read 从连接读取响应
func (i *Info) Read() ([]byte, error) {
if i.Conn == nil {
return nil, nil
}
// 设置读取超时
i.Conn.SetReadDeadline(time.Now().Add(time.Second * time.Duration(WrTimeout)))
// 读取数据
result, err := readFromConn(i.Conn)
if err != nil && strings.Contains(err.Error(), "close") {
// 连接关闭的错误处理
return result, err
}
// 记录接收到的数据
if len(result) > 0 {
i.Result.Recv = result
}
return result, err
}
// readFromConn 从连接读取数据的辅助函数
func readFromConn(conn net.Conn) ([]byte, error) {
size := 2 * 1024
var result []byte
for {
buf := make([]byte, size)
count, err := conn.Read(buf)
if count > 0 {
result = append(result, buf[:count]...)
}
if err != nil {
if len(result) > 0 {
return result, nil
}
if err == io.EOF {
return result, nil
}
return result, err
}
if count < size {
return result, nil
}
}
}
+72 -14
View File
@@ -11,6 +11,7 @@ import (
"net"
"runtime"
"sort"
"strings"
"sync"
"time"
)
@@ -21,15 +22,23 @@ type Addr struct {
port int // 端口号
}
// ScanResult 扫描结果
type ScanResult struct {
Address string // IP地址
Port int // 端口号
Service *ServiceInfo // 服务信息
}
func PortScan(hostslist []string, ports string, timeout int64) []string {
var AliveAddress []string
var results []ScanResult
var aliveAddrs []string // 新增:存储活跃地址
var mu sync.Mutex
// 解析端口列表
probePorts := Common.ParsePort(ports)
if len(probePorts) == 0 {
Common.LogError(fmt.Sprintf("端口格式错误: %s", ports))
return AliveAddress
return aliveAddrs
}
// 排除指定端口
@@ -38,7 +47,7 @@ func PortScan(hostslist []string, ports string, timeout int64) []string {
// 创建通道
workers := Common.ThreadNum
addrs := make(chan Addr, 100)
results := make(chan string, 100)
scanResults := make(chan ScanResult, 100)
var wg sync.WaitGroup
var workerWg sync.WaitGroup
@@ -48,7 +57,7 @@ func PortScan(hostslist []string, ports string, timeout int64) []string {
go func() {
defer workerWg.Done()
for addr := range addrs {
PortConnect(addr, results, timeout, &wg)
PortConnect(addr, scanResults, timeout, &wg)
}
}()
}
@@ -58,9 +67,12 @@ func PortScan(hostslist []string, ports string, timeout int64) []string {
resultWg.Add(1)
go func() {
defer resultWg.Done()
for result := range results {
for result := range scanResults {
mu.Lock()
AliveAddress = append(AliveAddress, result)
results = append(results, result)
// 构造活跃地址字符串
aliveAddr := fmt.Sprintf("%s:%d", result.Address, result.Port)
aliveAddrs = append(aliveAddrs, aliveAddr)
mu.Unlock()
}
}()
@@ -76,24 +88,23 @@ func PortScan(hostslist []string, ports string, timeout int64) []string {
close(addrs)
workerWg.Wait()
wg.Wait()
close(results)
close(scanResults)
resultWg.Wait()
return AliveAddress
return aliveAddrs
}
func PortConnect(addr Addr, respondingHosts chan<- string, timeout int64, wg *sync.WaitGroup) {
func PortConnect(addr Addr, results chan<- ScanResult, timeout int64, wg *sync.WaitGroup) {
defer wg.Done()
var isOpen bool
var err error
var conn net.Conn
if Common.UseSynScan {
// SYN扫描
isOpen, err = SynScan(addr.ip, addr.port, timeout)
} else {
// 标准TCP扫描
conn, err := Common.WrapperTcpWithTimeout("tcp4",
conn, err = Common.WrapperTcpWithTimeout("tcp4",
fmt.Sprintf("%s:%v", addr.ip, addr.port),
time.Duration(timeout)*time.Second)
if err == nil {
@@ -106,10 +117,57 @@ func PortConnect(addr Addr, respondingHosts chan<- string, timeout int64, wg *sy
return
}
// 记录开放端口
address := fmt.Sprintf("%s:%d", addr.ip, addr.port)
Common.LogSuccess(fmt.Sprintf("端口开放 %s", address))
respondingHosts <- address
// 创建扫描结果
result := ScanResult{
Address: addr.ip,
Port: addr.port,
}
// 进行服务识别
if conn != nil {
scanner := NewPortInfoScanner(addr.ip, addr.port, conn, time.Duration(timeout)*time.Second)
if serviceInfo, err := scanner.Identify(); err == nil {
result.Service = serviceInfo
// 打印服务识别信息
var logMsg strings.Builder
logMsg.WriteString(fmt.Sprintf("服务识别 %s => ", address))
// 添加服务名称
if serviceInfo.Name != "unknown" {
logMsg.WriteString(fmt.Sprintf("[%s]", serviceInfo.Name))
}
// 添加版本信息
if serviceInfo.Version != "" {
logMsg.WriteString(fmt.Sprintf(" 版本:%s", serviceInfo.Version))
}
// 添加其他有用的信息
if v, ok := serviceInfo.Extras["vendor_product"]; ok && v != "" {
logMsg.WriteString(fmt.Sprintf(" 产品:%s", v))
}
if v, ok := serviceInfo.Extras["os"]; ok && v != "" {
logMsg.WriteString(fmt.Sprintf(" 系统:%s", v))
}
if v, ok := serviceInfo.Extras["info"]; ok && v != "" {
logMsg.WriteString(fmt.Sprintf(" 信息:%s", v))
}
// 如果有Banner且长度合适,也输出
if len(serviceInfo.Banner) > 0 && len(serviceInfo.Banner) < 100 {
logMsg.WriteString(fmt.Sprintf(" Banner:[%s]", strings.TrimSpace(serviceInfo.Banner)))
}
Common.LogSuccess(logMsg.String())
}
}
// 发送结果
results <- result
}
// NoPortScan 生成端口列表(不进行扫描)
File diff suppressed because it is too large Load Diff