mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-11-05 10:45:27 +00:00
878 lines
24 KiB
Go
878 lines
24 KiB
Go
|
|
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初始化完成")
|
|||
|
|
}
|