diff --git a/common/logging/logger.go b/common/logging/logger.go index 759e507..7c78e63 100644 --- a/common/logging/logger.go +++ b/common/logging/logger.go @@ -2,6 +2,7 @@ package logging import ( "fmt" + "strings" "sync" "time" @@ -116,10 +117,22 @@ func (l *Logger) log(level LogLevel, content string) { // 格式化消息:保留前缀,去掉时间戳 prefix := l.getLevelPrefix(level) - logMsg := fmt.Sprintf("%s %s", prefix, content) - // 输出消息 - l.outputMessage(level, logMsg) + // 处理多行内容:给每行加上前缀,然后作为一个整体输出 + if strings.Contains(content, "\n") { + lines := strings.Split(content, "\n") + var formattedLines []string + for _, line := range lines { + if line != "" { + formattedLines = append(formattedLines, fmt.Sprintf("%s %s", prefix, line)) + } + } + logMsg := strings.Join(formattedLines, "\n") + l.outputMessage(level, logMsg) + } else { + logMsg := fmt.Sprintf("%s %s", prefix, content) + l.outputMessage(level, logMsg) + } // 根据慢速输出设置决定是否添加延迟 if l.config.SlowOutput { diff --git a/core/port_scan.go b/core/port_scan.go index 6cb3bdc..1be5f5f 100644 --- a/core/port_scan.go +++ b/core/port_scan.go @@ -322,7 +322,7 @@ func buildServiceLogMessage(addr string, serviceInfo *ServiceInfo, isWeb bool) s // 构建 [Product:xxx ||Version:xxx] 格式 var info []string - if product, ok := serviceInfo.Extras["product"]; ok && product != "" { + if product, ok := serviceInfo.Extras["vendor_product"]; ok && product != "" { info = append(info, fmt.Sprintf("Product:%s", product)) } if serviceInfo.Version != "" { diff --git a/core/portfinger/match_engine.go b/core/portfinger/match_engine.go index 3158bdc..9b15398 100644 --- a/core/portfinger/match_engine.go +++ b/core/portfinger/match_engine.go @@ -6,6 +6,32 @@ import ( "strings" ) +// BytesToRegexSafeString 将字节切片转换为 Go regexp 安全的正则表达式模式字符串 +// 非打印字符和高位字节转换为 \x{NN} 形式,用于编译正则表达式 +func BytesToRegexSafeString(b []byte) string { + var result strings.Builder + for _, c := range b { + if c < 32 || c >= 128 { + // 控制字符和高位字节转换为 \x{NN} 格式 + result.WriteString(fmt.Sprintf("\\x{%02x}", c)) + } else { + result.WriteByte(c) + } + } + return result.String() +} + +// bytesToLatin1String 将字节切片转换为 Latin-1 字符串 +// 每个字节直接映射到对应的 Unicode 码点 U+0000-U+00FF +// 这样可以与使用 \x{NN} 格式的正则表达式正确匹配 +func bytesToLatin1String(b []byte) string { + runes := make([]rune, len(b)) + for i, c := range b { + runes[i] = rune(c) + } + return string(runes) +} + // parseMatchDirective 解析match/softmatch指令的通用实现 func (p *Probe) parseMatchDirective(data, prefix string, isSoft bool) (Match, error) { match := Match{IsSoft: isSoft} @@ -23,13 +49,22 @@ func (p *Probe) parseMatchDirective(data, prefix string, isSoft bool) (Match, er pattern := textSplited[0] versionInfo := strings.Join(textSplited[1:], "") + // versionInfo 格式是 "flags p/product/ v/version/ ..." + // flags 是正则表达式修饰符(如 s、i、si),后面跟空格和版本信息字段 + // 需要跳过 flags 部分,找到第一个空格开始的版本信息 + if idx := strings.Index(versionInfo, " "); idx != -1 { + versionInfo = versionInfo[idx:] + } + // 解码并编译正则表达式 patternUnescaped, decodeErr := DecodePattern(pattern) if decodeErr != nil { return match, decodeErr } - patternCompiled, compileErr := regexp.Compile(string(patternUnescaped)) + // 将字节模式转换为 Go regexp 安全的字符串(处理高位字节) + safePattern := BytesToRegexSafeString(patternUnescaped) + patternCompiled, compileErr := regexp.Compile(safePattern) if compileErr != nil { return match, compileErr } @@ -58,10 +93,13 @@ func (m *Match) MatchPattern(response []byte) bool { return false } - matched := m.PatternCompiled.Match(response) + // 将响应字节转换为 Latin-1 字符串,每个字节映射到对应的 Unicode 码点 + // 这样正则表达式中的 \x{NN} 可以正确匹配对应的字节值 + latin1Response := bytesToLatin1String(response) + matched := m.PatternCompiled.MatchString(latin1Response) if matched { // 提取匹配到的子组 - submatches := m.PatternCompiled.FindStringSubmatch(string(response)) + submatches := m.PatternCompiled.FindStringSubmatch(latin1Response) if len(submatches) > 1 { m.FoundItems = submatches[1:] // 排除完整匹配,只保留分组 } diff --git a/core/portfinger/nmap-service-probes.txt b/core/portfinger/nmap-service-probes.txt index 3b18a97..d426a48 100644 --- a/core/portfinger/nmap-service-probes.txt +++ b/core/portfinger/nmap-service-probes.txt @@ -13987,7 +13987,14 @@ match sap-gui m|^\0\0\0\x0e\*\*DPTMMSG\*\*\0\0\xf8| p/SAP Gui Dispatcher/ cpe:/a softmatch smpp m|^\0\0\0\x10\x80\0\0\0\0\0\0\x03....$|s softmatch postgresql m|^E\0\0\0.SFATAL\0(?:VFATAL\0)?C\w{5}\0M| p/PostgreSQL DB/ cpe:/a:postgresql:postgresql/a -# SMB Negotiate Protocol +# SMB Multi-Protocol Negotiate (SMB1 format with SMB2 dialects) +##############################NEXT PROBE############################## +Probe TCP SMB2ProgNeg q|\0\0\0\x45\xffSMBr\0\0\0\0\x18\x01\x48\0\0\0\0\0\0\0\0\0\0\0\0\xff\xff\xac\x03\0\0\0\0\0\x22\0\x02NT LM 0.12\0\x02SMB 2.002\0\x02SMB 2.???\0| +rarity 3 +ports 139,445 +match microsoft-ds m|^\0\0..\xfeSMB\x40\0|s p/Microsoft Windows SMB2/ o/Windows/ cpe:/o:microsoft:windows/a + +# SMB Negotiate Protocol (SMB1 - for legacy systems) ##############################NEXT PROBE############################## Probe TCP SMBProgNeg q|\0\0\0\xa4\xff\x53\x4d\x42\x72\0\0\0\0\x08\x01\x40\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x40\x06\0\0\x01\0\0\x81\0\x02PC NETWORK PROGRAM 1.0\0\x02MICROSOFT NETWORKS 1.03\0\x02MICROSOFT NETWORKS 3.0\0\x02LANMAN1.0\0\x02LM1.2X002\0\x02Samba\0\x02NT LANMAN 1.0\0\x02NT LM 0.12\0| rarity 4 diff --git a/core/portfinger/version_parser.go b/core/portfinger/version_parser.go index 7ceb5c3..e949760 100644 --- a/core/portfinger/version_parser.go +++ b/core/portfinger/version_parser.go @@ -29,12 +29,7 @@ var ( func (m *Match) ParseVersionInfo(response []byte) Extras { var extras = Extras{} - // 确保有匹配项 - if len(m.FoundItems) == 0 { - return extras - } - - // 替换版本信息中的占位符(单次扫描) + // 替换版本信息中的占位符(如 $1, $2 等) versionInfo := m.VersionInfo if len(m.FoundItems) > 0 { replacements := make([]string, 0, len(m.FoundItems)*2) diff --git a/core/service_probe.go b/core/service_probe.go index 67f7fcb..b2ea612 100644 --- a/core/service_probe.go +++ b/core/service_probe.go @@ -225,6 +225,8 @@ func (s *SmartPortInfoScanner) tryProbeList(probes []*Probe, usedProbes map[stri response := s.info.Connect(probeData) if len(response) == 0 { + // 连接可能被关闭(如服务端返回 EOF),尝试重建连接后继续下一个探针 + s.reconnectIfNeeded() continue } @@ -238,6 +240,26 @@ func (s *SmartPortInfoScanner) tryProbeList(probes []*Probe, usedProbes map[stri return false } +// reconnectIfNeeded 强制重建连接 +// 当探针收到空响应时调用,说明连接可能已被服务端关闭 +func (s *SmartPortInfoScanner) reconnectIfNeeded() { + // 关闭旧连接 + if s.info.Conn != nil { + _ = s.info.Conn.Close() + s.info.Conn = nil + s.Conn = nil + } + + // 重新建立连接 + newConn, err := common.WrapperTcpWithTimeout("tcp", fmt.Sprintf("%s:%d", s.Address, s.Port), s.Timeout) + if err != nil { + return + } + + s.info.Conn = newConn + s.Conn = newConn +} + // performSSLSecondStage 执行 SSL 多阶段探测 // 参考 gonmap 的策略:ssl → ssl-specific probes → https func (s *SmartPortInfoScanner) performSSLSecondStage(serviceInfo *ServiceInfo) *ServiceInfo { diff --git a/plugins/services/findnet.go b/plugins/services/findnet.go index bc7d14d..0b3f08e 100644 --- a/plugins/services/findnet.go +++ b/plugins/services/findnet.go @@ -76,15 +76,20 @@ func (p *FindNetPlugin) Scan(ctx context.Context, info *common.HostInfo, config state.IncrementTCPSuccessPacketCount() - // 记录发现的网络信息 (每行一个IP,便于阅读) + // 记录发现的网络信息 (一次性输出,避免被其他日志打断) if networkInfo.Valid { - // 输出主机名 + var lines []string + // 主机名行 if networkInfo.Hostname != "" { - common.LogSuccess(fmt.Sprintf("NetInfo %s [%s]", target, networkInfo.Hostname)) + lines = append(lines, fmt.Sprintf("NetInfo %s [%s]", target, networkInfo.Hostname)) } // 每个IP单独一行 for _, ip := range networkInfo.IPv4Addrs { - common.LogSuccess(fmt.Sprintf("NetInfo %s -> %s", target, ip)) + lines = append(lines, fmt.Sprintf("NetInfo %s -> %s", target, ip)) + } + // 一次性输出所有行 + if len(lines) > 0 { + common.LogSuccess(strings.Join(lines, "\n")) } }