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  //}