github.com/chainreactors/fingers@v1.2.1/nmap/gonmap_test.go (about)

     1  package gonmap
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"os"
     7  	"strings"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/chainreactors/fingers/common"
    12  	"github.com/chainreactors/fingers/resources"
    13  )
    14  
    15  // TestParseLogic 测试解析逻辑
    16  func TestParseLogic(t *testing.T) {
    17  	content, err := os.ReadFile("../resources/nmap-service-probes.txt")
    18  	if err != nil {
    19  		t.Skip("nmap-service-probes.txt not available, skipping parse logic test")
    20  	}
    21  
    22  	fmt.Printf("\n=== 解析逻辑测试 ===\n")
    23  
    24  	// 统计原始文件中的Probe行数
    25  	lines := strings.Split(string(content), "\n")
    26  	probeLineCount := 0
    27  	tcpProbeCount := 0
    28  	udpProbeCount := 0
    29  
    30  	for _, line := range lines {
    31  		if strings.HasPrefix(line, "Probe TCP") {
    32  			tcpProbeCount++
    33  			probeLineCount++
    34  		} else if strings.HasPrefix(line, "Probe UDP") {
    35  			udpProbeCount++
    36  			probeLineCount++
    37  		}
    38  	}
    39  
    40  	fmt.Printf("原始文件统计:\n")
    41  	fmt.Printf("  - Probe行总数: %d\n", probeLineCount)
    42  	fmt.Printf("  - TCP Probe: %d\n", tcpProbeCount)
    43  	fmt.Printf("  - UDP Probe: %d\n", udpProbeCount)
    44  
    45  	// 使用TempParser解析
    46  	parser := NewTempParser(string(content))
    47  	probes := parser.GetProbes()
    48  
    49  	fmt.Printf("\n解析结果:\n")
    50  	fmt.Printf("  - 解析出的Probe数量: %d\n", len(probes))
    51  
    52  	// 统计解析出的TCP和UDP probe
    53  	parsedTCP := 0
    54  	parsedUDP := 0
    55  	for _, probe := range probes {
    56  		if strings.HasPrefix(probe.Name, "TCP_") {
    57  			parsedTCP++
    58  		} else if strings.HasPrefix(probe.Name, "UDP_") {
    59  			parsedUDP++
    60  		}
    61  	}
    62  
    63  	fmt.Printf("  - TCP Probe: %d\n", parsedTCP)
    64  	fmt.Printf("  - UDP Probe: %d\n", parsedUDP)
    65  
    66  	// 检查是否有差异
    67  	if probeLineCount != len(probes) {
    68  		fmt.Printf("\n⚠️  警告: 原始文件有%d个Probe行,但只解析出%d个Probe\n", probeLineCount, len(probes))
    69  		fmt.Printf("差异: %d个Probe未被解析\n", probeLineCount-len(probes))
    70  	} else {
    71  		fmt.Printf("\n✓ 解析完整,所有Probe都被正确解析\n")
    72  	}
    73  }
    74  func TestJSONDataStructure(t *testing.T) {
    75  	// 解压缩数据
    76  	decompressedProbes, err := resources.DecompressGzip(resources.NmapServiceProbesData)
    77  	if err != nil {
    78  		t.Fatalf("Failed to decompress probes data: %v", err)
    79  	}
    80  
    81  	decompressedServices, err := resources.DecompressGzip(resources.NmapServicesData)
    82  	if err != nil {
    83  		t.Fatalf("Failed to decompress services data: %v", err)
    84  	}
    85  
    86  	// 直接解析JSON查看原始数据
    87  	var data NmapProbesData
    88  	if err := json.Unmarshal(decompressedProbes, &data); err != nil {
    89  		t.Fatalf("Failed to unmarshal JSON: %v", err)
    90  	}
    91  
    92  	// 解析services数据
    93  	var servicesData ServicesData
    94  	if err := json.Unmarshal(decompressedServices, &servicesData); err != nil {
    95  		t.Fatalf("Failed to unmarshal services JSON: %v", err)
    96  	}
    97  
    98  	fmt.Printf("\n=== JSON Data Structure ===\n")
    99  	fmt.Printf("Probes in JSON: %d\n", len(data.Probes))
   100  	fmt.Printf("Services in JSON: %d\n", len(servicesData.Services))
   101  	fmt.Printf("Decompressed probes size: %d bytes\n", len(decompressedProbes))
   102  	fmt.Printf("Decompressed services size: %d bytes\n", len(decompressedServices))
   103  
   104  	// 统计match总数
   105  	totalMatches := 0
   106  	totalSoftMatches := 0
   107  	for _, probe := range data.Probes {
   108  		for _, match := range probe.MatchGroup {
   109  			if match.Soft {
   110  				totalSoftMatches++
   111  			}
   112  		}
   113  		totalMatches += len(probe.MatchGroup)
   114  	}
   115  	fmt.Printf("\nTotal match rules: %d\n", totalMatches)
   116  	fmt.Printf("  - Hard matches: %d\n", totalMatches-totalSoftMatches)
   117  	fmt.Printf("  - Soft matches: %d\n", totalSoftMatches)
   118  }
   119  
   120  func TestScan(t *testing.T) {
   121  	if testing.Short() {
   122  		t.Skip("skipping live network test in short mode")
   123  	}
   124  	engine, err := NewNmapEngine(resources.NmapServiceProbesData, resources.NmapServicesData)
   125  	if err != nil {
   126  		t.Fatalf("Failed to create nmap engine: %v", err)
   127  	}
   128  
   129  	// Test port 135 MSRPC detection
   130  	fmt.Println("=== Testing Port 135 MSRPC Detection ===")
   131  	res := engine.ServiceMatch("127.0.0.1", "135", 2, common.NewServiceSender(time.Second*5), nil)
   132  
   133  	if res != nil && res.Framework != nil {
   134  		fmt.Printf("✓ Successfully detected service on port 135\n")
   135  		fmt.Printf("  Service: %s, Product: %s\n", res.Framework.Name, res.Framework.Product)
   136  	} else {
   137  		fmt.Println("⚠ No service detected on port 135 (may not be running)")
   138  	}
   139  }
   140  
   141  // TestNoGuessFlag verifies that the NoGuess flag is respected
   142  func TestNoGuessFlag(t *testing.T) {
   143  	engine, err := NewNmapEngine(resources.NmapServiceProbesData, resources.NmapServicesData)
   144  	if err != nil {
   145  		t.Fatalf("Failed to create nmap engine: %v", err)
   146  	}
   147  
   148  	fmt.Println("\n=== Testing NoGuess Flag Behavior ===")
   149  
   150  	// Save original NoGuess value and restore after test
   151  	originalNoGuess := common.NoGuess
   152  	defer func() {
   153  		common.NoGuess = originalNoGuess
   154  	}()
   155  
   156  	// Test 1: NoGuess = false (default) - guess should work
   157  	fmt.Println("\n1. Testing with NoGuess = false (guess enabled)")
   158  	common.NoGuess = false
   159  
   160  	// Test against a high port that's unlikely to be running but might be guessable
   161  	// Using port 9999 which is unlikely to have a service but has a guess entry
   162  	res1 := engine.ServiceMatch("127.0.0.1", "9999", 1, common.NewServiceSender(time.Second*2), nil)
   163  	if res1 != nil && res1.Framework != nil && res1.Framework.IsGuess() {
   164  		fmt.Printf("   ✓ Guess result returned: %s (from: %s)\n", res1.Framework.Name, res1.Framework.From.String())
   165  	} else {
   166  		fmt.Println("   ⚠ No guess result (port may be closed or matched exactly)")
   167  	}
   168  
   169  	// Test 2: NoGuess = true - guess should be skipped
   170  	fmt.Println("\n2. Testing with NoGuess = true (guess disabled)")
   171  	common.NoGuess = true
   172  
   173  	res2 := engine.ServiceMatch("127.0.0.1", "9999", 1, common.NewServiceSender(time.Second*2), nil)
   174  	if res2 == nil {
   175  		fmt.Println("   ✓ No result returned when NoGuess is true (correct behavior)")
   176  	} else if res2.Framework != nil && !res2.Framework.IsGuess() {
   177  		fmt.Printf("   ✓ Non-guess result returned: %s (from: %s)\n", res2.Framework.Name, res2.Framework.From.String())
   178  	} else if res2.Framework != nil && res2.Framework.IsGuess() {
   179  		t.Errorf("   ✗ Guess result returned when NoGuess is true (incorrect behavior)")
   180  	}
   181  
   182  	fmt.Println("\n✓ NoGuess flag test completed")
   183  }
   184  
   185  // TestProbeDataIntegrity verifies that binary probe data is correctly preserved
   186  func TestProbeDataIntegrity(t *testing.T) {
   187  	n := &Nmap{
   188  		probeNameMap:      make(map[string]*Probe),
   189  		rarityProbeMap:    make(map[int][]*Probe),
   190  		portProbeMap:      make(map[int]ProbeList),
   191  		sslSecondProbeMap: make(ProbeList, 0),
   192  		sslProbeMap:       make(ProbeList, 0),
   193  	}
   194  
   195  	// 解压缩数据
   196  	decompressedProbes, err := resources.DecompressGzip(resources.NmapServiceProbesData)
   197  	if err != nil {
   198  		t.Fatalf("Failed to decompress probes data: %v", err)
   199  	}
   200  
   201  	decompressedServices, err := resources.DecompressGzip(resources.NmapServicesData)
   202  	if err != nil {
   203  		t.Fatalf("Failed to decompress services data: %v", err)
   204  	}
   205  
   206  	n.loadProbesFromBytes(decompressedProbes)
   207  	n.loadServicesFromBytes(decompressedServices)
   208  
   209  	// 输出探针统计信息
   210  	fmt.Printf("\n=== Probe Statistics ===\n")
   211  	fmt.Printf("Total probes loaded: %d\n", len(n.probeNameMap))
   212  	fmt.Printf("Decompressed probes data size: %d bytes\n", len(decompressedProbes))
   213  	fmt.Printf("Decompressed services data size: %d bytes\n", len(decompressedServices))
   214  
   215  	// 统计match总数
   216  	totalMatches := 0
   217  	for _, probe := range n.probeNameMap {
   218  		totalMatches += len(probe.MatchGroup)
   219  	}
   220  	fmt.Printf("Total match rules: %d\n", totalMatches)
   221  
   222  	// 列出前10个探针名称及其match数量
   223  	fmt.Printf("\nFirst 10 probes with match counts:\n")
   224  	count := 0
   225  	for name, probe := range n.probeNameMap {
   226  		if count >= 10 {
   227  			break
   228  		}
   229  		fmt.Printf("  - %s: %d matches\n", name, len(probe.MatchGroup))
   230  		count++
   231  	}
   232  
   233  	// Verify SMBProgNeg probe data integrity
   234  	probe, exists := n.probeNameMap["TCP_SMBProgNeg"]
   235  	if !exists {
   236  		t.Fatal("TCP_SMBProgNeg probe not found")
   237  	}
   238  
   239  	// Check data length (should be 168 bytes for SMBProgNeg)
   240  	fmt.Printf("\nSMBProgNeg probe SendRaw length: %d bytes\n", len(probe.SendRaw))
   241  	if len(probe.SendRaw) != 168 {
   242  		t.Errorf("Expected probe data length 168, got %d", len(probe.SendRaw))
   243  	}
   244  
   245  	// Check for UTF-8 replacement characters (0xef 0xbf 0xbd)
   246  	// These indicate corrupted binary data
   247  	for i := 0; i < len(probe.SendRaw)-2; i++ {
   248  		if probe.SendRaw[i] == 0xef && probe.SendRaw[i+1] == 0xbf && probe.SendRaw[i+2] == 0xbd {
   249  			t.Errorf("Found UTF-8 replacement character at position %d - binary data is corrupted", i)
   250  		}
   251  	}
   252  
   253  	// Verify specific bytes that should be present (0xa4 and 0xff from SMB header)
   254  	if probe.SendRaw[3] != 0xa4 {
   255  		t.Errorf("Expected byte 0xa4 at position 3, got 0x%02x", probe.SendRaw[3])
   256  	}
   257  	if probe.SendRaw[4] != 0xff {
   258  		t.Errorf("Expected byte 0xff at position 4, got 0x%02x", probe.SendRaw[4])
   259  	}
   260  }