github.com/chainreactors/fingers@v1.2.1/nmap/type-nmap.go (about) 1 package gonmap 2 3 import ( 4 "strconv" 5 "strings" 6 ) 7 8 type Nmap struct { 9 exclude PortList 10 probeNameMap map[string]*Probe 11 12 // 按稀有度分组的探针映射 map[Rarity][]*Probe 13 rarityProbeMap map[int][]*Probe 14 portProbeMap map[int]ProbeList 15 //bypassAllProbePort PortList 16 sslSecondProbeMap ProbeList 17 sslProbeMap ProbeList 18 19 // Services数据,用于端口服务识别 20 servicesData *ServicesData 21 nmapServices []string 22 } 23 24 // parsePortString 解析端口字符串,返回端口号、协议类型和是否为UDP 25 func (n *Nmap) parsePortString(portStr string) (port int, protocol string, isUDP bool) { 26 portStr = strings.TrimSpace(portStr) 27 28 // 检查UDP标记 (U:139) 29 if strings.HasPrefix(strings.ToUpper(portStr), "U:") { 30 portStr = portStr[2:] // 移除"U:"前缀 31 isUDP = true 32 protocol = "UDP" 33 } else { 34 // 默认为TCP 35 isUDP = false 36 protocol = "TCP" 37 } 38 39 // 解析端口号 40 portNum, err := strconv.Atoi(portStr) 41 if err != nil { 42 // 如果解析失败,返回默认值 43 return 0, protocol, isUDP 44 } 45 46 return portNum, protocol, isUDP 47 } 48 49 // shouldSkipUDPScan 判断是否应该跳过UDP扫描(未明确标记为UDP的情况下) 50 func (n *Nmap) shouldSkipUDPScan(port int) bool { 51 // 这里暂时返回false,因为我们现在主要处理TCP 52 // 将来可以根据需要添加更多逻辑 53 return false 54 } 55 56 // scanUDPPort UDP端口扫描逻辑 57 func (n *Nmap) scanUDPPort(ip string, port int, level int, sender func(host string, port int, data []byte, tls bool, protocol string) ([]byte, bool, error)) (status Status, response *Response) { 58 localProbeUsed := make(ProbeList, 0) 59 60 // 筛选适用的UDP探针 61 udpProbes := n.getUDPProbes(port, level) 62 if len(udpProbes) > 0 { 63 return n.getResponseByProbes(ip, port, level, sender, &localProbeUsed, udpProbes...) 64 } 65 66 return NotMatched, nil 67 } 68 69 // getUDPProbes 获取UDP探针列表 70 func (n *Nmap) getUDPProbes(port, level int) ProbeList { 71 var udpProbes ProbeList 72 for _, probe := range n.probeNameMap { 73 if probe.Protocol == "UDP" && probe.Rarity <= level { 74 // 检查端口是否匹配 75 if len(probe.Ports) == 0 || probe.Ports.exist(port) { 76 udpProbes = append(udpProbes, probe.Name) 77 } 78 } 79 } 80 return udpProbes 81 } 82 83 // handleNetworkError 统一处理网络错误 84 func (n *Nmap) handleNetworkError(err error, protocol string) (Status, *Response) { 85 errStr := err.Error() 86 87 // 明确的连接拒绝错误,端口关闭 88 connectionErrors := []string{"connection refused", "no route to host", "network is unreachable"} 89 for _, errPattern := range connectionErrors { 90 if strings.Contains(errStr, errPattern) { 91 return Closed, nil 92 } 93 } 94 95 // UDP特殊处理 96 if protocol == "UDP" && strings.Contains(errStr, "refused") { 97 return Closed, nil 98 } 99 100 // 超时和其他错误返回NotMatched,避免触发guess逻辑 101 return NotMatched, nil 102 } 103 104 func (n *Nmap) Scan(ip string, portStr string, level int, sender func(host string, port int, data []byte, tls bool, protocol string) ([]byte, bool, error)) (status Status, response *Response) { 105 // 解析端口字符串 106 port, _, isUDP := n.parsePortString(portStr) 107 if port == 0 { 108 return NotMatched, nil 109 } 110 111 // 如果没有明确标记为UDP,则只进行TCP扫描 112 if isUDP { 113 // UDP扫描逻辑(暂时简化,主要扫描UDP探针) 114 return n.scanUDPPort(ip, port, level, sender) 115 } 116 117 // TCP扫描逻辑 - 分层扫描策略 118 return n.scanTCPPort(ip, port, level, sender) 119 } 120 121 // scanTCPPort TCP端口扫描的分层策略 122 func (n *Nmap) scanTCPPort(ip string, port int, level int, sender func(host string, port int, data []byte, tls bool, protocol string) ([]byte, bool, error)) (status Status, response *Response) { 123 localProbeUsed := make(ProbeList, 0) 124 125 // 定义扫描层次 126 scanLayers := []struct { 127 name string 128 probes func() ProbeList 129 }{ 130 {"NULL", func() ProbeList { 131 if nullProbe, exists := n.probeNameMap["TCP_NULL"]; exists { 132 return ProbeList{nullProbe.Name} 133 } 134 return ProbeList{} 135 }}, 136 {"Port-Specific", func() ProbeList { 137 return n.getPortSpecificProbes(port) 138 }}, 139 {"SSL", func() ProbeList { 140 var sslProbes ProbeList 141 for _, sslProbe := range n.sslProbeMap { 142 if !localProbeUsed.exist(sslProbe) { 143 sslProbes = append(sslProbes, sslProbe) 144 } 145 } 146 return sslProbes 147 }}, 148 {"Rarity", func() ProbeList { 149 var rarityProbes ProbeList 150 for rarity := 1; rarity <= level; rarity++ { 151 if probes, exists := n.rarityProbeMap[rarity]; exists { 152 for _, probe := range probes { 153 if !localProbeUsed.exist(probe.Name) { 154 rarityProbes = append(rarityProbes, probe.Name) 155 } 156 } 157 } 158 } 159 return rarityProbes.removeDuplicate() 160 }}, 161 } 162 163 // 按层次依次执行扫描 164 for _, layer := range scanLayers { 165 probes := layer.probes() 166 if len(probes) > 0 { 167 status, response = n.getResponseByProbes(ip, port, level, sender, &localProbeUsed, probes...) 168 if status == Closed || status == Matched { 169 return status, response 170 } 171 } 172 } 173 174 return NotMatched, nil 175 } 176 177 // getPortSpecificProbes 获取端口特定的探针列表,从nmap-services配置自动选择 178 // 端口特定探针不受level限制,因为它们是最相关的探针 179 func (n *Nmap) getPortSpecificProbes(port int) ProbeList { 180 var probes ProbeList 181 182 // 优化1: 直接从portProbeMap获取该端口对应的探针,O(1)操作 183 if portProbes, exists := n.portProbeMap[port]; exists && len(portProbes) > 0 { 184 // 优化2: 使用probeNameMap直接获取探针信息,避免遍历 185 probesByRarity := make(map[int][]string) 186 maxRarity := 0 187 188 for _, probeName := range portProbes { 189 if probe, exists := n.probeNameMap[probeName]; exists { 190 rarity := probe.Rarity 191 probesByRarity[rarity] = append(probesByRarity[rarity], probeName) 192 if rarity > maxRarity { 193 maxRarity = rarity 194 } 195 } 196 } 197 198 // 优化3: 按稀有度排序,但不限制稀有度级别(因为是端口特定的) 199 for rarity := 1; rarity <= maxRarity; rarity++ { 200 probes = append(probes, probesByRarity[rarity]...) 201 } 202 203 // 不再限制探针数量,按分层逻辑执行 204 } 205 206 return probes.removeDuplicate() 207 } 208 209 // getPortCategoryProbes 获取端口分类特定探针,参考vscan的tcpPortsProbesScanTask 210 func (n *Nmap) getPortCategoryProbes(port int) ProbeList { 211 var probes ProbeList 212 213 switch port { 214 case 3389: // RDP - Terminal探针组 215 terminalProbes := []string{"TCP_TerminalServerCookie", "TCP_TerminalServer"} 216 for _, probeName := range terminalProbes { 217 if _, exists := n.probeNameMap[probeName]; exists { 218 probes = append(probes, probeName) 219 } 220 } 221 222 case 443, 8433, 9433: // HTTPS - SSL探针组 223 sslProbes := []string{"TCP_SSLSessionReq", "TCP_TLSSessionReq", "TCP_SSLv23SessionReq"} 224 for _, probeName := range sslProbes { 225 if _, exists := n.probeNameMap[probeName]; exists { 226 probes = append(probes, probeName) 227 } 228 } 229 230 case 80, 3000, 4567, 5000, 8000, 8001, 8080, 8081, 8888, 9001, 9080, 9090, 9100: // HTTP - FourOhFourRequest探针 231 httpProbes := []string{"TCP_GetRequest", "TCP_HTTPOptions"} 232 for _, probeName := range httpProbes { 233 if _, exists := n.probeNameMap[probeName]; exists { 234 probes = append(probes, probeName) 235 } 236 } 237 } 238 239 return probes.removeDuplicate() 240 } 241 242 // getResponseByProbes 使用外部sender和本地probeUsed进行扫描 243 func (n *Nmap) getResponseByProbes(host string, port int, level int, sender func(host string, port int, data []byte, tls bool, protocol string) ([]byte, bool, error), localProbeUsed *ProbeList, probes ...string) (status Status, response *Response) { 244 var responseNotMatch *Response 245 for _, requestName := range probes { 246 if localProbeUsed.exist(requestName) { 247 continue 248 } 249 *localProbeUsed = append(*localProbeUsed, requestName) 250 p := n.probeNameMap[requestName] 251 252 status, response = n.getResponse(host, port, p.SSLPorts.exist(port), sender, p) 253 254 if status == Closed { 255 return Closed, nil 256 } 257 if status == Matched { 258 // 如果匹配到ssl,需要进行二次扫描 259 if response.FingerPrint.Service == "ssl" { 260 sslStatus, sslResponse := n.getSSLSecondProbes(host, port, level, sender, localProbeUsed) 261 if sslStatus == Matched { 262 return Matched, sslResponse 263 } 264 } 265 return Matched, response 266 } 267 if status == Open { 268 responseNotMatch = response 269 } 270 } 271 272 if responseNotMatch != nil { 273 response = responseNotMatch 274 } 275 return status, response 276 } 277 278 // getSSLSecondProbes SSL二次探测 279 func (n *Nmap) getSSLSecondProbes(host string, port int, level int, sender func(host string, port int, data []byte, tls bool, protocol string) ([]byte, bool, error), localProbeUsed *ProbeList) (status Status, response *Response) { 280 // 直接使用SSL二次探测的探针(不需要额外过滤,已在主扫描中过滤) 281 status, response = n.getResponseByProbes(host, port, level, sender, localProbeUsed, n.sslSecondProbeMap...) 282 if status != Matched || response.FingerPrint.Service == "ssl" { 283 status, response = n.getResponseByHTTPS(host, port, sender) 284 } 285 if status == Matched && response.FingerPrint.Service != "ssl" { 286 if response.FingerPrint.Service == "http" { 287 response.FingerPrint.Service = "https" 288 } 289 return Matched, response 290 } 291 return NotMatched, response 292 } 293 294 // getResponseByHTTPS 处理HTTPS 295 func (n *Nmap) getResponseByHTTPS(host string, port int, sender func(host string, port int, data []byte, tls bool, protocol string) ([]byte, bool, error)) (status Status, response *Response) { 296 var httpRequest = n.probeNameMap["TCP_GetRequest"] 297 return n.getResponse(host, port, true, sender, httpRequest) 298 } 299 300 // getResponse 使用外部sender进行网络通信的核心方法 301 func (n *Nmap) getResponse(host string, port int, tls bool, sender func(host string, port int, data []byte, tls bool, protocol string) ([]byte, bool, error), p *Probe) (Status, *Response) { 302 //if port == 53 { 303 // if DnsScan(host, port) { 304 // return Matched, &dnsResponse 305 // } else { 306 // return Closed, nil 307 // } 308 //} 309 310 // 使用外部sender发送探测数据 311 probeData := []byte(p.buildRequest(host)) // 构建探测请求数据 312 313 responseData, actualTLS, err := sender(host, port, probeData, tls, p.Protocol) 314 315 if err != nil { 316 // 根据错误类型判断端口状态 317 errStr := err.Error() 318 319 // 明确的连接拒绝错误,端口关闭 320 if strings.Contains(errStr, "connection refused") || 321 strings.Contains(errStr, "no route to host") || 322 strings.Contains(errStr, "network is unreachable") { 323 return Closed, nil 324 } 325 326 // UDP特殊处理 327 if p.Protocol == "UDP" && strings.Contains(errStr, "refused") { 328 return Closed, nil 329 } 330 331 // 超时错误通常意味着端口被过滤或服务不响应,但不一定意味着端口关闭 332 // 这种情况下应该返回NotMatched而不是Open,避免触发guess逻辑 333 if strings.Contains(errStr, "timeout") || 334 strings.Contains(errStr, "i/o timeout") || 335 strings.Contains(errStr, "deadline exceeded") { 336 return NotMatched, nil 337 } 338 339 // 其他错误也返回NotMatched 340 return NotMatched, nil 341 } 342 343 response := &Response{ 344 Raw: responseData, 345 TLS: actualTLS, 346 FingerPrint: &FingerPrint{}, 347 } 348 349 //若存在返回包,则开始捕获指纹 350 fingerPrint := n.getFinger(responseData, actualTLS, p.Name) 351 response.FingerPrint = fingerPrint 352 353 if fingerPrint.Service == "" { 354 return NotMatched, response 355 } else { 356 return Matched, response 357 } 358 } 359 360 func (n *Nmap) getFinger(responseRaw []byte, tls bool, requestName string) *FingerPrint { 361 probe := n.probeNameMap[requestName] 362 363 finger := probe.match(responseRaw) 364 365 if tls == true { 366 if finger.Service == "http" { 367 finger.Service = "https" 368 } 369 } 370 371 if finger.Service != "" || n.probeNameMap[requestName].Fallback == "" { 372 //标记当前探针名称 373 finger.ProbeName = requestName 374 return finger 375 } 376 377 fallback := n.probeNameMap[requestName].Fallback 378 fallbackProbe := n.probeNameMap[fallback] 379 for fallback != "" { 380 finger = fallbackProbe.match(responseRaw) 381 fallback = n.probeNameMap[fallback].Fallback 382 if finger.Service != "" { 383 break 384 } 385 } 386 //标记当前探针名称 387 finger.ProbeName = requestName 388 return finger 389 } 390 391 func (n *Nmap) AddMatch(probeName string, expr string) { 392 var probe = n.probeNameMap[probeName] 393 if probe == nil { 394 return // 探针不存在,跳过 395 } 396 probe.loadMatch(expr, false) 397 } 398 399 // GetProbeMap 返回探针名称映射(用于调试) 400 func (n *Nmap) GetProbeMap() map[string]*Probe { 401 return n.probeNameMap 402 } 403 404 // GetPortProbeMap 返回端口探针映射(用于调试) 405 func (n *Nmap) GetPortProbeMap() map[int]ProbeList { 406 return n.portProbeMap 407 } 408 409 // GetRarityProbeMap 返回稀有度探针映射(用于调试) 410 func (n *Nmap) GetRarityProbeMap() map[int][]*Probe { 411 return n.rarityProbeMap 412 } 413 414 // GetPortSpecificProbes 公开方法,用于调试 415 func (n *Nmap) GetPortSpecificProbes(port int) ProbeList { 416 return n.getPortSpecificProbes(port) 417 } 418 419 // GuessProtocol 根据端口号猜测服务协议 420 func (n *Nmap) GuessProtocol(port int) string { 421 // 直接从实例获取服务名称 422 if port >= 0 && port < len(n.nmapServices) { 423 return n.nmapServices[port] 424 } 425 return "unknown" 426 } 427 428 // buildNmapServicesArray 构建原有格式的services数组以保持兼容性 429 func (n *Nmap) buildNmapServicesArray(data *ServicesData) []string { 430 // 找到最大端口号 431 maxPort := 0 432 for _, service := range data.Services { 433 if service.Port > maxPort { 434 maxPort = service.Port 435 } 436 } 437 438 // 初始化数组,所有端口默认为"unknown" 439 services := make([]string, maxPort+1) 440 for i := range services { 441 services[i] = "unknown" 442 } 443 444 // 填充已知服务 445 for _, service := range data.Services { 446 if service.Port >= 0 && service.Port < len(services) { 447 services[service.Port] = n.fixServiceName(service.Name) 448 } 449 } 450 451 return services 452 } 453 454 // fixServiceName 修复服务名称 455 func (n *Nmap) fixServiceName(serviceName string) string { 456 serviceName = strings.ToLower(serviceName) 457 if serviceName == "" { 458 return "unknown" 459 } 460 461 // 处理一些特殊情况 462 switch serviceName { 463 case "www", "www-http": 464 return "http" 465 case "https", "http-ssl": 466 return "https" 467 case "domain": 468 return "dns" 469 case "nameserver": 470 return "dns" 471 default: 472 serviceName = strings.ReplaceAll(serviceName, "_", "-") 473 return serviceName 474 } 475 } 476 477 //初始化类 478 479 func (n *Nmap) loadExclude(expr string) { 480 n.exclude = parsePortList(expr) 481 } 482 483 func (n *Nmap) pushProbe(p Probe) { 484 n.probeNameMap[p.Name] = &p 485 486 // 按稀有度分组探针 487 n.rarityProbeMap[p.Rarity] = append(n.rarityProbeMap[p.Rarity], &p) 488 489 //建立端口扫描对应表,将根据端口号决定使用何种请求包 490 //0记录所有使用的探针 491 n.portProbeMap[0] = append(n.portProbeMap[0], p.Name) 492 493 //分别压入sslports,ports 494 for _, i := range p.Ports { 495 n.portProbeMap[i] = append(n.portProbeMap[i], p.Name) 496 } 497 498 for _, i := range p.SSLPorts { 499 n.portProbeMap[i] = append(n.portProbeMap[i], p.Name) 500 } 501 } 502 503 func (n *Nmap) fixFallback() { 504 for probeName, probeType := range n.probeNameMap { 505 fallback := probeType.Fallback 506 if fallback == "" { 507 continue 508 } 509 if _, ok := n.probeNameMap["TCP_"+fallback]; ok { 510 n.probeNameMap[probeName].Fallback = "TCP_" + fallback 511 } else { 512 n.probeNameMap[probeName].Fallback = "UDP_" + fallback 513 } 514 } 515 } 516 517 func (n *Nmap) isCommand(line string) bool { 518 //删除注释行和空行 519 if len(line) < 2 { 520 return false 521 } 522 if line[:1] == "#" { 523 return false 524 } 525 //删除异常命令 526 commandName := line[:strings.Index(line, " ")] 527 commandArr := []string{ 528 "Exclude", "Probe", "match", "softmatch", "ports", "sslports", "totalwaitms", "tcpwrappedms", "rarity", "fallback", 529 } 530 for _, item := range commandArr { 531 if item == commandName { 532 return true 533 } 534 } 535 return false 536 } 537 538 // 工具函数 539 //func DnsScan(host string, port int) bool { 540 // domainServer := fmt.Sprintf("%s:%d", host, port) 541 // c := dns.Client{ 542 // Timeout: 2 * time.Second, 543 // } 544 // m := dns.Msg{} 545 // // 最终都会指向一个ip 也就是typeA, 这样就可以返回所有层的cname. 546 // m.SetQuestion("www.baidu.com.", dns.TypeA) 547 // _, _, err := c.Exchange(&m, domainServer) 548 // if err != nil { 549 // return false 550 // } 551 // return true 552 //}