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  }