github.com/chainreactors/fingers@v1.2.1/cmd/transform/transform.go (about) 1 package main 2 3 import ( 4 "compress/gzip" 5 "encoding/json" 6 "flag" 7 "fmt" 8 "io" 9 "log" 10 "net/http" 11 "net/url" 12 "os" 13 "os/exec" 14 "path/filepath" 15 "runtime" 16 "strconv" 17 "strings" 18 "time" 19 20 gonmap "github.com/chainreactors/fingers/nmap" 21 "gopkg.in/yaml.v3" 22 ) 23 24 // DataSource 定义数据源接口 25 type DataSource interface { 26 Name() string // 数据源名称 27 URL() string // 下载URL 28 CacheFileName() string // 本地缓存文件名 29 OutputFileName() string // 输出JSON文件名 30 Download(client *http.Client) error // 下载逻辑 31 Transform() error // 转换逻辑 32 } 33 34 // ProbesDataSource nmap-service-probes数据源 35 type ProbesDataSource struct{} 36 37 func (p *ProbesDataSource) Name() string { return "probes" } 38 func (p *ProbesDataSource) URL() string { 39 return "https://raw.githubusercontent.com/nmap/nmap/master/nmap-service-probes" 40 } 41 func (p *ProbesDataSource) CacheFileName() string { return "nmap-service-probes.txt" } 42 func (p *ProbesDataSource) OutputFileName() string { return "resources/nmap-service-probes.json.gz" } 43 44 func (p *ProbesDataSource) Download(client *http.Client) error { 45 return downloadFile(client, p.URL(), p.CacheFileName()) 46 } 47 48 func (p *ProbesDataSource) Transform() error { 49 return transformProbes(p.CacheFileName(), p.OutputFileName()) 50 } 51 52 // ServicesDataSource nmap-services数据源 53 type ServicesDataSource struct{} 54 55 func (s *ServicesDataSource) Name() string { return "services" } 56 func (s *ServicesDataSource) URL() string { 57 return "https://raw.githubusercontent.com/nmap/nmap/master/nmap-services" 58 } 59 func (s *ServicesDataSource) CacheFileName() string { return "nmap-services.txt" } 60 func (s *ServicesDataSource) OutputFileName() string { return "resources/nmap-services.json.gz" } 61 62 func (s *ServicesDataSource) Download(client *http.Client) error { 63 return downloadFile(client, s.URL(), s.CacheFileName()) 64 } 65 66 func (s *ServicesDataSource) Transform() error { 67 return transformServices(s.CacheFileName(), s.OutputFileName()) 68 } 69 70 // FingerprintHubWebDataSource fingerprinthub web指纹数据源 71 type FingerprintHubWebDataSource struct{} 72 73 func (f *FingerprintHubWebDataSource) Name() string { return "fingerprinthub-web" } 74 func (f *FingerprintHubWebDataSource) URL() string { 75 return "https://github.com/0x727/FingerprintHub/releases/latest/download/web_fingerprint_v4.json" 76 } 77 func (f *FingerprintHubWebDataSource) CacheFileName() string { return "fingerprinthub-web.json" } 78 func (f *FingerprintHubWebDataSource) OutputFileName() string { 79 return "resources/fingerprinthub_web.json.gz" 80 } 81 82 func (f *FingerprintHubWebDataSource) Download(client *http.Client) error { 83 // 优先使用本地 refer 目录的文件 84 localFile := "refer/FingerprintHub/web_fingerprint_v4.json" 85 if fileExists(localFile) { 86 fmt.Printf("使用本地文件: %s\n", localFile) 87 return copyFile(localFile, f.CacheFileName()) 88 } 89 return downloadFile(client, f.URL(), f.CacheFileName()) 90 } 91 92 func (f *FingerprintHubWebDataSource) Transform() error { 93 return transformJSON(f.CacheFileName(), f.OutputFileName()) 94 } 95 96 // FingerprintHubServiceDataSource fingerprinthub service指纹数据源 97 type FingerprintHubServiceDataSource struct{} 98 99 func (f *FingerprintHubServiceDataSource) Name() string { return "fingerprinthub-service" } 100 func (f *FingerprintHubServiceDataSource) URL() string { 101 return "https://github.com/0x727/FingerprintHub/releases/latest/download/service_fingerprint_v4.json" 102 } 103 func (f *FingerprintHubServiceDataSource) CacheFileName() string { 104 return "fingerprinthub-service.json" 105 } 106 func (f *FingerprintHubServiceDataSource) OutputFileName() string { 107 return "resources/fingerprinthub_service.json.gz" 108 } 109 110 func (f *FingerprintHubServiceDataSource) Download(client *http.Client) error { 111 // 优先使用本地 refer 目录的文件 112 localFile := "refer/FingerprintHub/service_fingerprint_v4.json" 113 if fileExists(localFile) { 114 fmt.Printf("使用本地文件: %s\n", localFile) 115 return copyFile(localFile, f.CacheFileName()) 116 } 117 return downloadFile(client, f.URL(), f.CacheFileName()) 118 } 119 120 func (f *FingerprintHubServiceDataSource) Transform() error { 121 return transformJSON(f.CacheFileName(), f.OutputFileName()) 122 } 123 124 // WappalyzerDataSource wappalyzer数据源 125 type WappalyzerDataSource struct{} 126 127 func (w *WappalyzerDataSource) Name() string { return "wappalyzer" } 128 func (w *WappalyzerDataSource) URL() string { 129 return "https://raw.githubusercontent.com/projectdiscovery/wappalyzergo/main/fingerprints_data.json" 130 } 131 func (w *WappalyzerDataSource) CacheFileName() string { return "wappalyzer.json" } 132 func (w *WappalyzerDataSource) OutputFileName() string { return "resources/wappalyzer.json.gz" } 133 134 func (w *WappalyzerDataSource) Download(client *http.Client) error { 135 // 优先使用本地 refer 目录的文件 136 localFile := "refer/wappalyzergo/fingerprints_data.json" 137 if fileExists(localFile) { 138 fmt.Printf("使用本地文件: %s\n", localFile) 139 return copyFile(localFile, w.CacheFileName()) 140 } 141 return downloadFile(client, w.URL(), w.CacheFileName()) 142 } 143 144 func (w *WappalyzerDataSource) Transform() error { 145 return transformJSON(w.CacheFileName(), w.OutputFileName()) 146 } 147 148 // EholeDataSource ehole数据源 149 type EholeDataSource struct{} 150 151 func (e *EholeDataSource) Name() string { return "ehole" } 152 func (e *EholeDataSource) URL() string { 153 return "https://raw.githubusercontent.com/EdgeSecurityTeam/EHole/master/finger.json" 154 } 155 func (e *EholeDataSource) CacheFileName() string { return "ehole.json" } 156 func (e *EholeDataSource) OutputFileName() string { return "resources/ehole.json.gz" } 157 158 func (e *EholeDataSource) Download(client *http.Client) error { 159 return downloadFile(client, e.URL(), e.CacheFileName()) 160 } 161 162 func (e *EholeDataSource) Transform() error { 163 return transformJSON(e.CacheFileName(), e.OutputFileName()) 164 } 165 166 // GobyDataSource goby数据源 167 type GobyDataSource struct{} 168 169 func (g *GobyDataSource) Name() string { return "goby" } 170 func (g *GobyDataSource) URL() string { 171 return "https://raw.githubusercontent.com/chainreactors/templates/master/goby.json" 172 } 173 func (g *GobyDataSource) CacheFileName() string { return "goby.json" } 174 func (g *GobyDataSource) OutputFileName() string { return "resources/goby.json.gz" } 175 176 func (g *GobyDataSource) Download(client *http.Client) error { 177 return downloadFile(client, g.URL(), g.CacheFileName()) 178 } 179 180 func (g *GobyDataSource) Transform() error { 181 return transformJSON(g.CacheFileName(), g.OutputFileName()) 182 } 183 184 // FingersHTTPDataSource fingers HTTP指纹数据源 185 type FingersHTTPDataSource struct{} 186 187 func (f *FingersHTTPDataSource) Name() string { return "fingers-http" } 188 func (f *FingersHTTPDataSource) URL() string { 189 return "https://github.com/chainreactors/templates" 190 } 191 func (f *FingersHTTPDataSource) CacheFileName() string { return "refer/templates" } 192 func (f *FingersHTTPDataSource) OutputFileName() string { return "resources/fingers_http.json.gz" } 193 194 func (f *FingersHTTPDataSource) Download(client *http.Client) error { 195 return cloneOrPullRepo(f.URL(), f.CacheFileName()) 196 } 197 198 func (f *FingersHTTPDataSource) Transform() error { 199 return transformFingersYAML(f.CacheFileName(), "http", f.OutputFileName()) 200 } 201 202 // FingersSocketDataSource fingers Socket指纹数据源 203 type FingersSocketDataSource struct{} 204 205 func (f *FingersSocketDataSource) Name() string { return "fingers-socket" } 206 func (f *FingersSocketDataSource) URL() string { 207 return "https://github.com/chainreactors/templates" 208 } 209 func (f *FingersSocketDataSource) CacheFileName() string { return "refer/templates" } 210 func (f *FingersSocketDataSource) OutputFileName() string { return "resources/fingers_socket.json.gz" } 211 212 func (f *FingersSocketDataSource) Download(client *http.Client) error { 213 return cloneOrPullRepo(f.URL(), f.CacheFileName()) 214 } 215 216 func (f *FingersSocketDataSource) Transform() error { 217 return transformFingersYAML(f.CacheFileName(), "socket", f.OutputFileName()) 218 } 219 220 // DataManager 数据管理器 221 type DataManager struct { 222 sources map[string]DataSource 223 client *http.Client 224 } 225 226 // NewDataManager 创建数据管理器 227 func NewDataManager(proxyURL string) *DataManager { 228 dm := &DataManager{ 229 sources: make(map[string]DataSource), 230 client: createHTTPClientWithProxy(proxyURL), 231 } 232 233 // 注册数据源 234 dm.RegisterSource(&ProbesDataSource{}) 235 dm.RegisterSource(&ServicesDataSource{}) 236 dm.RegisterSource(&FingerprintHubWebDataSource{}) 237 dm.RegisterSource(&FingerprintHubServiceDataSource{}) 238 dm.RegisterSource(&WappalyzerDataSource{}) 239 dm.RegisterSource(&EholeDataSource{}) 240 dm.RegisterSource(&GobyDataSource{}) 241 dm.RegisterSource(&FingersHTTPDataSource{}) 242 dm.RegisterSource(&FingersSocketDataSource{}) 243 244 return dm 245 } 246 247 // RegisterSource 注册数据源 248 func (dm *DataManager) RegisterSource(source DataSource) { 249 dm.sources[source.Name()] = source 250 } 251 252 // GetSource 获取数据源 253 func (dm *DataManager) GetSource(name string) (DataSource, bool) { 254 source, exists := dm.sources[name] 255 return source, exists 256 } 257 258 // ListSources 列出所有数据源 259 func (dm *DataManager) ListSources() []string { 260 var names []string 261 for name := range dm.sources { 262 names = append(names, name) 263 } 264 return names 265 } 266 267 // Download 下载数据源 268 func (dm *DataManager) Download(sourceName string) error { 269 source, exists := dm.GetSource(sourceName) 270 if !exists { 271 return fmt.Errorf("数据源 '%s' 不存在", sourceName) 272 } 273 274 fmt.Printf("正在下载 %s 从 %s...\n", source.Name(), source.URL()) 275 return source.Download(dm.client) 276 } 277 278 // Transform 转换数据源 279 func (dm *DataManager) Transform(sourceName string) error { 280 source, exists := dm.GetSource(sourceName) 281 if !exists { 282 return fmt.Errorf("数据源 '%s' 不存在", sourceName) 283 } 284 285 fmt.Printf("正在转换 %s 数据为 JSON 格式...\n", source.Name()) 286 return source.Transform() 287 } 288 289 // Update 更新数据源(下载+转换) 290 func (dm *DataManager) Update(sourceName string) error { 291 if err := dm.Download(sourceName); err != nil { 292 return err 293 } 294 return dm.Transform(sourceName) 295 } 296 297 // DownloadAll 下载所有数据源 298 func (dm *DataManager) DownloadAll() error { 299 var errors []string 300 successCount := 0 301 302 for name := range dm.sources { 303 if err := dm.Download(name); err != nil { 304 errMsg := fmt.Sprintf("下载 %s 失败: %v", name, err) 305 fmt.Println("⚠ " + errMsg) 306 errors = append(errors, errMsg) 307 } else { 308 successCount++ 309 } 310 } 311 312 fmt.Printf("\n下载完成: 成功 %d 个, 失败 %d 个\n", successCount, len(errors)) 313 314 if len(errors) > 0 { 315 fmt.Println("\n失败的数据源:") 316 for _, err := range errors { 317 fmt.Println(" - " + err) 318 } 319 } 320 321 return nil 322 } 323 324 // TransformAll 转换所有数据源 325 func (dm *DataManager) TransformAll() error { 326 var errors []string 327 successCount := 0 328 329 for name := range dm.sources { 330 if err := dm.Transform(name); err != nil { 331 errMsg := fmt.Sprintf("转换 %s 失败: %v", name, err) 332 fmt.Println("⚠ " + errMsg) 333 errors = append(errors, errMsg) 334 } else { 335 successCount++ 336 } 337 } 338 339 fmt.Printf("\n转换完成: 成功 %d 个, 失败 %d 个\n", successCount, len(errors)) 340 341 if len(errors) > 0 { 342 fmt.Println("\n失败的数据源:") 343 for _, err := range errors { 344 fmt.Println(" - " + err) 345 } 346 } 347 348 return nil 349 } 350 351 // UpdateAll 更新所有数据源 352 func (dm *DataManager) UpdateAll() error { 353 if err := dm.DownloadAll(); err != nil { 354 return err 355 } 356 return dm.TransformAll() 357 } 358 359 func main() { 360 var proxyURL string 361 flag.StringVar(&proxyURL, "proxy", "", "HTTP代理地址 (例如: http://127.0.0.1:1080)") 362 flag.Parse() 363 364 args := flag.Args() 365 if len(args) < 1 { 366 printUsage() 367 return 368 } 369 370 dm := NewDataManager(proxyURL) 371 command := args[0] 372 373 switch command { 374 case "download": 375 if len(args) == 1 { 376 // 下载所有 377 if err := dm.DownloadAll(); err != nil { 378 log.Fatal(err) 379 } 380 } else { 381 // 下载指定数据源 382 for _, sourceName := range args[1:] { 383 if err := dm.Download(sourceName); err != nil { 384 log.Fatal(err) 385 } 386 } 387 } 388 389 case "transform": 390 if len(args) == 1 { 391 // 转换所有 392 if err := dm.TransformAll(); err != nil { 393 log.Fatal(err) 394 } 395 } else { 396 // 转换指定数据源 397 for _, sourceName := range args[1:] { 398 if err := dm.Transform(sourceName); err != nil { 399 log.Fatal(err) 400 } 401 } 402 } 403 404 case "update": 405 if len(args) == 1 { 406 // 更新所有 407 if err := dm.UpdateAll(); err != nil { 408 log.Fatal(err) 409 } 410 } else { 411 // 更新指定数据源 412 for _, sourceName := range args[1:] { 413 if err := dm.Update(sourceName); err != nil { 414 log.Fatal(err) 415 } 416 } 417 } 418 419 case "list": 420 fmt.Println("可用的数据源:") 421 for _, name := range dm.ListSources() { 422 source, _ := dm.GetSource(name) 423 fmt.Printf(" %-10s - %s\n", name, source.URL()) 424 } 425 426 default: 427 printUsage() 428 } 429 } 430 431 func printUsage() { 432 fmt.Println("数据转换工具") 433 fmt.Println() 434 fmt.Println("用法:") 435 fmt.Println(" go run cmd/transform/transform.go [flags] <command> [sources...]") 436 fmt.Println() 437 fmt.Println("标志:") 438 fmt.Println(" -proxy string HTTP代理地址 (例如: http://127.0.0.1:1080)") 439 fmt.Println() 440 fmt.Println("命令:") 441 fmt.Println(" download [sources...] 下载指定数据源(不指定则下载所有)") 442 fmt.Println(" transform [sources...] 转换指定数据源(不指定则转换所有)") 443 fmt.Println(" update [sources...] 更新指定数据源(不指定则更新所有)") 444 fmt.Println(" list 列出所有可用数据源") 445 fmt.Println() 446 fmt.Println("可用数据源: probes, services, fingerprinthub-web, fingerprinthub-service, wappalyzer, ehole, goby, fingers-http, fingers-socket") 447 fmt.Println() 448 fmt.Println("示例:") 449 fmt.Println(" go run cmd/transform/transform.go list") 450 fmt.Println(" go run cmd/transform/transform.go -proxy http://127.0.0.1:1080 download probes") 451 fmt.Println(" go run cmd/transform/transform.go update services") 452 fmt.Println(" go run cmd/transform/transform.go -proxy http://127.0.0.1:1080 update") 453 fmt.Println(" go run cmd/transform/transform.go download fingerprinthub-web fingerprinthub-service") 454 fmt.Println(" go run cmd/transform/transform.go transform fingers-http fingers-socket") 455 } 456 457 // createHTTPClientWithProxy 创建支持代理的HTTP客户端 458 func createHTTPClientWithProxy(proxyURL string) *http.Client { 459 client := &http.Client{ 460 Timeout: 30 * time.Second, 461 } 462 463 // 如果提供了代理URL,使用代理 464 if proxyURL != "" { 465 parsedProxyURL, err := url.Parse(proxyURL) 466 if err != nil { 467 fmt.Printf("代理URL解析失败: %v,将使用直连\n", err) 468 } else { 469 transport := &http.Transport{ 470 Proxy: http.ProxyURL(parsedProxyURL), 471 } 472 client.Transport = transport 473 fmt.Printf("使用代理: %s\n", proxyURL) 474 } 475 } 476 477 return client 478 } 479 480 // downloadFile 通用文件下载函数 481 func downloadFile(client *http.Client, url, filename string) error { 482 resp, err := client.Get(url) 483 if err != nil { 484 return fmt.Errorf("下载失败: %v", err) 485 } 486 defer resp.Body.Close() 487 488 if resp.StatusCode != 200 { 489 return fmt.Errorf("下载失败,状态码: %d", resp.StatusCode) 490 } 491 492 file, err := os.Create(filename) 493 if err != nil { 494 return fmt.Errorf("创建本地文件失败: %v", err) 495 } 496 defer file.Close() 497 498 _, err = io.Copy(file, resp.Body) 499 if err != nil { 500 return fmt.Errorf("保存文件失败: %v", err) 501 } 502 503 fmt.Printf("✓ 下载完成: %s\n", filename) 504 return nil 505 } 506 507 // writeGzipJSON 写入gzip压缩的JSON文件 508 func writeGzipJSON(data interface{}, filename string) error { 509 // 确保输出目录存在 510 if err := os.MkdirAll(filepath.Dir(filename), 0755); err != nil { 511 return fmt.Errorf("创建输出目录失败: %v", err) 512 } 513 514 // 转换为 JSON 515 jsonData, err := json.MarshalIndent(data, "", " ") 516 if err != nil { 517 return fmt.Errorf("JSON 序列化失败: %v", err) 518 } 519 520 // 创建gzip压缩文件 521 outputFile, err := os.Create(filename) 522 if err != nil { 523 return fmt.Errorf("创建输出文件失败: %v", err) 524 } 525 defer outputFile.Close() 526 527 // 创建gzip写入器 528 gzipWriter := gzip.NewWriter(outputFile) 529 defer gzipWriter.Close() 530 531 // 写入压缩数据 532 _, err = gzipWriter.Write(jsonData) 533 if err != nil { 534 return fmt.Errorf("写入压缩数据失败: %v", err) 535 } 536 537 fmt.Printf("✓ 转换完成: %s\n", filename) 538 539 // 显示文件大小 540 if outputStat, err := os.Stat(filename); err == nil { 541 fmt.Printf(" - 文件大小: %d bytes (%.2f KB)\n", outputStat.Size(), float64(outputStat.Size())/1024) 542 } 543 544 return nil 545 } 546 547 // fileExists 检查文件是否存在 548 func fileExists(filename string) bool { 549 _, err := os.Stat(filename) 550 return !os.IsNotExist(err) 551 } 552 553 // copyFile 复制文件 554 func copyFile(src, dst string) error { 555 sourceData, err := os.ReadFile(src) 556 if err != nil { 557 return fmt.Errorf("读取源文件失败: %v", err) 558 } 559 560 err = os.WriteFile(dst, sourceData, 0644) 561 if err != nil { 562 return fmt.Errorf("写入目标文件失败: %v", err) 563 } 564 565 fmt.Printf("✓ 文件复制完成: %s -> %s\n", src, dst) 566 return nil 567 } 568 569 // Service 代表一个服务条目 570 type Service struct { 571 Name string `json:"name"` 572 Port int `json:"port"` 573 Protocol string `json:"protocol"` 574 Probability float64 `json:"probability"` 575 Comments string `json:"comments,omitempty"` 576 } 577 578 // ServicesData 代表解析后的services数据结构 579 type ServicesData struct { 580 Services []Service `json:"services"` 581 } 582 583 // transformServices 转换services数据 584 func transformServices(cacheFile, outputFile string) error { 585 if !fileExists(cacheFile) { 586 return fmt.Errorf("找不到文件: %s,请先下载", cacheFile) 587 } 588 589 fmt.Printf("使用本地缓存文件: %s\n", cacheFile) 590 content, err := os.ReadFile(cacheFile) 591 if err != nil { 592 return fmt.Errorf("读取本地文件失败: %v", err) 593 } 594 595 fmt.Printf("解析服务数据...\n") 596 data := parseNmapServices(string(content)) 597 598 if err := writeGzipJSON(data, outputFile); err != nil { 599 return err 600 } 601 602 fmt.Printf(" - 服务数量: %d\n", len(data.Services)) 603 fmt.Println() 604 fmt.Println("转换后的 JSON 文件可以用于:") 605 fmt.Println("1. 快速加载服务数据,避免每次解析原始文件") 606 fmt.Println("2. 定期自动更新服务库") 607 fmt.Println("3. 在嵌入式资源中使用") 608 609 return nil 610 } 611 612 // transformProbes 转换probes数据 613 func transformProbes(cacheFile, outputFile string) error { 614 if !fileExists(cacheFile) { 615 return fmt.Errorf("找不到文件: %s,请先下载", cacheFile) 616 } 617 618 fmt.Printf("使用本地缓存文件: %s\n", cacheFile) 619 content, err := os.ReadFile(cacheFile) 620 if err != nil { 621 return fmt.Errorf("读取本地文件失败: %v", err) 622 } 623 624 fmt.Printf("解析探针数据...\n") 625 data := parseAndExportProbes(string(content)) 626 627 fmt.Printf("应用自定义匹配规则...\n") 628 applyCustomNMAPMatch(data) 629 630 probeCount := len(data.Probes) 631 totalMatches := 0 632 for _, probe := range data.Probes { 633 totalMatches += len(probe.MatchGroup) 634 } 635 636 if err := writeGzipJSON(data, outputFile); err != nil { 637 return err 638 } 639 640 fmt.Printf(" - 探针数量: %d\n", probeCount) 641 fmt.Printf(" - 指纹数量: %d\n", totalMatches) 642 fmt.Println() 643 fmt.Println("转换后的 JSON 文件可以用于:") 644 fmt.Println("1. 快速加载探针数据,避免每次解析原始文件") 645 fmt.Println("2. 定期自动更新指纹库") 646 fmt.Println("3. 在嵌入式资源中使用") 647 648 return nil 649 } 650 651 // parseNmapServices 解析nmap-services内容 652 func parseNmapServices(content string) *ServicesData { 653 lines := strings.Split(content, "\n") 654 var services []Service 655 656 for _, line := range lines { 657 line = strings.TrimSpace(line) 658 659 // 跳过空行和注释行 660 if len(line) == 0 || strings.HasPrefix(line, "#") { 661 continue 662 } 663 664 // 解析格式: service-name port/protocol probability [comments] 665 // 例如: http 80/tcp 0.484143 # World Wide Web HTTP 666 parts := strings.Fields(line) 667 if len(parts) < 2 { 668 continue 669 } 670 671 serviceName := parts[0] 672 portProtocol := parts[1] 673 674 // 解析端口和协议 675 portProtocolParts := strings.Split(portProtocol, "/") 676 if len(portProtocolParts) != 2 { 677 continue 678 } 679 680 port, err := strconv.Atoi(portProtocolParts[0]) 681 if err != nil { 682 continue 683 } 684 685 protocol := portProtocolParts[1] 686 687 // 解析概率值 688 var probability float64 = 0.0 689 if len(parts) >= 3 { 690 if prob, err := strconv.ParseFloat(parts[2], 64); err == nil { 691 probability = prob 692 } 693 } 694 695 // 解析注释(如果存在) 696 var comments string 697 if commentIndex := strings.Index(line, "#"); commentIndex != -1 { 698 comments = strings.TrimSpace(line[commentIndex+1:]) 699 } 700 701 service := Service{ 702 Name: serviceName, 703 Port: port, 704 Protocol: protocol, 705 Probability: probability, 706 Comments: comments, 707 } 708 709 services = append(services, service) 710 } 711 712 return &ServicesData{ 713 Services: services, 714 } 715 } 716 717 // parseAndExportProbes 解析探针内容并导出为数据结构 718 func parseAndExportProbes(content string) *gonmap.NmapProbesData { 719 tempNmap := gonmap.NewTempParser(content) 720 721 probes := make([]*gonmap.Probe, 0, len(tempNmap.GetProbes())) 722 for _, probe := range tempNmap.GetProbes() { 723 probeCopy := *probe 724 probes = append(probes, &probeCopy) 725 } 726 727 return &gonmap.NmapProbesData{ 728 Probes: probes, 729 Services: make(map[string]string), 730 } 731 } 732 733 // applyCustomNMAPMatch 应用自定义匹配规则 734 func applyCustomNMAPMatch(data *gonmap.NmapProbesData) { 735 probeMap := make(map[string]*gonmap.Probe) 736 for _, probe := range data.Probes { 737 probeMap[probe.Name] = probe 738 } 739 740 addCustomMatch := func(probeName, matchExpr string) { 741 if probe, exists := probeMap[probeName]; exists { 742 probe.LoadMatch(matchExpr, false) 743 } 744 } 745 746 // 新增自定义指纹信息 747 addCustomMatch("TCP_GetRequest", `echo m|^GET / HTTP/1.0\r\n\r\n$|s`) 748 addCustomMatch("TCP_GetRequest", `mongodb m|.*It looks like you are trying to access MongoDB.*|s p/MongoDB/`) 749 addCustomMatch("TCP_GetRequest", `http m|^HTTP/1\.[01] \d\d\d (?:[^\r\n]+\r\n)*?Server: ([^\r\n]+)| p/$1/`) 750 addCustomMatch("TCP_GetRequest", `http m|^HTTP/1\.[01] \d\d\d|`) 751 addCustomMatch("TCP_NULL", `mysql m|.\x00\x00..j\x04Host '.*' is not allowed to connect to this MariaDB server| p/MariaDB/`) 752 addCustomMatch("TCP_NULL", `mysql m|.\x00\x00..j\x04Host '.*' is not allowed to connect to this MySQL server| p/MySQL/`) 753 addCustomMatch("TCP_NULL", `mysql m|.\x00\x00\x00\x0a(\d+\.\d+\.\d+)\x00.*caching_sha2_password\x00| p/MariaDB/ v/$1/`) 754 addCustomMatch("TCP_NULL", `mysql m|.\x00\x00\x00\x0a([\d.-]+)-MariaDB\x00.*mysql_native_password\x00| p/MariaDB/ v/$1/`) 755 addCustomMatch("TCP_NULL", `redis m|-DENIED Redis is running in.*| p/Redis/ i/Protected mode/`) 756 addCustomMatch("TCP_NULL", `telnet m|^.*Welcome to visit (.*) series router!.*|s p/$1 Router/`) 757 addCustomMatch("TCP_NULL", `telnet m|^Username: ??|`) 758 addCustomMatch("TCP_NULL", `telnet m|^.*Telnet service is disabled or Your telnet session has expired due to inactivity.*|s i/Disabled/`) 759 addCustomMatch("TCP_NULL", `telnet m|^.*Telnet connection from (.*) refused.*|s i/Refused/`) 760 addCustomMatch("TCP_NULL", `telnet m|^.*Command line is locked now, please retry later.*\x0d\x0a\x0d\x0a|s i/Locked/`) 761 addCustomMatch("TCP_NULL", `telnet m|^.*Warning: Telnet is not a secure protocol, and it is recommended to use Stelnet.*|s`) 762 addCustomMatch("TCP_NULL", `telnet m|^telnetd:|s`) 763 addCustomMatch("TCP_NULL", `telnet m|^.*Quopin CLI for (.*)\x0d\x0a\x0d\x0a|s p/$1/`) 764 addCustomMatch("TCP_NULL", `telnet m|^\x0d\x0aHello, this is FRRouting \(version ([\d.]+)\).*|s p/FRRouting/ v/$1/`) 765 addCustomMatch("TCP_NULL", `telnet m|^.*User Access Verification.*Username:|s`) 766 addCustomMatch("TCP_NULL", `telnet m|^Connection failed. Windows CE Telnet Service cannot accept anymore concurrent users.|s o/Windows/`) 767 addCustomMatch("TCP_NULL", `telnet m|^\x0d\x0a\x0d\x0aWelcome to the host.\x0d\x0a.*|s o/Windows/`) 768 addCustomMatch("TCP_NULL", `telnet m|^.*Welcome Visiting Huawei Home Gateway\x0d\x0aCopyright by Huawei Technologies Co., Ltd.*Login:|s p/Huawei/`) 769 } 770 771 // transformJSON 转换JSON数据(直接压缩) 772 func transformJSON(cacheFile, outputFile string) error { 773 if !fileExists(cacheFile) { 774 return fmt.Errorf("找不到文件: %s,请先下载", cacheFile) 775 } 776 777 fmt.Printf("使用本地缓存文件: %s\n", cacheFile) 778 content, err := os.ReadFile(cacheFile) 779 if err != nil { 780 return fmt.Errorf("读取本地文件失败: %v", err) 781 } 782 783 // 验证JSON格式 784 var data interface{} 785 if err := json.Unmarshal(content, &data); err != nil { 786 return fmt.Errorf("JSON格式验证失败: %v", err) 787 } 788 789 // 确保输出目录存在 790 if err := os.MkdirAll(filepath.Dir(outputFile), 0755); err != nil { 791 return fmt.Errorf("创建输出目录失败: %v", err) 792 } 793 794 // 创建gzip压缩文件 795 outFile, err := os.Create(outputFile) 796 if err != nil { 797 return fmt.Errorf("创建输出文件失败: %v", err) 798 } 799 defer outFile.Close() 800 801 // 创建gzip写入器 802 gzipWriter := gzip.NewWriter(outFile) 803 defer gzipWriter.Close() 804 805 // 写入压缩数据 806 _, err = gzipWriter.Write(content) 807 if err != nil { 808 return fmt.Errorf("写入压缩数据失败: %v", err) 809 } 810 811 fmt.Printf("✓ 转换完成: %s\n", outputFile) 812 813 // 显示文件大小 814 if outputStat, err := os.Stat(outputFile); err == nil { 815 fmt.Printf(" - 文件大小: %d bytes (%.2f KB)\n", outputStat.Size(), float64(outputStat.Size())/1024) 816 } 817 818 return nil 819 } 820 821 // cloneOrPullRepo 克隆或更新Git仓库 822 func cloneOrPullRepo(repoURL, targetDir string) error { 823 // 检查目录是否存在 824 if fileExists(targetDir) { 825 fmt.Printf("仓库已存在,正在更新: %s\n", targetDir) 826 // 使用 exec.Command 并设置工作目录 827 cmd := exec.Command("git", "pull") 828 cmd.Dir = targetDir 829 output, err := cmd.CombinedOutput() 830 if err != nil { 831 return fmt.Errorf("更新仓库失败: %v: %s", err, string(output)) 832 } 833 if len(output) > 0 { 834 fmt.Printf("%s\n", string(output)) 835 } 836 fmt.Printf("✓ 仓库更新完成: %s\n", targetDir) 837 } else { 838 fmt.Printf("正在克隆仓库: %s\n", repoURL) 839 // 确保父目录存在 840 parentDir := filepath.Dir(targetDir) 841 if err := os.MkdirAll(parentDir, 0755); err != nil { 842 return fmt.Errorf("创建目录失败: %v", err) 843 } 844 // 执行 git clone --depth 1 845 cmd := exec.Command("git", "clone", "--depth", "1", repoURL, targetDir) 846 output, err := cmd.CombinedOutput() 847 if err != nil { 848 return fmt.Errorf("克隆仓库失败: %v: %s", err, string(output)) 849 } 850 if len(output) > 0 { 851 fmt.Printf("%s\n", string(output)) 852 } 853 fmt.Printf("✓ 仓库克隆完成: %s\n", targetDir) 854 } 855 return nil 856 } 857 858 // executeCommand 执行shell命令 859 func executeCommand(cmd string) error { 860 fmt.Printf("执行命令: %s\n", cmd) 861 // 使用 os/exec 执行命令 862 var shellCmd *exec.Cmd 863 if runtime.GOOS == "windows" { 864 shellCmd = exec.Command("cmd", "/C", cmd) 865 } else { 866 shellCmd = exec.Command("sh", "-c", cmd) 867 } 868 869 output, err := shellCmd.CombinedOutput() 870 if err != nil { 871 return fmt.Errorf("%v: %s", err, string(output)) 872 } 873 874 if len(output) > 0 { 875 fmt.Printf("%s\n", string(output)) 876 } 877 878 return nil 879 } 880 881 // transformFingersYAML 转换Fingers YAML指纹数据 882 func transformFingersYAML(repoDir, fingerprintType, outputFile string) error { 883 if !fileExists(repoDir) { 884 return fmt.Errorf("找不到目录: %s,请先下载", repoDir) 885 } 886 887 // 根据类型确定扫描目录 888 var scanDir string 889 if fingerprintType == "http" { 890 scanDir = filepath.Join(repoDir, "fingers", "http") 891 } else if fingerprintType == "socket" { 892 scanDir = filepath.Join(repoDir, "fingers", "socket") 893 } else { 894 return fmt.Errorf("不支持的指纹类型: %s", fingerprintType) 895 } 896 897 if !fileExists(scanDir) { 898 return fmt.Errorf("找不到目录: %s", scanDir) 899 } 900 901 fmt.Printf("正在收集 %s 类型的指纹...\n", fingerprintType) 902 fmt.Printf("扫描目录: %s\n", scanDir) 903 904 var fingerprints []map[string]interface{} 905 906 // 递归遍历目录 907 err := filepath.Walk(scanDir, func(path string, info os.FileInfo, err error) error { 908 if err != nil { 909 return err 910 } 911 912 // 跳过目录 913 if info.IsDir() { 914 return nil 915 } 916 917 // 只处理 .yaml 和 .yml 文件 918 ext := strings.ToLower(filepath.Ext(path)) 919 if ext != ".yaml" && ext != ".yml" { 920 return nil 921 } 922 923 // 读取文件 924 content, err := os.ReadFile(path) 925 if err != nil { 926 fmt.Printf("警告: 读取文件失败 %s: %v\n", path, err) 927 return nil 928 } 929 930 // 尝试解析为对象 931 var dataMap map[string]interface{} 932 if err := yaml.Unmarshal(content, &dataMap); err == nil { 933 fingerprints = append(fingerprints, dataMap) 934 return nil 935 } 936 937 // 尝试解析为数组 938 var dataArray []map[string]interface{} 939 if err := yaml.Unmarshal(content, &dataArray); err == nil { 940 // 将数组中的每个元素添加到指纹列表 941 fingerprints = append(fingerprints, dataArray...) 942 return nil 943 } 944 945 // 两种格式都解析失败 946 fmt.Printf("警告: 解析YAML失败 %s\n", path) 947 948 return nil 949 }) 950 951 if err != nil { 952 return fmt.Errorf("遍历目录失败: %v", err) 953 } 954 955 fmt.Printf("收集到 %d 个 %s 指纹\n", len(fingerprints), fingerprintType) 956 957 // 写入 gzip 压缩的 JSON 958 if err := writeGzipJSON(fingerprints, outputFile); err != nil { 959 return err 960 } 961 962 fmt.Printf(" - 指纹数量: %d\n", len(fingerprints)) 963 return nil 964 } 965 966 // shouldIncludeFingerprint 判断指纹是否应该包含在指定类型中 967 func shouldIncludeFingerprint(data map[string]interface{}, fingerprintType string) bool { 968 // 检查 protocol 字段(fingers 格式) 969 if protocol, ok := data["protocol"].(string); ok { 970 if fingerprintType == "http" { 971 return protocol == "http" || protocol == "https" 972 } else if fingerprintType == "socket" { 973 return protocol == "tcp" || protocol == "udp" 974 } 975 } 976 977 // 兼容 neutron/nuclei 格式 978 if fingerprintType == "http" { 979 // HTTP 指纹包含 http 或 requests 字段 980 if _, hasHTTP := data["http"]; hasHTTP { 981 return true 982 } 983 if _, hasRequests := data["requests"]; hasRequests { 984 return true 985 } 986 return false 987 } else if fingerprintType == "socket" { 988 // Socket 指纹包含 network, tcp, udp 字段 989 if _, hasNetwork := data["network"]; hasNetwork { 990 return true 991 } 992 if _, hasTCP := data["tcp"]; hasTCP { 993 return true 994 } 995 if _, hasUDP := data["udp"]; hasUDP { 996 return true 997 } 998 return false 999 } 1000 return false 1001 }