nmap 之服务探测

2021年09月15日 阅读数:2
这篇文章主要向大家介绍nmap 之服务探测,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

1、概要说明正则表达式

对目标主机的开放端口(主机发现模块识别出的)发送特定的探测报文;经过预约义的正则表达式规则, 对目标主机的响应数据进行规则的匹配,判断出目标主机上运行的服务以及版本等信息。并发

2、服务探测app

1.预约义规则文件less

总体结构异步

Probe protocol Name 探测报文
ports xxx,xxx
...
match serviceName 正则表达式 版本信息... 
softmatch serviceName 正则表达式
...

1.1 全局排除端口配置tcp

排除端口,不对如下端口探测ide

Exclude T:9100-9107

--allports 选项能够忽略此配置,直接所有端口都要探测函数

1.2 探测报文配置oop

Probe TCP/UDP 探测名字 q“分隔字符”探测内容“分隔字符”ui

clipboard.png

1.3 探测属性配置

#关键词 值
rarity <1-9> default 5 
ports port1,port2,port3-port4,..
sslports  port1,port2,port3-port4,..
fallback 探测名称1,探测名称2,...
totalwaitms <100-300000> ms default 5000
tcpwrappedms <100-300000> ms default 2000

rarity表示此条探测的等级,取值范围为<1-9>, 默认值为5

在服务探测过程当中能够根据此值进行过滤,减小一些服务的探测, 大于o.version_intersity值的服务将不会探测。

(*current_probe)->getRarity() <= o.version_intensity)

o.version_intersity默认值为7,可经过--version-intensity进行这设置,<1-9>

--version-light 将设置为2

--version-all 将设置为9

fallback 此探测报文的返回结果使用其余的探测规则进行匹配,减小重复的规则

clipboard.png

1.4探测规则配置

match/softmatch 服务名 m"分割字符"正则表达式"分割字符""可选字符(i/s)" 其余信息
可选字符i: 忽略大小写
可选字符s: .匹配换行
其余信息: ?/.../
    ?表示为p, v, i, h, o, or d; p:product, v:version, i:info, h:hostname, o:ostype, d:devicetype, cpe:
    /为分割字符
    ...为具体内容

clipboard.png

2.探测流程

2.1 总体逻辑图

clipboard.png

2.2 开关

默认不进行服务探测,须要开启。

2.2.1 使用-sV or -sR选项,打开服务探测,-sR选项不推荐。

parse_options()
    ...
     while ((arg = getopt_long_only(argc, argv, "46Ab:D:d::e:Ffg:hIi:M:m:nO::o:P::p:qRrS:s::T:Vv::", long_options, &option_index)) != EOF) {
     switch (arg) {
         ...
         case 's':
           ...
          p = optarg;
          while (*p) {
            switch (*p) {
            ...
            case 'R':
              o.servicescan = true;
              delayed_options.warn_deprecated("sR", "sV");
              error("WARNING: -sR is now an alias for -sV and activates version detection as well as RPC scan.");
              break;
            case 'V':
              o.servicescan = true;
              break;
              ...
              }
       }

2.1.2 使用-A 选项,将默认开启服务探测,操做系统探测,脚本探测三项

parse_options()
    ...
     while ((arg = getopt_long_only(argc, argv, "46Ab:D:d::e:Ffg:hIi:M:m:nO::o:P::p:qRrS:s::T:Vv::", long_options, &option_index)) != EOF) {
     switch (arg) {
         ...
         case 'A':
          delayed_options.advanced = true;
          break;
          ...
          }
      }
  
main()
    ...          
    if (delayed_options.advanced) {
        o.servicescan = true;
        o.script = true;
        o.osscan = true;
          ...
      }
      ...

2.1.3 开启服务探测后,将进行服务探测

nmap_main()
    ...
    do {
        ...
         if (o.servicescan) {
            o.current_scantype = SERVICE_SCAN;
            service_scan(Targets);
          }
        ...
    }while(!o.max_ips_to_scan || o.max_ips_to_scan > o.numhosts_scanned);

2.3 规则加载

service_scan_init() ->
    parse_nmap_service_probes() ->
        parse_nmap_service_probe_file()

2.2.1 解析探测配置文件,将全部的探测加载到一个std::vector<ServiceProbe *> probes中

clipboard.png

2.2.2 根据协议将规则,解析fallbackStr,将每一个探测规则整合

compileFallbacks()
    ...
    getProbeByName()
    ...

clipboard.png

2.2.3 每一个探测中的规则预先正则编译后,加入std::vector<ServiceProbeMatch *> matches

InitMatch()
    ...
    pcre_compile()
    ...

端口解析后,根据是否使用ssl分别存入  std::vector<u16> probableports;和std::vector<u16> probablesslports;

setProbablePorts()->
    setPortVector()

clipboard.png

2.4 服务探测

对特定的端口,发送特定的数据包,根据正则进行匹配。

2.4.1 探测端口

对哪些端口进行探测。

  • 将端口扫描结果中open状态的端口做为扫描目标
for(targetno = 0 ; targetno < Targets.size(); targetno++) {
    nxtport = NULL;
    ...
    while((nxtport = Targets[targetno]->ports.nextPort(nxtport, &port, TCPANDUDPANDSCTP, PORT_OPEN))) {
      svc = new ServiceNFO(AP);
      svc->target = Targets[targetno];
      svc->portno = nxtport->portno;
      svc->proto = nxtport->proto;
      services_remaining.push_back(svc);
    }
  }
  • 将端口扫描结果中的open filtered状态的端口做为扫描目标
 for(targetno = 0 ; targetno < Targets.size(); targetno++) {
    nxtport = NULL;
   ...
    while((nxtport = Targets[targetno]->ports.nextPort(nxtport, &port, TCPANDUDPANDSCTP, PORT_OPENFILTERED))) {
      svc = new ServiceNFO(AP);
      svc->target = Targets[targetno];
      svc->portno = nxtport->portno;
      svc->proto = nxtport->proto;
      services_remaining.push_back(svc);
    }
  }

2.4.2 探测

须要探测哪些服务,发送哪些探测报文

launchSomeServiceProbes()
    //遍历扫描目标端口
    while (SG->services_in_progress.size() < SG->ideal_parallelism &&
         !SG->services_remaining.empty()) {
    // Start executing a probe from the new list and move it to in_progress
    
    //1.从链表头取一个
    svc = SG->services_remaining.front();
    
    //2.检测此目标是否已经超时,若是超时,则直接结束此目标的探测,并进入下一个探测
    if (svc->target->timedOut(nsock_gettimeofday())) {
      end_svcprobe(nsp, PROBESTATE_INCOMPLETE, SG, svc, NULL);
      continue;
    }
    else if (!svc->target->timeOutClockRunning()) {
      svc->target->startTimeOutClock(nsock_gettimeofday());
    }
    
    //3.根据端口查找须要探测的报文信息
    nextprobe = svc->nextProbe(true);

    //没有对应端口探测配置,下一个端口
    if (nextprobe == NULL) {
      ...
      end_svcprobe(nsp, PROBESTATE_FINISHED_NOMATCH, SG, svc, NULL);
      continue;
    }
    ...
    //4.获取目标IP地址
    svc->target->TargetSockAddr(&ss, &ss_len);
    
    //5.根据tcp/udp进行连接操做,而且设置相应的回调函数
    if (svc->proto == IPPROTO_TCP)
      nsock_connect_tcp(nsp, svc->niod, servicescan_connect_handler,
                        DEFAULT_CONNECT_TIMEOUT, svc,
                        (struct sockaddr *)&ss, ss_len,
                        svc->portno);
    else {
      assert(svc->proto == IPPROTO_UDP);
      nsock_connect_udp(nsp, svc->niod, servicescan_connect_handler,
                        svc, (struct sockaddr *) &ss, ss_len,
                        svc->portno);
    }
    ...
  }
nextProbe()
...
    //查询使用哪一个探测
    while (current_probe != AP->probes.end()) {
         // For the first run, we only do probes that match this port number
         //1.相同的协议 TCP/UDP
         //2.此端口在这个探测端口列表中
         //3.硬匹配 or  version_intensity >= 9 or 探测结果中服务名在探测匹配列表中
         if ((proto == (*current_probe)->getProbeProtocol()) &&
             (*current_probe)->portIsProbable(tunnel, portno) &&
             // Skip the probe if we softmatched and the service isn't available via this probe.
             // --version-all avoids this optimization here and in PROBESTATE_NONMATCHINGPROBES below.
             (!softMatchFound || o.version_intensity >= 9 || (*current_probe)->serviceIsPossible(probe_matched))) {
           // This appears to be a valid probe.  Let's do it!
           return *current_probe;
         }
         current_probe++;
       }
 //上面的探测都失败了,进行下面的再次探测
 if (!dropdown && current_probe != AP->probes.end()) current_probe++;
   while (current_probe != AP->probes.end()) {
     // The protocol must be right, it must be a nonmatching port ('cause we did those),
     // and we better either have no soft match yet, or the soft service match must
     // be available via this probe. Also, the Probe's rarity must be <= to our
     // version detection intensity level.
     if ((proto == (*current_probe)->getProbeProtocol()) &&
         !(*current_probe)->portIsProbable(tunnel, portno) &&
         // No softmatch so obey intensity, or
         ((!softMatchFound && (*current_probe)->getRarity() <= o.version_intensity) ||
         // Softmatch, so only require service match (no rarity check)
         (softMatchFound && (o.version_intensity >= 9 || (*current_probe)->serviceIsPossible(probe_matched))))) {
       // Valid, probe.  Let's do it!
       return *current_probe;
     }
     current_probe++;
   }

2.4.3 主循环,处理事件

nsock_loop()
    ...
    while (1) {
        ...
        if (nsock_engine_loop(ms, msecs_left) == -1) {
          quitstatus = NSOCK_LOOP_ERROR;
          break;
        }
       ...
  }

2.4.4如何匹配

遍历当前探测中的匹配规则,依次匹配

匹配上一个则返回

servicescan_read_handler()
    ...
    int fallbackDepth=0;
    for (MD = NULL; probe->fallbacks[fallbackDepth] != NULL; fallbackDepth++) {
          MD = (probe->fallbacks[fallbackDepth])->testMatch(readstr, readstrlen);
          if (MD && MD->serviceName) break; // Found one!
    }
    ...

const struct MatchDetails *ServiceProbe::testMatch(const u8 *buf, int buflen, int n = 0) {
  std::vector<ServiceProbeMatch *>::iterator vi;
  const struct MatchDetails *MD;

  for(vi = matches.begin(); vi != matches.end(); vi++) {
    MD = (*vi)->testMatch(buf, buflen);
    if (MD->serviceName) {
      if (n == 0)
        return MD;
      n--;
    }
  }

  return NULL;
}

2.4.5匹配上一个后,后续处理逻辑,还匹配其余么

match 匹配上后,若是不须要ssl匹配,则直接结束, 不然进行ssl的再次探测。

softmatch 匹配上后,须要继续匹配。

if (MD && MD->serviceName) {
      // WOO HOO!!!!!!  MATCHED!  But might be soft
      if (MD->isSoft && svc->probe_matched) {
       ...
      } else {
       ...
        svc->softMatchFound = MD->isSoft;
        if (!svc->softMatchFound) {
          // We might be able to continue scan through a tunnel protocol
          // like SSL
          if (scanThroughTunnel(nsp, nsi, SG, svc) == 0)
            end_svcprobe(nsp, PROBESTATE_FINISHED_HARDMATCHED, SG, svc, nsi);
        }
      }
    }

2.4.6 结果合并

proce***esults()
    setServiceProbeResults()

2.4.7 服务名

  1. 探测出来的名字
  2. tcpwrapper,(除了超时和真实返回数据)
  3. 经过端口在文件nmap-services中查找名字

2.4.8 其余

  1. 探测端口包含了openfilter状态的,当连接成功(TCP)或者接收到数据时(UDP),更新端口状态为open
  2. 空探测使用后,若是未匹配上,后续第一个探测能够复用这个连接
  3. 并发数

3.总结

  • 依赖端口,须要目标机器上开放相应的端口
  • 依赖配置,探测中有对应的端口才进行探测
  • 正则匹配是串行匹配, 每一个探测下都有不少规则特征
  • 目标机器上若是开放的端口越多,探测的越多
  • softmatch匹配上后还须要继续匹配
  • ssl服务,还须要二次探测
  • 探测完后都未匹配时,将进行更通用的探测
  • 结果中不必定准确,因未匹配上规则(无探测配置)时,从映射文件中获取服务名
  • 使用io多路复用进行异步并发处理