mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 05:56:46 +08:00
refactor: 重构PortFinger.go为模块化架构以提升代码可维护性
将原有的878行单一文件重构为多个专门化模块: - 类型定义模块:集中管理所有数据结构 - 扫描器核心:初始化和全局状态管理 - 编码工具:处理各种编码格式转换 - 探测器解析:解析nmap-service-probes格式 - 匹配引擎:模式匹配和服务识别 - 版本解析:服务版本信息提取 通过向后兼容层保持原有API接口不变,确保现有代码无需修改即可使用新架构
This commit is contained in:
parent
69a70fc577
commit
0a60d76f71
@ -1,877 +1,40 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
|
// 重新导出portfinger包的类型和函数,保持向后兼容性
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
"github.com/shadow1ng/fscan/core/portfinger"
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"github.com/shadow1ng/fscan/common"
|
"github.com/shadow1ng/fscan/common"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed nmap-service-probes.txt
|
// 重新导出类型定义
|
||||||
var ProbeString string
|
type VScan = portfinger.VScan
|
||||||
|
type Probe = portfinger.Probe
|
||||||
|
type Match = portfinger.Match
|
||||||
|
type Directive = portfinger.Directive
|
||||||
|
type Extras = portfinger.Extras
|
||||||
|
type Target = portfinger.Target
|
||||||
|
|
||||||
var v VScan // 改为VScan类型而不是指针
|
// 兼容原有的全局变量访问模式
|
||||||
|
var v *VScan
|
||||||
type VScan struct {
|
var null *Probe
|
||||||
Exclude string
|
var commonProbe *Probe
|
||||||
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() {
|
func init() {
|
||||||
common.LogDebug("开始初始化全局变量")
|
common.LogDebug("初始化PortFinger兼容层")
|
||||||
|
|
||||||
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 genericProbe, ok := v.ProbesMapKName["GenericLines"]; ok {
|
|
||||||
common.LogDebug(fmt.Sprintf("成功获取GenericLines探测器,Data长度: %d", len(genericProbe.Data)))
|
|
||||||
commonProbe = &genericProbe
|
|
||||||
} 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
|
// 初始化兼容性全局变量
|
||||||
|
v = portfinger.GetGlobalVScan()
|
||||||
|
null = portfinger.GetNullProbe()
|
||||||
|
commonProbe = portfinger.GetCommonProbe()
|
||||||
|
|
||||||
|
common.LogDebug("PortFinger兼容层初始化完成")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseVersionInfo 解析版本信息并返回额外信息结构
|
// 重新导出编码函数
|
||||||
func (m *Match) ParseVersionInfo(response []byte) Extras {
|
var DecodeData = portfinger.DecodeData
|
||||||
common.LogDebug("开始解析版本信息")
|
var DecodePattern = portfinger.DecodePattern
|
||||||
var extras = Extras{}
|
|
||||||
|
|
||||||
// 替换版本信息中的占位符
|
// 重新导出探测器字符串
|
||||||
foundItems := m.FoundItems[1:] // 跳过第一个完整匹配项
|
var ProbeString = portfinger.ProbeString
|
||||||
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初始化完成")
|
|
||||||
}
|
|
||||||
|
@ -3,6 +3,7 @@ package core
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/shadow1ng/fscan/common"
|
"github.com/shadow1ng/fscan/common"
|
||||||
|
"github.com/shadow1ng/fscan/core/portfinger"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
@ -50,11 +51,7 @@ type PortInfoScanner struct {
|
|||||||
info *Info // 探测上下文
|
info *Info // 探测上下文
|
||||||
}
|
}
|
||||||
|
|
||||||
// 预定义的基础探测器
|
// 预定义的基础探测器已在PortFinger.go中定义,这里不再重复定义
|
||||||
var (
|
|
||||||
null = new(Probe) // 空探测器,用于基本协议识别
|
|
||||||
commonProbe = new(Probe) // 通用探测器,用于常见服务识别
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewPortInfoScanner 创建新的端口服务识别器实例
|
// NewPortInfoScanner 创建新的端口服务识别器实例
|
||||||
func NewPortInfoScanner(addr string, port int, conn net.Conn, timeout time.Duration) *PortInfoScanner {
|
func NewPortInfoScanner(addr string, port int, conn net.Conn, timeout time.Duration) *PortInfoScanner {
|
||||||
@ -347,7 +344,7 @@ func (i *Info) handleHardMatch(response []byte, match *Match) {
|
|||||||
|
|
||||||
result.Service.Name = match.Service
|
result.Service.Name = match.Service
|
||||||
result.Extras = extrasMap
|
result.Extras = extrasMap
|
||||||
result.Banner = trimBanner(response)
|
result.Banner = portfinger.TrimBanner(string(response))
|
||||||
result.Service.Extras = extrasMap
|
result.Service.Extras = extrasMap
|
||||||
|
|
||||||
// 特殊处理 microsoft-ds 服务
|
// 特殊处理 microsoft-ds 服务
|
||||||
@ -363,7 +360,7 @@ func (i *Info) handleHardMatch(response []byte, match *Match) {
|
|||||||
// handleNoMatch 处理未找到匹配的情况
|
// handleNoMatch 处理未找到匹配的情况
|
||||||
func (i *Info) handleNoMatch(response []byte, result *Result, softFound bool, softMatch Match) {
|
func (i *Info) handleNoMatch(response []byte, result *Result, softFound bool, softMatch Match) {
|
||||||
common.LogDebug("处理未匹配情况")
|
common.LogDebug("处理未匹配情况")
|
||||||
result.Banner = trimBanner(response)
|
result.Banner = portfinger.TrimBanner(string(response))
|
||||||
|
|
||||||
if !softFound {
|
if !softFound {
|
||||||
// 尝试识别 HTTP 服务
|
// 尝试识别 HTTP 服务
|
||||||
|
105
Core/portfinger/encoding_utils.go
Normal file
105
Core/portfinger/encoding_utils.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
package portfinger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
// DecodePattern 解码匹配模式
|
||||||
|
func DecodePattern(s string) ([]byte, error) {
|
||||||
|
b := []byte(s)
|
||||||
|
var result []byte
|
||||||
|
|
||||||
|
for i := 0; i < len(b); {
|
||||||
|
if b[i] == '\\' && i+1 < len(b) {
|
||||||
|
// 处理转义序列
|
||||||
|
switch b[i+1] {
|
||||||
|
case 'x':
|
||||||
|
// 十六进制编码 \xNN
|
||||||
|
if i+3 < len(b) {
|
||||||
|
if hexStr := string(b[i+2:i+4]); isValidHex(hexStr) {
|
||||||
|
if decoded, err := hex.DecodeString(hexStr); err == nil {
|
||||||
|
result = append(result, decoded...)
|
||||||
|
i += 4
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'a':
|
||||||
|
result = append(result, '\a')
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
case 'f':
|
||||||
|
result = append(result, '\f')
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
case 't':
|
||||||
|
result = append(result, '\t')
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
case 'n':
|
||||||
|
result = append(result, '\n')
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
case 'r':
|
||||||
|
result = append(result, '\r')
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
case 'v':
|
||||||
|
result = append(result, '\v')
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
case '\\':
|
||||||
|
result = append(result, '\\')
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
// 八进制编码 \NNN
|
||||||
|
if i+1 < len(b) && b[i+1] >= '0' && b[i+1] <= '7' {
|
||||||
|
octalStr := ""
|
||||||
|
j := i + 1
|
||||||
|
for j < len(b) && j < i+4 && b[j] >= '0' && b[j] <= '7' {
|
||||||
|
octalStr += string(b[j])
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
if octal, err := strconv.ParseInt(octalStr, 8, 8); err == nil {
|
||||||
|
result = append(result, byte(octal))
|
||||||
|
i = j
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 普通字符
|
||||||
|
result = append(result, b[i])
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeData 解码探测数据
|
||||||
|
func DecodeData(s string) ([]byte, error) {
|
||||||
|
// 移除首尾的分隔符
|
||||||
|
if len(s) > 0 && (s[0] == '"' || s[0] == '\'') {
|
||||||
|
s = s[1:]
|
||||||
|
}
|
||||||
|
if len(s) > 0 && (s[len(s)-1] == '"' || s[len(s)-1] == '\'') {
|
||||||
|
s = s[:len(s)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return DecodePattern(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isValidHex 检查字符串是否为有效的十六进制
|
||||||
|
func isValidHex(s string) bool {
|
||||||
|
for _, c := range s {
|
||||||
|
if !((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(s) == 2
|
||||||
|
}
|
||||||
|
|
173
Core/portfinger/match_engine.go
Normal file
173
Core/portfinger/match_engine.go
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
package portfinger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/shadow1ng/fscan/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 解析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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
if m.PatternCompiled == nil {
|
||||||
|
common.LogDebug("警告: 匹配规则的正则表达式未编译")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
matched := m.PatternCompiled.Match(response)
|
||||||
|
if matched {
|
||||||
|
// 提取匹配到的子组
|
||||||
|
submatches := m.PatternCompiled.FindStringSubmatch(string(response))
|
||||||
|
if len(submatches) > 1 {
|
||||||
|
m.FoundItems = submatches[1:] // 排除完整匹配,只保留分组
|
||||||
|
common.LogDebug(fmt.Sprintf("模式匹配成功,提取到 %d 个分组", len(m.FoundItems)))
|
||||||
|
} else {
|
||||||
|
common.LogDebug("模式匹配成功,但没有分组匹配")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return matched
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPortMatch 检查探测器是否适用于指定端口
|
||||||
|
func (p *Probe) IsPortMatch(port int) bool {
|
||||||
|
// 如果没有指定端口范围,认为适用于所有端口
|
||||||
|
if p.Ports == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.containsPort(port)
|
||||||
|
}
|
||||||
|
|
16624
Core/portfinger/nmap-service-probes.txt
Normal file
16624
Core/portfinger/nmap-service-probes.txt
Normal file
File diff suppressed because it is too large
Load Diff
292
Core/portfinger/probe_parser.go
Normal file
292
Core/portfinger/probe_parser.go
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
package portfinger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/shadow1ng/fscan/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 解析指令语法,返回指令结构
|
||||||
|
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 (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)))
|
||||||
|
}
|
71
Core/portfinger/scanner_core.go
Normal file
71
Core/portfinger/scanner_core.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package portfinger
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/shadow1ng/fscan/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed nmap-service-probes.txt
|
||||||
|
var ProbeString string
|
||||||
|
|
||||||
|
var v VScan
|
||||||
|
var null *Probe
|
||||||
|
var commonProbe *Probe
|
||||||
|
|
||||||
|
// Init 初始化VScan对象
|
||||||
|
func (vs *VScan) Init() {
|
||||||
|
common.LogDebug("开始初始化VScan")
|
||||||
|
vs.parseProbesFromContent(ProbeString)
|
||||||
|
vs.parseProbesToMapKName()
|
||||||
|
vs.SetusedProbes()
|
||||||
|
common.LogDebug("VScan初始化完成")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// InitializeGlobalVScan 初始化全局VScan实例
|
||||||
|
func InitializeGlobalVScan() {
|
||||||
|
common.LogDebug("开始初始化全局变量")
|
||||||
|
|
||||||
|
v = 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 genericProbe, ok := v.ProbesMapKName["GenericLines"]; ok {
|
||||||
|
common.LogDebug(fmt.Sprintf("成功获取GenericLines探测器,Data长度: %d", len(genericProbe.Data)))
|
||||||
|
commonProbe = &genericProbe
|
||||||
|
} else {
|
||||||
|
common.LogDebug("警告: 未找到GenericLines探测器")
|
||||||
|
}
|
||||||
|
|
||||||
|
common.LogDebug("全局变量初始化完成")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGlobalVScan 获取全局VScan实例
|
||||||
|
func GetGlobalVScan() *VScan {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNullProbe 获取NULL探测器
|
||||||
|
func GetNullProbe() *Probe {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCommonProbe 获取通用探测器
|
||||||
|
func GetCommonProbe() *Probe {
|
||||||
|
return commonProbe
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
InitializeGlobalVScan()
|
||||||
|
}
|
67
Core/portfinger/types.go
Normal file
67
Core/portfinger/types.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package portfinger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VScan 主扫描器结构体
|
||||||
|
type VScan struct {
|
||||||
|
Exclude string
|
||||||
|
AllProbes []Probe
|
||||||
|
UdpProbes []Probe
|
||||||
|
Probes []Probe
|
||||||
|
ProbesMapKName map[string]Probe
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 // 匹配规则列表
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match 匹配规则结构体
|
||||||
|
type Match struct {
|
||||||
|
IsSoft bool // 是否为软匹配
|
||||||
|
Service string // 服务名称
|
||||||
|
Pattern string // 匹配模式
|
||||||
|
VersionInfo string // 版本信息格式
|
||||||
|
FoundItems []string // 找到的项目
|
||||||
|
PatternCompiled *regexp.Regexp // 编译后的正则表达式
|
||||||
|
}
|
||||||
|
|
||||||
|
// Directive 指令结构体
|
||||||
|
type Directive struct {
|
||||||
|
DirectiveName string
|
||||||
|
Flag string
|
||||||
|
Delimiter string
|
||||||
|
DirectiveStr string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extras 额外信息结构体
|
||||||
|
type Extras struct {
|
||||||
|
VendorProduct string
|
||||||
|
Version string
|
||||||
|
Info string
|
||||||
|
Hostname string
|
||||||
|
OperatingSystem string
|
||||||
|
DeviceType string
|
||||||
|
CPE string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Target 目标结构体
|
||||||
|
type Target struct {
|
||||||
|
Host string
|
||||||
|
Port int
|
||||||
|
Timeout int
|
||||||
|
}
|
||||||
|
|
131
Core/portfinger/version_parser.go
Normal file
131
Core/portfinger/version_parser.go
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
package portfinger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/shadow1ng/fscan/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseVersionInfo 解析版本信息并返回额外信息结构
|
||||||
|
func (m *Match) ParseVersionInfo(response []byte) Extras {
|
||||||
|
common.LogDebug("开始解析版本信息")
|
||||||
|
var extras = Extras{}
|
||||||
|
|
||||||
|
// 确保有匹配项
|
||||||
|
if len(m.FoundItems) == 0 {
|
||||||
|
common.LogDebug("没有匹配项,无法解析版本信息")
|
||||||
|
return extras
|
||||||
|
}
|
||||||
|
|
||||||
|
// 替换版本信息中的占位符
|
||||||
|
foundItems := m.FoundItems
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrimBanner 清理横幅数据,移除不可打印字符
|
||||||
|
func TrimBanner(banner string) string {
|
||||||
|
// 移除开头和结尾的空白字符
|
||||||
|
banner = strings.TrimSpace(banner)
|
||||||
|
|
||||||
|
// 移除控制字符,但保留换行符和制表符
|
||||||
|
var result strings.Builder
|
||||||
|
for _, r := range banner {
|
||||||
|
if r >= 32 && r <= 126 { // 可打印ASCII字符
|
||||||
|
result.WriteRune(r)
|
||||||
|
} else if r == '\n' || r == '\t' { // 保留换行符和制表符
|
||||||
|
result.WriteRune(r)
|
||||||
|
} else {
|
||||||
|
result.WriteRune(' ') // 其他控制字符替换为空格
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 压缩多个连续空格为单个空格
|
||||||
|
resultStr := result.String()
|
||||||
|
spaceRe := regexp.MustCompile(`\s+`)
|
||||||
|
resultStr = spaceRe.ReplaceAllString(resultStr, " ")
|
||||||
|
|
||||||
|
return strings.TrimSpace(resultStr)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user