技工之家

Let's talk about tech

导航

nmap-3.75源码分析(1. 整体流程)

从nmap.cc文件的nmap_main()函数开始

第307行:

  while((arg = getopt_long_only(argc,fakeargv,"6Ab:D:d::e:Ffg:hIi:M:m:NnOo:P:p:qRrS:s:T:Vv", long_options, &option_index)) != EOF) {

开始解析命令行参数,根据解析结果对相应的变量进行设置。

第736行:

  winip_postopt_init();

进行windows相关的一些初始化,包括初始化winsock,检查raw socket和winpcap的availability,加查windows版本,查询主机上的网络接口和ip地址等。

第922行开始:
  if  (randomize) {
    
if (ports->tcp_count) {
      shortfry(ports
->tcp_ports, ports->tcp_count); 
      
// move a few more common ports closer to the beginning to speed scan
      random_port_cheat(ports->tcp_ports, ports->tcp_count);
    }

    
if (ports->udp_count) 
      shortfry(ports
->udp_ports, ports->udp_count); 
    
if (ports->prot_count) 
      shortfry(ports
->prots, ports->prot_count); 
  }


将端口顺序打乱,其中tcp端口在打乱后,用ramdom_port_cheat()将一些常用的端口移到前面,据说在一些情况下可以加快扫描速度。

第953行开始:

  while(num_host_exp_groups < o.ping_group_sz &&
    (host_spec 
= grab_next_host_spec(inputfd, argc, fakeargv))) {
    host_exp_group[num_host_exp_groups
++=
 strdup(host_spec);
    
// For purposes of random scan

    if (o.max_ips_to_scan && o.max_ips_to_scan <= numhosts_scanned + num_host_exp_groups)
      
break
;
  }


  
if (num_host_exp_groups == 0)
    fatal(
"No target machines/networks specified!"
);
  hstate 
= new
 HostGroupState(o.ping_group_sz, o.randomize_hosts,
                  host_exp_group, num_host_exp_groups);

首先将命令行参数中表示目标ip地址的字符串读入host_exp_group数组中,在用这个数组new一个HostGroupState对象。其中字符串的个数小于o.ping_group_sz指定的值。若命令行参数中给出的字符串个数大于这个值,则剩下的字符串留待以后读入。

第966行到第1163行是一个大循环:

  do {} while(!o.max_ips_to_scan || o.max_ips_to_scan > numhosts_scanned);

这个循环也是整个程序的主体和中枢,是执行工作都在这个循环中完成。结束条件是设置了o.max_ips_to_scan并且已经扫描过的主机数大于这个值。

第967行:

    ideal_scan_group_sz = determineScanGroupSize(numhosts_scanned, ports);

确定ideal_scan_group_sz的值,这个值指定一次扫描行动涉及的最多的主机数

第968行到第1065行:

    while(Targets.size() < ideal_scan_group_sz) {}

将ideal_scan_group_sz个数的主机防进Targets容器。Targets是一个vector<Target *>型的容器。

首先,第969行

      currenths = nexthost(hstate, exclude_group, ports, &(o.pingtype));

从前面创建的hstate中读出下一个主机,用Target类描述。

第970行至第993行处理hstate中已经没有剩下的主机的情况:
      if (!currenths) {}

hstate中的主机数量它持有的地质字符串组决定的,比如{"219.224.18.0/24", "166.111.0.0/16"}. 如果nexthost()返回空指针,则说明hstate的地址字符串所包含的主机都已被处理过。这种情况下,要读入命令行参数中剩下的地址字符串。首先释放用来存放地址字符串的host_exp_group:(第971行至第975行)
    /* Try to refill with any remaining expressions */
    
/* First free the old ones */
    
for(i=0; i < num_host_exp_groups; i++)
      free(host_exp_group[i]);
    num_host_exp_groups 
= 0;

然后读取命令行参数中剩下的地址字符串:(第976行至第982行)
    /* Now grab any new expressions */
    
while(num_host_exp_groups < o.ping_group_sz && 
          (
!o.max_ips_to_scan ||  o.max_ips_to_scan > numhosts_scanned + num_host_exp_groups) &&

          (host_spec 
= grab_next_host_spec(inputfd, argc, fakeargv))) {
      
// For purposes of random scan

      host_exp_group[num_host_exp_groups++= strdup(host_spec);
    }

接下来,第983行至第993行:
    if (num_host_exp_groups == 0)
      
break
;
    delete hstate;
    hstate 
= new
 HostGroupState(o.ping_group_sz, o.randomize_hosts,
                    host_exp_group, num_host_exp_groups);
      
    
/* Try one last time -- with new expressions */

    currenths 
= nexthost(hstate, exclude_group, ports, &(o.pingtype));
    
if (!
currenths)
      
break
;
      }
num_host_exp_groups等于0说明没有剩余的地址字符串了,于是也没有更多的主机可添加了,所以跳出968行开始的循环。否则创建新的hstate对象,并尝试从这个新的hstate对象中读取下一个主机,如果失败(没有主机),也认为没有更多的主机了,于是也跳出968行开始的循环。这里不是非常严谨,因为照理应该是如果hstate中没有剩余的主机就再读命令行参数中剩下的字符串数组,如果剩下的字符串数组也没了,才可以跳出。这里的考虑可能是,实际情况下,一个刚读进来的hstate,第一次调用nexthost就没有主机不是一个正常的现象,所以这一次循环就不再读进新的主机了。

如果Targets中已经有了主机,我们比较连接当前读到的这个主机所用的接口是否和已有的主机一样,如果不一样,就不加这个主机了,把这个主机留到下一批处理,并停止增加这一批的主机。因此,第1051行至第1060行:
      /* Groups should generally use the same device as properties
         change quite a bit between devices.  Plus dealing with a
         multi-device group can be a pain programmatically. So if
         this Target has a different device the rest, we give it
         back. 
*/

      
if (Targets.size() > 0 && strcmp(Targets[Targets.size() - 1]->device, currenths->device)) {
        returnhost(hstate);
        numhosts_scanned
--; numhosts_up--
;
        
break
;
      }


如果顺利读到了下一个主机,把它加入这一批要处理的容器中。第1063行:
    Targets.push_back(currenths);

在从第968行开始的循环结束之后,检查Targets容器中是否有主机:
    if (Targets.size() == 0)
      
break/* Couldn't find any more targets */
如果容器大小为0,说明再没有剩下未被处理的主机了,于是跳出大循环(主体、中枢)。

在得到了一个非空的Targets之后,就可以开始真正的扫描了。从第1075行至第1120行进行扫描:
    /* I now have the group for scanning in the Targets vector */
    
/* TODO: Add parallel-capable scans here */
    
if (o.synscan)
      ultra_scan(Targets, ports, SYN_SCAN);

    
if
 (o.ackscan)
      ultra_scan(Targets, ports, ACK_SCAN);

    
if
 (o.windowscan)
      ultra_scan(Targets, ports, WINDOW_SCAN);

    
if
 (o.finscan)
      ultra_scan(Targets, ports, FIN_SCAN);

    
if
 (o.xmasscan)
      ultra_scan(Targets, ports, XMAS_SCAN);

    
if
 (o.nullscan)
      ultra_scan(Targets, ports, NULL_SCAN);

    
if
 (o.maimonscan)
      ultra_scan(Targets, ports, MAIMON_SCAN);

    
if
 (o.udpscan)
      ultra_scan(Targets, ports, UDP_SCAN);

    
if
 (o.connectscan)
      ultra_scan(Targets, ports, CONNECT_SCAN);

    
if
 (o.ipprotscan)
      ultra_scan(Targets, ports, IPPROT_SCAN);

   
/* These lame functions can only handle one target at a time */

    
for(targetno = 0; targetno < Targets.size(); targetno++{
      currenths 
=
 Targets[targetno];
      
if (o.idlescan) idle_scan(currenths, ports->
tcp_ports, 
                ports
->
tcp_count, idleProxy);
      
if (o.bouncescan) 
{
    
if (ftp.sd <= 0) ftp_anon_connect(&
ftp);
    
if (ftp.sd > 0) bounce_scan(currenths, ports->
tcp_ports, 
                    ports
->tcp_count, &
ftp);
      }

    }


    
if (o.servicescan)
      service_scan(Targets);
是否进行某一种类型的扫描是由参数决定的。可见,各种扫描方式之间是顺序完成的,所以作者在头上的注释中指出可以加入代码使各种扫描并行完成。

接下来对Targets中的每一个主机输出扫描结果和进行日志记录:第1122行至1154行
    for(targetno = 0; targetno < Targets.size(); targetno++{
      currenths 
=
 Targets[targetno];
      

    printportoutput(currenths, 
&currenths->
ports);
    printmacinfo(currenths);
    printosscanoutput(currenths);
    
    }

最后就是清除并释放Targets中的内容,准备接受下一批主机:(第1157行至1162行)
    /* Free all of the Targets */
    
while(!Targets.empty()) {
      currenths 
= Targets.back();
      delete currenths;
      Targets.pop_back();
    }
这同时也是大循化的最后一个步骤。

大循环执行外后,便是一些释放内存的工作。整体流程到此结束。后续的源代码分析将会分析执行具体扫描工作的代码。

posted on 2005-01-29 12:40  techmania  阅读(3559)  评论(2编辑  收藏  举报