diff --git a/Core/PortFinger.go b/Core/PortFinger.go
index 5970b3c..19959b8 100644
--- a/Core/PortFinger.go
+++ b/Core/PortFinger.go
@@ -1,877 +1,40 @@
package core
+// 重新导出portfinger包的类型和函数,保持向后兼容性
+
import (
- _ "embed"
- "encoding/hex"
- "fmt"
+ "github.com/shadow1ng/fscan/core/portfinger"
"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类型而不是指针
-
-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
-}
+// 兼容原有的全局变量访问模式
+var v *VScan
+var null *Probe
+var commonProbe *Probe
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 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
- }
+ common.LogDebug("初始化PortFinger兼容层")
- return false
+ // 初始化兼容性全局变量
+ v = portfinger.GetGlobalVScan()
+ null = portfinger.GetNullProbe()
+ commonProbe = portfinger.GetCommonProbe()
+
+ common.LogDebug("PortFinger兼容层初始化完成")
}
-// ParseVersionInfo 解析版本信息并返回额外信息结构
-func (m *Match) ParseVersionInfo(response []byte) Extras {
- common.LogDebug("开始解析版本信息")
- var extras = Extras{}
+// 重新导出编码函数
+var DecodeData = portfinger.DecodeData
+var DecodePattern = portfinger.DecodePattern
- // 替换版本信息中的占位符
- 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)
+// 重新导出探测器字符串
+var ProbeString = portfinger.ProbeString
- // 定义解析函数
- 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初始化完成")
-}
diff --git a/Core/PortInfo.go b/Core/PortInfo.go
index 4ca2191..d65f6f1 100644
--- a/Core/PortInfo.go
+++ b/Core/PortInfo.go
@@ -3,6 +3,7 @@ package core
import (
"fmt"
"github.com/shadow1ng/fscan/common"
+ "github.com/shadow1ng/fscan/core/portfinger"
"io"
"net"
"strings"
@@ -50,11 +51,7 @@ type PortInfoScanner struct {
info *Info // 探测上下文
}
-// 预定义的基础探测器
-var (
- null = new(Probe) // 空探测器,用于基本协议识别
- commonProbe = new(Probe) // 通用探测器,用于常见服务识别
-)
+// 预定义的基础探测器已在PortFinger.go中定义,这里不再重复定义
// NewPortInfoScanner 创建新的端口服务识别器实例
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.Extras = extrasMap
- result.Banner = trimBanner(response)
+ result.Banner = portfinger.TrimBanner(string(response))
result.Service.Extras = extrasMap
// 特殊处理 microsoft-ds 服务
@@ -363,7 +360,7 @@ func (i *Info) handleHardMatch(response []byte, match *Match) {
// handleNoMatch 处理未找到匹配的情况
func (i *Info) handleNoMatch(response []byte, result *Result, softFound bool, softMatch Match) {
common.LogDebug("处理未匹配情况")
- result.Banner = trimBanner(response)
+ result.Banner = portfinger.TrimBanner(string(response))
if !softFound {
// 尝试识别 HTTP 服务
diff --git a/Core/portfinger/encoding_utils.go b/Core/portfinger/encoding_utils.go
new file mode 100644
index 0000000..f1b68b6
--- /dev/null
+++ b/Core/portfinger/encoding_utils.go
@@ -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
+}
+
diff --git a/Core/portfinger/match_engine.go b/Core/portfinger/match_engine.go
new file mode 100644
index 0000000..c785730
--- /dev/null
+++ b/Core/portfinger/match_engine.go
@@ -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)
+}
+
diff --git a/Core/portfinger/nmap-service-probes.txt b/Core/portfinger/nmap-service-probes.txt
new file mode 100644
index 0000000..b565155
--- /dev/null
+++ b/Core/portfinger/nmap-service-probes.txt
@@ -0,0 +1,16624 @@
+# Nmap service detection probe list -*- mode: fundamental; -*-
+# $Id$
+#
+# This is a database of custom probes and expected responses that the
+# Nmap Security Scanner ( https://nmap.org ) uses to
+# identify what services (eg http, smtp, dns, etc.) are listening on
+# open ports. Contributions to this database are welcome.
+# Instructions for obtaining and submitting service detection fingerprints can
+# be found in the Nmap Network Scanning book and online at
+# https://nmap.org/book/vscan-community.html
+#
+# This collection of probe data is (C) 1998-2020 by Insecure.Com
+# LLC. It is distributed under the Nmap Public Source license as
+# provided in the LICENSE file of the source distribution or at
+# https://nmap.org/data/LICENSE . Note that this license
+# requires you to license your own work under a compatible open source
+# license. If you wish to embed Nmap technology into proprietary
+# software, we sell alternative licenses (contact sales@insecure.com).
+# Dozens of software vendors already license Nmap technology such as
+# host discovery, port scanning, OS detection, and version detection.
+# For more details, see https://nmap.org/book/man-legal.html
+#
+# For details on how Nmap version detection works, why it was added,
+# the grammar of this file, and how to detect and contribute new
+# services, see https://nmap.org/book/vscan.html.
+
+# The Exclude directive takes a comma separated list of ports.
+# The format is exactly the same as the -p switch.
+Exclude T:9100-9107
+
+# This is the NULL probe that just compares any banners given to us
+##############################NEXT PROBE##############################
+Probe TCP NULL q||
+# Wait for at least 6 seconds for data. It used to be 5, but some
+# smtp services have lately been instituting an artificial pause (see
+# FEATURE('greet_pause') in Sendmail, for example)
+totalwaitms 6000
+# If the service closes the connection before 3 seconds, it's probably
+# tcpwrapped. Adjust up or down depending on your false-positive rate.
+tcpwrappedms 3000
+
+match 1c-server m|^S\xf5\xc6\x1a{| p/1C:Enterprise business management server/
+
+match 3cx-tunnel m|^\x04\0\xfb\xffLAPK| p/3CX Tunnel Protocol/
+
+match 4d-server m|^\0\0\0H\0\0\0\x02.[^\0]*\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0$|s p/4th Dimension database server/ cpe:/a:4d_sas:4d/
+
+match aastra-pbx m|^BUSY$| p|Aastra/Mitel 400-series PBX service port|
+match acap m|^\* ACAP \(IMPLEMENTATION \"CommuniGate Pro ACAP (\d[-.\w]+)\"\) | p/CommuniGate Pro ACAP server/ v/$1/ i/for mail client preference sharing/ cpe:/a:stalker:communigate_pro:$1/
+match acarsd m|^g\0\0\0\x1b\0\0\0\0\0\0\0acarsd\t([\w._-]+)\tAPI-([\w._-]+)\)\0\0\0\x06\x05\0\0\0\0\0\0<\?xml | p/acarsd/ v/$1/ i/API $2/ cpe:/a:acarsd:acarsd:$1/
+match acmp m|^ACMP Server Version ([\w._-]+)\r\n| p/Aagon ACMP Inventory/ v/$1/
+
+match apachemq m|^\0\0..\x01ActiveMQ\0\0\0.\x01\0\0.*\x0cProviderName\t\0\x08ActiveMQ.*\x0fPlatformDetails\t..JVM: (\d[^,]*), [^,]*, Oracle Corporation, OS: Linux, (\d\.[\d.]+)[^,]*, ([\w_-]+).*\x0fProviderVersion\t..(\d[\w._-]*)|s p/ActiveMQ OpenWire transport/ v/$4/ i/Java $1; arch: $3/ o/Linux $2/ cpe:/a:apache:activemq:$4/ cpe:/o:linux:linux_kernel:$2/a
+softmatch apachemq m|^\0\0..\x01ActiveMQ\0| p/ActiveMQ OpenWire transport/
+
+
+# Microsoft ActiveSync Version 3.7 Build 3083 (It's used for syncing
+# my ipaq it disappears when you remove the ipaq.)
+match activesync m|^.\0\x01\0[^\0]\0[^\0]\0[^\0]\0[^\0]\0[^\0]\0.*\0\0\0$|s p/Microsoft ActiveSync/ o/Windows/ cpe:/a:microsoft:activesync/ cpe:/o:microsoft:windows/a
+match activesync m|^\(\0\0\0\x02\0\0\0\x03\0\0\0\+\0\0\x003\0\0\0\0\0\0\0\x04\0\0`\x01\0\0\xff\0\0\0\0\0\0\0\0\0\0\0$|s p/Citrix ActiveSync/ o/Windows/ cpe:/o:microsoft:windows/a
+
+match adabas-d m|^Adabas D Remote Control Server Version ([\d.]+) Date [\d-]+ \(key is [0-9a-f]+\)\r\nOK> | p/Adabas D database remote control/ v/$1/
+
+match adobe-crossdomain m|^500 Internal Server Error
\r\n\r\n\r\n| p/Cisco Catalyst http config/ d/switch/ o/IOS/ cpe:/o:cisco:ios/a
+match http m|^HTTP/1\.1 200 OK\nMax-Age: 0\nExpires: 0\nCache-Control: no-cache\nCache-Control: private\nPragma: no-cache\nContent-type: multipart/x-mixed-replace;boundary=BoundaryString\n\n--BoundaryString\n| p/Motion Webcam gateway httpd/
+match http m|^HTTP/1\.[01] 200 OK\r\nServer: Motion/([\d.]+)\r\n| p/Motion Camera httpd/ v/$1/ d/webcam/
+match http m|^HTTP/1\.1 200 OK\r\nServer: Motion-httpd/([\d.]+)\r\n| p/Motion-httpd/ v/$1/ d/webcam/
+match http m|^HTTP/1\.1 \d\d\d .*\nServer: Motion/([\d.]+)\n.*\nContent-type: image/jpeg\n|s p/Motion webcam httpd/ v/$1/
+match http m|^HTTP/1\.1 \d\d\d .*\r\nContent-Type: text/plain\r\nServer: WPA/([-\w_.]+)\r\n\r\n| p/Glucose WeatherPop Advanced httpd/ v/$1/ o/Mac OS X/ cpe:/o:apple:mac_os_x/a
+match http m|^HTTP/1\.0 503 R\r\nContent-Type: text/html\r\n\r\nBusy$| p/D-Link router http config/ d/router/
+match http m|^501 Not Implemented
\nThe server has not implemented your request type\.
\n\r\n$| p/Hummingbird Document Manager httpd/
+match http m|^HTTP/1\.0 200 OK\r\nContent-Type: text/html\r\n\r\n\n\n
\nProgramArguments\n\n
Bad Request \(Invalid Verb\)
|s p/Microsoft IIS httpd/ o/Windows/ cpe:/a:microsoft:internet_information_services/ cpe:/o:microsoft:windows/a
+match http m|^400 Bad Request
\nYour request has bad syntax or is inherently impossible to satisfy\.\n
\n\n$| p/thttpd/ v/$1/ i/Asotel Vector 1908 switch http config/ d/switch/ cpe:/a:acme:thttpd:$1/
+match http m|^HTTP/1\.1 200 OK\r\nServer: DVBViewer \(Windows\)\r\nContent-Type: video/mpeg2\r\n\r\n\r\n| p/DVBViewer digital TV viewer httpd/ o/Windows/ cpe:/o:microsoft:windows/a
+match http m|^HTTP/1\.1 400 Bad Request\r\nserver: kolibri-([\w._-]+)\r\ncontent-type: text/plain\r\ncontent-length: 11\r\n\r\nBad Request$| p/Kolibri httpd/ v/$1/ cpe:/a:senkas:kolibri:$1/
+match http m|^HTTP/1\.1 405 Method Not Allowed\r\nServer: remote-potato-v([\w._-]+)\r\n| p/Remote Potato media player/ v/$1/
+# The date reveals the time zone instead of using GMT.
+match http m|^HTTP/1\.1 405 Method Not Allowed\r\nDate: ([^\r]+)\r\nServer: Embedthis-Appweb/([\w._-]+)\r\n| p/Embedthis-Appweb/ v/$2/ i/date: $1/ cpe:/a:mbedthis:appweb:$2/
+match http m|^HTTP/1\.0 503 Service Unavailable\r\nDate: .* GMT\r\nServer: Embedthis-Appweb/([\w._-]+)\r\n| p/Embedthis-Appweb/ v/$1/ i/Sharp Open System Architecture/ d/printer/ cpe:/a:mbedthis:appweb:$1/
+match http m|^HTTP/1\.1 400 Bad Request\r\nServer: Microsoft-Cassini/([\w._-]+)\r\n| p/Microsoft Cassini httpd/ v/$1/ o/Windows/ cpe:/a:microsoft:cassini:$1/ cpe:/o:microsoft:windows/a
+match http m|^HTTP/1\.1 408 Request Timeout\r\nServer: WebSphere Application Server/([\w._-]+)\r\nContent-Type: text/html\r\nContent-Length: 117\r\n| p/IBM WebSphere Application Server/ v/$1/ cpe:/a:ibm:websphere_application_server:$1/
+match http m|^HTTP/1\.0 200 Ok Welcome to VOC\r\nServer: Voodoo chat daemon ver ([\w._ -]+)\r\nContent-type: text/html\r\nExpires: Mon, 08 Apr 1976 19:30:00 GMT\+3\r\nConnection: close\r\nKeep-Alive: max=0\r\nCache-Control: no-store, no-cache, must-revalidate\r\nCache-Control: post-check=0, pre-check=0\r\nPragma: no-cache\r\n\r\n$| p/Voodoo http chat daemon/ v/$1/
+match http m|^HTTP/1\.1 400 Bad Request\r\nServer: Cassini/([\w._-]+)\r\n.*\n\nInvalid Access
\n
The service is not available\. Please try again later\.
$| p/Alcatel-Lucent OmniPCX PBX httpd/ d/PBX/ cpe:/a:alcatel-lucent:omnipcx/ +match http m|^HTTP/1\.0 401 Unauthorized\r\nServer: \r\nDate: .* GMT\r\nWWW-Authenticate: Basic realm=\"\.\"\r\nContent-type: text/html\r\nConnection: close\r\n\r\nAccess to this document requires a User ID
\r\n\r\n$|s p/Alvarion-Webs/ i/Alvarion BreezeMAX WiMAX WAP http config/ d/WAP/ +match http m|^HTTP/1\.0 400 Bad Request\r\nPragma: no-cache\r\nContent-type: text/html\r\n\r\n\n \n\r\n
\nReturn to last page
\n\n\n\n$|s p/AudioCodes Mediant 200 VoIP gateway http config/ d/VoIP adapter/ cpe:/a:allegro:rompager:$1/ cpe:/h:audiocodes:mediant_200/a +match http m|^HTTP/1\.1 200 OK\r\nServer: WHC chatroom\r\n| p/Fifi chat server http interface/ +match http m|^HTTP/1\.0 200 OK\r\nServer: Xunlei Http Server/([\d.]+)\r\n| p/Xunlei BitTorrent http interface/ v/$1/ +match http m|^HTTP/1\.1 200 OK\r\n.*<\?xml version=\"1\.0\" encoding=\"utf-8\"\?>\n\n\n
\n \r\n\r\n\r\n\r\n