现在的位置: 首页 > 自动控制 > 工业·编程 > 正文

Nmap源码分析(基本框架)

2014-01-20 21:04 工业·编程 ⁄ 共 23254字 ⁄ 字号 暂无评论

Nmap是一款非常强大的开源扫描工具。自己在使用过程中忍不住想仔细阅读一下它的源码。源码里面汇集了众多安全专家的精巧设计与优雅写法,读起来令人心旷神怡而又受益匪浅。
这里我们以阅读nmap6.0的代码作为主线来分析Nmap源码的实现框架。
源码下载地址:http://nmap.org/dist/nmap-6.01.tar.bz2
SVN检出:svn co https://svn.nmap.org/nmap
 
1      文件组织
我们先从总体上了解Nmap的文件组织方式及分析源码需要关注的重点。
1.1    目录结构
解压Nmap的源码包后,可以看到根目录下有许多的子目录和文件。
文件分析
放在Nmap/根目录下包括几种类型文件:
1)       Nmap核心功能的源码(如nmap.cc/ scan_engine.cc/ service_scan.cc/osscan2.cc/ nse_main.lua等)。
2)       Nmap的核心数据库文件(nmap-os-db/ nmap-service-probes/ nmap-rpc/nmap-protocols等)。
3)       编译链接相关的Makefile或CONFIG文件。
4)       其他杂项文件(如安装提示:README-WIN32)
 
目录分析
使用Windows的tree命令列举出Nmap的目录结构.
为了避免目录树过长,这里只显示了3个级别的目录,目录的主要作用在名字后面简略说明。
可以看到Nmap工具也使用到很多其他开源项目的成果,例如libdnet/ liblinear/ liblua/libpcap/ libpcre等;另外Nmap自身也实现很多有用的程序库,如nsock/ libnetutil/;Nmap项目包括附带几个小工具:1)ncat,这是根据TCPIP协议栈瑞士军刀netcat(该工具已停止维护更新)开发扩展出来的工具。2)nping,类似于Hping的工具,用于进行主机探测和发包收包。3)ndiff,用于比较两次nmap扫描结果之间差异。

 

Nmap/ 
├─docs(Nmap相关文档,包括License、usage说明及XMLschema文件等) 
│  ├─licenses    
│  └─man-xlate 
├─libdnet-stripped(libdnet:简单的网络接口开源库) 
│  ├─config 
│  ├─include 
│  └─src 
├─liblinear(LIBLINEAR:负责大型线性分类的开源库) 
│  └─blas 
├─liblua(Lua脚本语言源码库) 
├─libnetutil(Nmap实现的基本的网络实用函数) 
├─libpcap(开源的抓包代码库libpcap) 
│  ├─bpf 
│  ├─ChmodBPF 
│  ├─lbl 
│  ├─missing 
│  ├─msdos 
│  ├─NMAP_MODIFICATIONS 
│  ├─packaging 
│  ├─pcap 
│  ├─SUNOS4 
│  ├─tests 
│  └─Win32 
├─libpcre(Perl兼容的正则表达式开源库libpcre) 
├─macosx(该目录负责支持苹果的操作系统MACOS X) 
│  └─nmap.pmdoc 
├─mswin32(该目录负责支持Windows操作系统) 
│  ├─lib 
│  ├─license-format 
│  ├─NET 
│  ├─NETINET 
│  ├─nsis 
│  ├─OpenSSL 
│  ├─pcap-include 
│  ├─RPC 
│  └─winpcap 
├─nbase(Nmap封装的基础使用程序库,包括string/path/random等) 
├─ncat(Ncat是Nmap项目组实现的新版的netcat:强大的网络工具) 
│  ├─certs 
│  ├─docs 
│  └─test 
├─ndiff(Ndiff是用于比较Nmap扫描结果的实用命令) 
│  ├─docs 
│  └─test-scans 
├─nmap-update(负责Nmap更新相关操作) 
├─nping(Nping是Nmap项目组实现的新版的Hping:网络探测与构建packet) 
│  └─docs 
├─nselib(Nmap使用Lua语言编写的常用的脚本库) 
│  └─data 
├─nsock(Nmap实现的并行的SocketEvent处理库) 
│  ├─include 
│  └─src 
├─scripts(Nmap提供常用的扫描检查的lua脚本) 
├─todo(介绍Nmap项目将来开发的具体任务) 
└─zenmap(Nmap的官方的图形界面程序,由python语言编写) 
   ├─install_scripts 
   ├─radialnet 
   ├─share 
   ├─test 
   ├─zenmapCore 
   └─zenmapGUI 

1.2    文件类型
下面从源码类型的角度上浏览一下Nmap项目的特点:
Nmap6.0工程内总共包括1300多个文件。
1)       C和C++文件有600多个,主要实现Nmap最核心的功能:主机发现、端口扫描、服务侦测、OS侦测及搭建脚本引擎框架;也包括其他开源项目如libpcap的源码。
2)       Python文件有100多个,主要实现Zenmap图形界面,Zenmap会调用到Nmap基本命令,也实现一些新的功能:例如确定网络拓扑结构、Profile的管理(常用的命令保存为Profile)等。
3)       Lua与NSE文件400多个,负责构建Nmap脚本引擎及提供常用的扫描脚本。其中NSE格式为Nmap定制的Lua文件,方便用户自行编写脚本进行功能扩展。
4)       XML文件数十个,用于辅助描述Nmap的内容或Zenmap的测试等工作。
5)       其他文件,其他辅助工具操作的文件。
 
2      源码分析
2.1    执行流程框架
Nmap的执行流程简单清晰,主要的工作在nmap.cc文件中完成,而main.cc负责简单地包装nmap_main()函数。
nmap_main()函数是执行流程的核心。
准备阶段:在其中会执行参数解析、资源分配、基本扫描信息的输出、端口与地址列表的初始化、NSE环境准备及pre-scripts的运行等基本的准备操作。
工作阶段:然后进入主循环,每次循环对一组目标地址进行主机发现、端口扫描、服务与版本侦测、OS侦测及脚本扫描等操作,直到所有的目标地址都被扫描完毕才推出主循环。
善后阶段:在完成所有的扫描操作后,调用post-script完成相应处理,然后打印出扫描的最终结果,并释放掉分配的资源。

2.2    nmap_main()函数
以下代码是对nmap_main()函数基本的分析。
其中以///开头是新添加的注释。而以类似于///<Start------创建主机组状态,进入主循环--------Start>的形式出现的注释用于标注一个比较大的功能代码段。
Nmap的所有的功能都在此nmap_main()设有入口,以此为基础可以更深入地分析Nmap的其他模块。

int nmap_main(int argc, char *argv[]) { 
  int i; 
  vector<Target *> Targets; 
  time_t now; 
  struct hostent *target = NULL; 
  time_t timep; 
  char mytime[128]; 
  addrset exclude_group; 
  #ifndef NOLUA 
  /* Only NSE scripts can add targets */ 
  NewTargets *new_targets = NULL;///NewTargets为Singleton模式,产生单个实例 
  /* Pre-Scan and Post-Scan script results datastructure */ 
  ScriptResults *script_scan_results = NULL; 
  #endif 
  char **host_exp_group;   
  int num_host_exp_groups; 
  HostGroupState *hstate = NULL; 
  unsigned int ideal_scan_group_sz = 0; 
  Target *currenths; 
  char *host_spec = NULL; 
  char myname[MAXHOSTNAMELEN + 1]; 
  int sourceaddrwarning = 0; /* Have we warned them yet about unguessable
                                source addresses? */ 
  unsigned int targetno; 
  char hostname[MAXHOSTNAMELEN + 1] = ""; 
  struct sockaddr_storage ss; 
  size_t sslen; 
  char **fakeargv = NULL; 
 
  now = time(NULL); 
  local_time = localtime(&now); 
  ///设置错误log输出函数 
  if(o.debugging) 
    nbase_set_log(fatal,error); 
  else 
    nbase_set_log(fatal,NULL); 
 
  if (argc < 2 ) printusage(-1); 
 
  /* argv faking silliness */ 
  fakeargv = (char **) safe_malloc(sizeof(char *) * (argc + 1)); 
  for(i=0; i < argc; i++) { 
    fakeargv[i] = strdup(argv[i]); 
  } 
  fakeargv[argc] = NULL; 
 
  Targets.reserve(100); 
#ifdef WIN32 
  win_pre_init(); 
#endif 
///调用parse_options进行命令参数的解析 
  parse_options(argc, fakeargv);     
///在Linux下设置终端为只读非阻塞方式,在Windows平台为空函数。 
  tty_init(); // Put the keyboard in raw mode 
///将解析命令时需要延迟执行的操作在此处处理 
  apply_delayed_options(); 
 
#ifdef WIN32 
///调用WSAStartup启动Winsock DLL,后续网络解析等需要用到。 
  win_init();     
#endif 
 
///如果用户使用了参数--iflist,那么会在此处打印网卡和路由表信息,然后退出。 
///该选项对于显示指定发送网卡非常有帮助,可以提供基本的网络设备信息。 
  if (delayed_options.iflist) { 
    print_iflist(); 
    exit(0); 
  } 
 
///quashargv部分用于修改命令行参数,将程序名字更改为FAKE_ARGV(默认为“pine”), 
///并将剩余的各个参数都清空。 
///在命令中加入-q可实现quashargv功能。这最初是为了逃避ps等程序名称显示,便于隐蔽Nmap。 
///不过在Windows系统上并无实效。 
  /* more fakeargv junk, BTW malloc'ing extra space in argv[0] doesn't work */ 
  if (o.quashargv) { 
    size_t fakeargvlen = strlen(FAKE_ARGV), argvlen = strlen(argv[0]); 
    if (argvlen < fakeargvlen) 
      fatal("If you want me to fake your argv, you need to call the program with a longer name.  Try the full pathname, or rename it fyodorssuperdedouperportscanner"); 
    strncpy(argv[0], FAKE_ARGV, fakeargvlen); 
    memset(&argv[0][fakeargvlen], '\0', strlen(&argv[0][fakeargvlen])); 
    for(i=1; i < argc; i++) 
      memset(argv[i], '\0', strlen(argv[i])); 
  } 
///如果使用FTP bounce scan的扫描方式,那么需要首先保证该FTP网站是可以访问到的。 
///关于FTP bounce scan更多介绍,请参考:
http://nmap.org/nmap_doc.html#bounce 
  /* If he wants to bounce off of an FTP site, that site better damn well be reachable! */ 
  if (o.bouncescan) { 
    if (!inet_pton(AF_INET, ftp.server_name, &ftp.server)) { 
      if ((target = gethostbyname(ftp.server_name))) 
        memcpy(&ftp.server, target->h_addr_list[0], 4); 
      else { 
        fatal("Failed to resolve FTP bounce proxy hostname/IP: %s", 
              ftp.server_name); 
      } 
    } else if (o.verbose) { 
      log_write(LOG_STDOUT, "Resolved FTP bounce attack proxy to %s (%s).\n", 
                ftp.server_name, inet_ntoa(ftp.server)); 
    } 
  } 
///<Start--------------扫描信息输出-----------------Start>   
  fflush(stdout); 
  fflush(stderr); 
 
  timep = time(NULL); 
///准备将基本的扫描输出到文件与控制台中 
  /* Brief info in case they forget what was scanned */ 
  Strncpy(mytime, ctime(&timep), sizeof(mytime)); 
  chomp(mytime);  ///去掉字符串末尾换行符 
  char *xslfname = o.XSLStyleSheet();///XML样式表 
  xml_start_document(); 
  if (xslfname) { 
    xml_open_pi("xml-stylesheet"); 
    xml_attribute("href", "%s", xslfname); 
    xml_attribute("type", "text/xsl"); 
    xml_close_pi(); 
    xml_newline(); 
  } 
 
  std::string command; 
  if (argc > 0) 
    command += fakeargv[0]; 
  for (i = 1; i < argc; i++) { 
    command += " "; 
    command += fakeargv[i]; 
  } 
 
  xml_start_comment(); 
  xml_write_escaped(" %s %s scan initiated %s as: %s ", NMAP_NAME, NMAP_VERSION, mytime, join_quoted(fakeargv, argc).c_str()); 
  xml_end_comment(); 
  xml_newline(); 
 
  log_write(LOG_NORMAL|LOG_MACHINE, "# "); 
  log_write(LOG_NORMAL|LOG_MACHINE, "%s %s scan initiated %s as: ", NMAP_NAME, NMAP_VERSION, mytime); 
  log_write(LOG_NORMAL|LOG_MACHINE, "%s", command.c_str()); 
  log_write(LOG_NORMAL|LOG_MACHINE, "\n"); 
 
  xml_open_start_tag("nmaprun"); 
  xml_attribute("scanner", "nmap"); 
  xml_attribute("args", "%s", join_quoted(fakeargv, argc).c_str()); 
  xml_attribute("start", "%lu", (unsigned long) timep); 
  xml_attribute("startstr", "%s", mytime); 
  xml_attribute("version", "%s", NMAP_VERSION); 
  xml_attribute("xmloutputversion", NMAP_XMLOUTPUTVERSION); 
  xml_close_start_tag(); 
  xml_newline(); 
 
  output_xml_scaninfo_records(&ports); 
 
  xml_open_start_tag("verbose"); 
  xml_attribute("level", "%d", o.verbose); 
  xml_close_empty_tag(); 
  xml_newline(); 
  xml_open_start_tag("debugging"); 
  xml_attribute("level", "%d", o.debugging); 
  xml_close_empty_tag(); 
  xml_newline(); 
 
  /* Before we randomize the ports scanned, lets output them to machine
     parseable output */ 
  if (o.verbose) ///输出机器可以解析端口信息(grepable格式) 
    output_ports_to_machine_parseable_output(&ports); 
 
#if defined(HAVE_SIGNAL) && defined(SIGPIPE) 
  ///注册信号处理函数,这里只是直接忽略SIGPIPE信号。其具体实现为#define SIG_IGN       (void (*)(int))1 
  signal(SIGPIPE, SIG_IGN); /* ignore SIGPIPE so our program doesn't crash because
                               of it, but we really shouldn't get an unexpected
                               SIGPIPE */ 
#endif 
  ///检查配置的最大并发度是否在系统最大的套接字数量范围之内 
  if (o.max_parallelism && (i = max_sd()) && i < o.max_parallelism) { 
    error("WARNING:  Your specified max_parallel_sockets of %d, but your system says it might only give us %d.  Trying anyway", o.max_parallelism, i); 
  } 
 
  if (o.debugging > 1) log_write(LOG_STDOUT, "The max # of sockets we are using is: %d\n", o.max_parallelism); 
 
  // At this point we should fully know our timing parameters 
  if (o.debugging) { 
    log_write(LOG_PLAIN, "--------------- Timing report ---------------\n"); 
    log_write(LOG_PLAIN, "  hostgroups: min %d, max %d\n", o.minHostGroupSz(), o.maxHostGroupSz()); 
    log_write(LOG_PLAIN, "  rtt-timeouts: init %d, min %d, max %d\n", o.initialRttTimeout(), o.minRttTimeout(), o.maxRttTimeout()); 
    log_write(LOG_PLAIN, "  max-scan-delay: TCP %d, UDP %d, SCTP %d\n", o.maxTCPScanDelay(), o.maxUDPScanDelay(), o.maxSCTPScanDelay()); 
    log_write(LOG_PLAIN, "  parallelism: min %d, max %d\n", o.min_parallelism, o.max_parallelism); 
    log_write(LOG_PLAIN, "  max-retries: %d, host-timeout: %ld\n", o.getMaxRetransmissions(), o.host_timeout); 
    log_write(LOG_PLAIN, "  min-rate: %g, max-rate: %g\n", o.min_packet_send_rate, o.max_packet_send_rate); 
    log_write(LOG_PLAIN, "---------------------------------------------\n"); 
  } 
///<End--------------扫描信息输出-----------------End> 
 
///<Start-------------端口与地址初始化-------------Start> 
  /* Before we randomize the ports scanned, we must initialize PortList class. */ 
  if (o.ipprotscan) 
    PortList::initializePortMap(IPPROTO_IP,  ports.prots, ports.prot_count); 
  if (o.TCPScan()) 
    PortList::initializePortMap(IPPROTO_TCP, ports.tcp_ports, ports.tcp_count); 
  if (o.UDPScan()) 
    PortList::initializePortMap(IPPROTO_UDP, ports.udp_ports, ports.udp_count); 
  if (o.SCTPScan()) 
    PortList::initializePortMap(IPPROTO_SCTP, ports.sctp_ports, ports.sctp_count); 
 
  if (o.randomize_ports) { 
    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.sctp_count) 
      shortfry(ports.sctp_ports, ports.sctp_count); 
    if (ports.prot_count) 
      shortfry(ports.prots, ports.prot_count); 
  } 
  ///exclude_group记录的是排除地址,如命令行nmap 192.168.1.1/24 --exclude 192.168.1.0-10 
  ///扫描C类地址192.168.1.x,并排除其中192.168.1.0-192.168.1.10地址。 
  ///addrset_init()将初始化排除地址组的链表头指针为NULL。 
  addrset_init(&exclude_group); 
 
  /* lets load our exclude list */ 
  if (o.excludefd != NULL) {///文件指定的排除地址 
    load_exclude_file(&exclude_group, o.excludefd); 
    fclose(o.excludefd); 
  } 
  if (o.exclude_spec != NULL) {///命令行直接指定的排除地址 
    load_exclude_string(&exclude_group, o.exclude_spec); 
  } 
 
  if (o.debugging > 3)  ///若调试级别大于3,打印出排除地址信息 
    dumpExclude(&exclude_group); 
///<End-------------端口与地址初始化-------------End> 
 
 
///<Start------NSE环境准备并执行pre-scripts--------Start> 
#ifndef NOLUA 
  if (o.scriptupdatedb) { 
    o.max_ips_to_scan = o.numhosts_scanned; // disable warnings? 
  } 
  if (o.servicescan)  ///当配置了版本扫描时,会默认启动版本扫描脚本,位于NSE中version类别中 
    o.scriptversion = 1; 
  if (o.scriptversion || o.script || o.scriptupdatedb) 
    open_nse();  ///开启NSE环境 
 
  /* Run the script pre-scanning phase */ 
  if (o.script) { 
    new_targets = NewTargets::get();  ///分配实例或返回已有实例(Singleton模式) 
    script_scan_results = get_script_scan_results_obj(); 
    script_scan(Targets, SCRIPT_PRE_SCAN); 
    printscriptresults(script_scan_results, SCRIPT_PRE_SCAN); 
    script_scan_results->clear(); 
  } 
#endif 
///<End------NSE环境准备并执行pre-scripts--------End> 
 
 
///<Start------创建主机组状态,进入主循环--------Start> 
  /* Time to create a hostgroup state object filled with all the requested
     machines. The list is initially empty. It is refilled inside the loop
     whenever it is empty. */ 
  ///分配字符串数组,用以保存各个主机表达式字符串的地址 
  host_exp_group = (char **) safe_malloc(o.ping_group_sz * sizeof(char *)); 
  num_host_exp_groups = 0; 
 
  hstate = new HostGroupState(o.ping_group_sz, o.randomize_hosts, 
                  host_exp_group, num_host_exp_groups); 
 
  do { 
    ///确定最佳的host group的大小,该大小取决于扫描方式与网络速度。 
    ideal_scan_group_sz = determineScanGroupSize(o.numhosts_scanned, &ports); 
    ///<Start---------对host group进行主机发现----------Start> 
    ///以下的while()将依次进行主机发现,确定主机是否在线。 
    ///若该主机在线加入该host group,用于后续的操作。当数量达到最佳大小时,退出循环。 
    while(Targets.size() < ideal_scan_group_sz) { 
      o.current_scantype = HOST_DISCOVERY;  ///设置扫描状态:HOST_DICOVERY 
      currenths = nexthost(hstate, &exclude_group, &ports, o.pingtype); ///主机发现的核心函数 
      ///如果当前主机发现无法找到有效主机,那么会做以下尝试: 
      ///1)更换主机表达式(host expressions) 
      ///例如:nmap 192.168.1.1/24 10.10.30.55-100,192.168.1.x不能再发现主机时候,切换为10.30.55-100 
      ///2)将执行脚本扫描时发现的主机,加入主机表达式组host_exp_group 
      ///3) 建立新的主机组状态,并做最后的主机发现尝试 
      if (!currenths) { 
        /* 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; 
        /* Now grab any new expressions */ 
        while(num_host_exp_groups < o.ping_group_sz &&  
          (!o.max_ips_to_scan || o.max_ips_to_scan > o.numhosts_scanned + (int) Targets.size() + num_host_exp_groups) && 
          (host_spec = grab_next_host_spec(o.inputfd, o.generate_random_ips, argc, fakeargv))) { 
            // For purposes of random scan 
            host_exp_group[num_host_exp_groups++] = strdup(host_spec); 
        } 
#ifndef NOLUA 
        /* Add the new NSE discovered targets to the scan queue */ 
        if (o.script) { 
          if (new_targets != NULL) { 
            while (new_targets->get_queued() > 0 && num_host_exp_groups < o.ping_group_sz) { 
              std::string target_spec = new_targets->read(); 
              if (target_spec.length()) 
                host_exp_group[num_host_exp_groups++] = strdup(target_spec.c_str()); 
            } 
 
            if (o.debugging > 3) 
              log_write(LOG_PLAIN, 
                  "New targets in the scanned cache: %ld, pending ones: %ld.\n", 
                  new_targets->get_scanned(), new_targets->get_queued()); 
          } 
        } 
#endif 
        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; 
      } 
     
      if (currenths->flags & HOST_UP && !o.listscan)  
        o.numhosts_up++; 
     
      if ((o.noportscan && !o.traceroute 
#ifndef NOLUA 
      && !o.script 
#endif 
          ) || o.listscan) { 
        ///当不进行端口扫描(-sn)并且没有指定traceroute和脚本的话,那么扫描就到此处就结束。 
        ///或当进行列表扫描(-sL,只列举出主机IP,并不真正扫描)时,扫描也到此结束。       
        /* We're done with the hosts */ 
        if (currenths->flags & HOST_UP || o.verbose) { 
          xml_start_tag("host"); 
          write_host_header(currenths); 
          printmacinfo(currenths); 
          //  if (currenths->flags & HOST_UP) 
          //  log_write(LOG_PLAIN,"\n"); 
          printtimes(currenths); 
          xml_end_tag(); 
          xml_newline(); 
          log_flush_all(); 
        } 
        delete currenths; 
        o.numhosts_scanned++; 
        continue; 
      } 
      ///若配置要伪造源IP地址(-S ip),将命令行中传入的地址写入当前主机源地址 
      if (o.spoofsource) { 
        o.SourceSockAddr(&ss, &sslen); 
        currenths->setSourceSockAddr(&ss, sslen); 
      } 
       
      ///如果主机状态为HOST_DOWN,那么需要根据配置考虑是否输出其状态 
      ///输出条件:verbose级别大于0,并且没有指定openonly或已确定有开放端口。 
      ///疑问:如果有open Ports,为什么此主机状态会是HOST_DOWN呢? 
      /* I used to check that !currenths->weird_responses, but in some
         rare cases, such IPs CAN be port successfully scanned and even
         connected to */ 
      if (!(currenths->flags & HOST_UP)) { 
        if (o.verbose && (!o.openOnly() || currenths->ports.hasOpenPorts())) { 
          xml_start_tag("host"); 
          write_host_header(currenths); 
          xml_end_tag(); 
          xml_newline(); 
        } 
        delete currenths; 
        o.numhosts_scanned++; 
        continue; 
      } 
      ///如果是RawScan(即涉及到构建原始的packet的扫描方式,如SYN/FIN/ARP等等), 
      ///需要设置套接字源IP地址 
      if (o.RawScan()) { 
        if (currenths->SourceSockAddr(NULL, NULL) != 0) { 
          if (o.SourceSockAddr(&ss, &sslen) == 0) { 
            ///若全局变量o中已有源IP地址,直接赋值给当前目标机 
            currenths->setSourceSockAddr(&ss, sslen); 
          } else { 
            ///否则,需要重新查询、解析主机来获取源地址 
            if (gethostname(myname, MAXHOSTNAMELEN) || 
                resolve(myname, 0, 0, &ss, &sslen, o.af()) == 0) 
              fatal("Cannot get hostname!  Try using -S <my_IP_address> or -e <interface to scan through>\n");  
         
            o.setSourceSockAddr(&ss, sslen); 
            currenths->setSourceSockAddr(&ss, sslen); 
            if (! sourceaddrwarning) { 
              error("WARNING:  We could not determine for sure which interface to use, so we are guessing %s .  If this is wrong, use -S <my_IP_address>.", 
                  inet_socktop(&ss)); 
                sourceaddrwarning = 1; 
            } 
          } 
        } 
 
        if (!currenths->deviceName())///网卡名字,在主机发现函数nexthost()中设置 
          fatal("Do not have appropriate device name for target"); 
         
        ///如果新发现的主机与该主机组类型不大相同,那么考虑将此主机放入新的主机组内。 
        ///因为对主机分组是为了加快扫描速度,所以尽可能特征相似的主机组合在一起。 
        ///流水线工作模式的扫描思想。 
        /* Hosts in a group need to be somewhat homogeneous. Put this host in
           the next group if necessary. See target_needs_new_hostgroup for the
           details of when we need to split. */ 
        if (target_needs_new_hostgroup(Targets, currenths)) { 
          returnhost(hstate); 
          o.numhosts_up--; 
          break; 
        } 
        ///设置IP诱骗时,将当前主机真实IP放入decoyturn位置。 
        ///其他的诱骗IP地址在parse options时已经确定。 
        o.decoys[o.decoyturn] = currenths->v4source();     
      } 
      ///将新发现的主机加入Targets向量 
      Targets.push_back(currenths);         
    }///一次分组的主机发现在此处结束,接下来执行端口扫描、服务侦测、OS侦测、脚本扫描等。 
    ///<End---------对host group进行主机发现----------End> 
     
    if (Targets.size() == 0)///主机发现没有找到任何目标机时,退出主循环 
      break; /* Couldn't find any more targets */ 
     
    // Set the variable for status printing 
    o.numhosts_scanning = Targets.size(); 
     
    // Our source must be set in decoy list because nexthost() call can 
    // change it (that issue really should be fixed when possible) 
    if (o.af() == AF_INET && o.RawScan()) 
      o.decoys[o.decoyturn] = Targets[0]->v4source(); 
     
    /* I now have the group for scanning in the Targets vector */ 
 
    if (!o.noportscan) { 
      ///<Start---------端口扫描----------Start> 
      ///针对用户指定的不同扫描方式,分别使用不同参数调用ultra_scan() 
      ///ultra_scan()设计精巧,用统一的接口处理大多数的端口扫描 
      // Ultra_scan sets o.scantype for us so we don't have to worry 
      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.sctpinitscan) 
        ultra_scan(Targets, &ports, SCTP_INIT_SCAN); 
       
      if (o.sctpcookieechoscan) 
        ultra_scan(Targets, &ports, SCTP_COOKIE_ECHO_SCAN); 
       
      if (o.ipprotscan) 
        ultra_scan(Targets, &ports, IPPROT_SCAN); 
       
      /* These lame functions can only handle one target at a time */ 
      if (o.idlescan) { 
        for(targetno = 0; targetno < Targets.size(); targetno++) { 
           o.current_scantype = IDLE_SCAN; 
           keyWasPressed(); // Check if a status message should be printed 
           idle_scan(Targets[targetno], ports.tcp_ports, 
                                  ports.tcp_count, o.idleProxy, &ports); 
        } 
      } 
      if (o.bouncescan) { 
        for(targetno = 0; targetno < Targets.size(); targetno++) { 
           o.current_scantype = BOUNCE_SCAN; 
           keyWasPressed(); // Check if a status message should be printed 
          if (ftp.sd <= 0) ftp_anon_connect(&ftp); 
          if (ftp.sd > 0) bounce_scan(Targets[targetno], ports.tcp_ports, 
                                      ports.tcp_count, &ftp); 
        } 
      } 
      ///<End---------端口扫描----------End> 
      ///<Start------服务与版本扫描--------Start> 
      if (o.servicescan) { 
        o.current_scantype = SERVICE_SCAN;  
 
        service_scan(Targets); 
      } 
 
      if (o.servicescan) { 
        /* This scantype must be after any TCP or UDP scans since it
         * get's it's port scan list from the open port list of the current
         * host rather than port list the user specified.
         */ 
        for(targetno = 0; targetno < Targets.size(); targetno++) 
          pos_scan(Targets[targetno], NULL, 0, RPC_SCAN); 
      } 
      ///<End------服务与版本扫描--------End> 
    } 
    ///操作系统扫描 
    if (o.osscan){ 
      OSScan os_engine; 
      os_engine.os_scan(Targets); 
    } 
    ///若需要路径追踪,在此处调用traceroute获取路径 
    if (o.traceroute) 
      traceroute(Targets); 
    ///脚本扫描 
#ifndef NOLUA 
    if(o.script || o.scriptversion) { 
      script_scan(Targets, SCRIPT_SCAN); 
    } 
#endif 
 
    ///<Start------输出扫描结果--------Start> 
    for(targetno = 0; targetno < Targets.size(); targetno++) { 
      currenths = Targets[targetno]; 
      /* Now I can do the output and such for each host */ 
      if (currenths->timedOut(NULL)) { 
        xml_open_start_tag("host"); 
        xml_attribute("starttime", "%lu", (unsigned long) currenths->StartTime()); 
        xml_attribute("endtime", "%lu", (unsigned long) currenths->EndTime()); 
        xml_close_start_tag(); 
        write_host_header(currenths); 
        xml_end_tag(); /* host */ 
        xml_newline(); 
        log_write(LOG_PLAIN,"Skipping host %s due to host timeout\n", 
            currenths->NameIP(hostname, sizeof(hostname))); 
        log_write(LOG_MACHINE,"Host: %s (%s)\tStatus: Timeout",  
            currenths->targetipstr(), currenths->HostName()); 
      } else { 
        /* --open means don't show any hosts without open ports. */ 
        if (o.openOnly() && !currenths->ports.hasOpenPorts()) 
          continue; 
 
        xml_open_start_tag("host"); 
        xml_attribute("starttime", "%lu", (unsigned long) currenths->StartTime()); 
        xml_attribute("endtime", "%lu", (unsigned long) currenths->EndTime()); 
        xml_close_start_tag(); 
        write_host_header(currenths); 
        printportoutput(currenths, ¤ths->ports); 
        printmacinfo(currenths); 
        printosscanoutput(currenths); 
        printserviceinfooutput(currenths); 
#ifndef NOLUA 
        printhostscriptresults(currenths); 
#endif 
        if (o.traceroute) 
          printtraceroute(currenths); 
        printtimes(currenths); 
        log_write(LOG_PLAIN|LOG_MACHINE,"\n"); 
        xml_end_tag(); /* host */ 
        xml_newline(); 
      } 
    } 
    log_flush_all(); 
    ///<End------输出扫描结果--------End> 
    o.numhosts_scanned += Targets.size(); 
   
    /* Free all of the Targets */ 
    while(!Targets.empty()) { 
      currenths = Targets.back(); 
      delete currenths; 
      Targets.pop_back(); 
    } 
    o.numhosts_scanning = 0; 
  } while(!o.max_ips_to_scan || o.max_ips_to_scan > o.numhosts_scanned); 
  ///当指定的扫描数量没有达到已经扫描数量,继续循环 
  ///<End------创建主机组状态,进入主循环--------End> 
   
  ///执行post-script,释放分配的资源 
#ifndef NOLUA 
  if (o.script) {    
    script_scan(Targets, SCRIPT_POST_SCAN); 
    printscriptresults(script_scan_results, SCRIPT_POST_SCAN); 
    script_scan_results->clear(); 
    delete new_targets; 
    new_targets = NULL; 
  } 
#endif 
 
  delete hstate; 
   
  addrset_free(&exclude_group); 
  hstate = NULL; 
 
  /* Free host expressions */ 
  for(i=0; i < num_host_exp_groups; i++) 
    free(host_exp_group[i]); 
  num_host_exp_groups = 0; 
  free(host_exp_group); 
 
  if (o.inputfd != NULL) 
    fclose(o.inputfd); 
 
  printdatafilepaths(); 
 
  printfinaloutput(); 
 
  free_scan_lists(&ports); 
 
  eth_close_cached(); 
 
  if (o.release_memory) { 
    /* Free fake argv */ 
    for(i=0; i < argc; i++) 
      free(fakeargv[i]); 
    free(fakeargv); 
 
    nmap_free_mem(); 
  } 
  return 0; 
}

给我留言

留言无头像?