nmap源码分析 您所在的位置:网站首页 nmap代码解析 nmap源码分析

nmap源码分析

#nmap源码分析 | 来源: 网络整理| 查看: 265

整体架构

功能目录

docs :相关文档 libdnet-stripped :开源网络接口库 liblinear:开源大型线性分类库 liblua:开源Lua脚本语言库 libnetutil:基本的网络函数 libpcap:开源抓包库 libpcre:开源正则表达式库 macosx:xcode项目文件 mswin32:vs项目文件 nbase:Nmap封装的基础使用函数库 ncat:netcat网络工具,由Nmap实现 ndiff:比较Nmap扫描结果的实用命令 nmap-update:负责Nmap更新操作 nping:Nmap项目组实现的新版的Hping,探测与构建包 nselib:Nmap的Lua脚本 nsock:Nmap实现的并行的SocketEvent处理库 scripts:Nmap提供常用的扫描检查的lua脚本 todo:开发任务 zenmap:python的图形界面程序

主体程序逻辑

入口程序在main.cc,主要功能

检查环境变量NMAP_ARGS 检查有没有–resume参数 判断是resume之前扫描,还是新请求

然后是根据传入参数去调用 nmap.cc的nmap_main()函数。下面是精简后的源码:

12345678910111213141516171819202122232425262728293031323334353637int main(int argc, char *argv[]) {   char command[2048];   int myargc;   char **myargv = NULL;   char *cptr;   int ret;   int i;   set_program_name(argv[0]);   if ((cptr = getenv("NMAP_ARGS"))) {     if (Snprintf(command, sizeof(command), "nmap %s", cptr) >= (int) sizeof(command)) {         error("Warning: NMAP_ARGS variable is too long, truncated");     }     /* copy rest of command-line arguments */     for (i = 1; i < argc && strlen(command) + strlen(argv[i]) + 1 < sizeof(command); i++) {       strcat(command, " ");       strcat(command, argv[i]);     }     myargc = arg_parse(command, &myargv);     if (myargc < 1) {       fatal("NMAP_ARGS variable could not be parsed");     }     ret = nmap_main(myargc, myargv);     arg_parse_free(myargv);     return ret;   }   if (argc == 3 && strcmp("--resume", argv[1]) == 0) {     if (gather_logfile_resumption_state(argv[2], &myargc, &myargv) == -1) {       fatal("Cannot resume from (supposed) log file %s", argv[2]);     }     return nmap_main(myargc, myargv);   }   return nmap_main(argc, argv); }

然后程序教育nmap_main(). nmap_main里,表面看起来扫描的循环是从2065行开始:

for (targetno = 0; targetno currenths = Targets[targetno]; 前后的代码都比较多,下次再抽时间细致分析。

这里引用一个别人做的流程图:

主体程序位置在nmap.cc内的nmap_main函数

新建一个主机的单例对象

123456#ifndef NOLUA   /* Only NSE scripts can add targets */   NewTargets *new_targets = NULL;   /* Pre-Scan and Post-Scan script results datastructure */   ScriptResults *script_scan_results = NULL; #endif 开始主程序 Target类

target.cc定义的是主机的类,扫描信息也是保存在target对象。nmap_main创建target时,使用了单例模式。

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660int nmap_main(int argc, char *argv[]) {   int i;   std::vector Targets;   time_t now;   struct hostent *target = NULL;   time_t timep;   char mytime[128];   struct addrset *exclude_group; #ifndef NOLUA   /* Only NSE scripts can add targets */   NewTargets *new_targets = NULL;   /* Pre-Scan and Post-Scan script results datastructure */   ScriptResults *script_scan_results = NULL; #endif   unsigned int ideal_scan_group_sz = 0;   Target *currenths;   char myname[FQDN_LEN + 1];   int sourceaddrwarning = 0; /* Have we warned them yet about unguessable                                 source addresses? */   unsigned int targetno;   char hostname[FQDN_LEN + 1] = "";   struct sockaddr_storage ss;   size_t sslen; #ifdef LINUX   /* Check for WSL and warn that things may not go well. */   struct utsname uts;   if (!uname(&uts)) {     if (strstr(uts.release, "Microsoft") != NULL) {       error("Warning: %s may not work correctly on Windows Subsystem for Linux.\n"           "For best performance and accuracy, use the native Windows build from %s/download.html#windows.",           NMAP_NAME, NMAP_URL);     }   } #endif   now = time(NULL);   local_time = localtime(&now);   if (o.debugging)     nbase_set_log(fatal, error);   else     nbase_set_log(fatal, NULL);   if (argc < 2){     printusage();     exit(-1);   }   Targets.reserve(100); #ifdef WIN32   win_pre_init(); #endif   // 命令行参数解析   printf("命令行参数解析\n");   parse_options(argc, argv);   // Linux平台设置只读非堵塞   printf("Linux平台设置只读非堵塞\n");   tty_init(); // Put the keyboard in raw mode #ifdef WIN32   // Must come after parse_options because of --unprivileged   // Must come before apply_delayed_options because it sets o.isr00t   win_init(); #endif   // 延迟处理的操作   printf("延迟处理的操作\n");   apply_delayed_options(); /* 这里用到的变量route_dst_hosts是由参数 --route-dst debugging模式定义的目标列表。定义如下: static std::vector route_dst_hosts; 前面命令行解析后会对其赋值。 */   for (unsigned int i = 0; i < route_dst_hosts.size(); i++) {     const char *dst;     struct sockaddr_storage ss;     struct route_nfo rnfo;     size_t sslen;     int rc;     dst = route_dst_hosts[i].c_str();     printf("解析参数 route_dst_hosts:%s\n", dst);     // 解析目标     printf("解析目标\n");     rc = resolve(dst, 0, &ss, &sslen, o.af());     if (rc != 0)       fatal("Can't resolve %s: %s.", dst, gai_strerror(rc));     printf("%s\n", inet_ntop_ez(&ss, sslen));     if (!route_dst(&ss, &rnfo, o.device, o.SourceSockAddr())) {       printf("Can't route %s (%s).", dst, inet_ntop_ez(&ss, sslen));     } else {       printf("%s %s", rnfo.ii.devname, rnfo.ii.devfullname);       printf(" srcaddr %s", inet_ntop_ez(&rnfo.srcaddr, sizeof(rnfo.srcaddr)));       if (rnfo.direct_connect)         printf(" direct");       else         printf(" nexthop %s", inet_ntop_ez(&rnfo.nexthop, sizeof(rnfo.nexthop)));     }     printf("\n");   }   route_dst_hosts.clear();   if (delayed_options.iflist) {     print_iflist();     exit(0);   }   /* If he wants to bounce off of an FTP site, that site better damn well be reachable! */   // FTP bounce scan模式,nmap -b参数定义   if (o.bouncescan) {     printf("nmap -b参数\n");     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));     }   }   fflush(stdout);   fflush(stderr);   timep = time(NULL);   // 扫描的简要信息 记录到xml   Strncpy(mytime, ctime(&timep), sizeof(mytime));   chomp(mytime);   if (!o.resuming) {     /* Brief info in case they forget what was scanned */     char *xslfname = o.XSLStyleSheet();     xml_start_document("nmaprun");     if (xslfname) {       xml_open_pi("xml-stylesheet");       xml_attribute("href", "%s", xslfname);       xml_attribute("type", "text/xsl");       xml_close_pi();       xml_newline();     }     xml_start_comment();     xml_write_escaped(" %s %s scan initiated %s as: %s ", NMAP_NAME, NMAP_VERSION, mytime, join_quoted(argv, argc).c_str());     xml_end_comment();     xml_newline();     xml_open_start_tag("nmaprun");     xml_attribute("scanner", "nmap");     xml_attribute("args", "%s", join_quoted(argv, 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();   } else {     xml_start_tag("nmaprun", false);   }   // 记录扫描日志   printf("记录扫描日志\n");   log_write(LOG_NORMAL | LOG_MACHINE, "# ");   log_write(LOG_NORMAL | LOG_MACHINE, "%s %s scan initiated %s as: %s", NMAP_NAME, NMAP_VERSION, mytime, join_quoted(argv, argc).c_str());   log_write(LOG_NORMAL | LOG_MACHINE, "\n");   /* Before we randomize the ports scanned, lets output them to machine      parseable output */   // 在随机端口扫描前,把可以解析的端口输出机器   if (o.verbose)   {     printf("在随机端口扫描前,把可以解析的端口输出机器\n");     output_ports_to_machine_parseable_output(&ports);   } #if defined(HAVE_SIGNAL) && defined(SIGPIPE)   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)   {     printf("端口号是否溢出\n");     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");   }   /* Before we randomize the ports scanned, we must initialize PortList class. */   // 端口与地址初始化   if (o.ipprotscan)   {     printf("端口与地址初始化\n");     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) {     printf("打乱端口顺序\n");     if (ports.tcp_count) {       shortfry(ports.tcp_ports, ports.tcp_count);       // move a few more common ports closer to the beginning to speed scan       // 常见端口往前放       printf("常见端口往前放\n");       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 命令行参数:排除地址处理(排除主机或网络)   printf("--exclude_group 命令行参数:排除地址处理(排除主机或网络)\n");   exclude_group = addrset_new();   /* 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)     dumpExclude(exclude_group); // NES 环境 printf("NES 环境\n"); #ifndef NOLUA   if (o.scriptupdatedb) {     o.max_ips_to_scan = o.numhosts_scanned; // disable warnings?   }   // 版本扫描   if (o.servicescan)   {     printf("版本扫描\n");     o.scriptversion = true;   }   if (o.scriptversion || o.script || o.scriptupdatedb)     open_nse();   /* Run the script pre-scanning phase */   // 预分析扫描   if (o.script) {     printf("预分析扫描\n");     new_targets = NewTargets::get();     script_scan_results = get_script_scan_results_obj();     script_scan(Targets, SCRIPT_PRE_SCAN);     printscriptresults(script_scan_results, SCRIPT_PRE_SCAN);     while (!script_scan_results->empty()) {       script_scan_results->front().clear();       script_scan_results->pop_front();     }   } #endif   if (o.ping_group_sz < o.minHostGroupSz())     o.ping_group_sz = o.minHostGroupSz();   // hstate 是一个list,初始为空,循环执行后保存各主机表达式字符串地址   HostGroupState hstate(o.ping_group_sz, o.randomize_hosts, argc, (const char **) argv);   // 主程序循环   do {     // 计算 host group 大小     ideal_scan_group_sz = determineScanGroupSize(o.numhosts_scanned, &ports);     // 主机发现成功,同加入到 host group,再后续处理     while (Targets.size() < ideal_scan_group_sz) {       o.current_scantype = HOST_DISCOVERY;       // 主机发现       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) {         /* We're done with the hosts */         // 如果 命令行参数-sn(不进行端口扫描) 且没有指定traceroute和脚本的话,扫描结束         // 如果 -sL(只列出ip),扫描也结束         if (currenths->flags & HOST_UP || (o.verbose && !o.openOnly())) {           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++;         if (!o.max_ips_to_scan || o.max_ips_to_scan > o.numhosts_scanned + Targets.size())           continue;         else           break;       }       // -S ip (配置要伪造的IP)       if (o.spoofsource) {         printf("-S ip (配置要伪造的IP)\n");         o.SourceSockAddr(&ss, &sslen);         currenths->setSourceSockAddr(&ss, sslen);       }       /* I used to check that !currenths->weird_responses, but in some          rare cases, such IPs CAN be port successfully scanned and even          connected to */       // 一些情况下,主机有返回状态,全状态为HOST_DOWN       if (!(currenths->flags & HOST_UP)) {         printf("一些情况下,主机有返回状态,全状态为HOST_DOWN\n");         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++;         if (!o.max_ips_to_scan || o.max_ips_to_scan > o.numhosts_scanned + Targets.size())           continue;         else           break;       }       // RawScan ,如SYN/FIN/ARP       if (o.RawScan()) {         printf("RawScan ,如SYN/FIN/ARP \n");         if (currenths->SourceSockAddr(NULL, NULL) != 0) {           if (o.SourceSockAddr(&ss, &sslen) == 0) {             // 直接设置IP             printf("直接设置IP\n");             currenths->setSourceSockAddr(&ss, sslen);           } else {             // 解析主机名             printf("解析主机名\n");             if (gethostname(myname, FQDN_LEN) ||                 resolve(myname, 0, &ss, &sslen, o.af()) != 0)               fatal("Cannot get hostname!  Try using -S or -e \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 .",                     inet_socktop(&ss));               sourceaddrwarning = 1;             }           }         }         // 网络设备(网卡)名称         if (!currenths->deviceName())           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. */         // 同一个组内主机要是同性质的,这里判断目标是否加到list列表内         if (Targets.size() && target_needs_new_hostgroup(&Targets[0], Targets.size(), currenths)) {           printf("同一个组内主机要是同性质的,这里判断目标是否加到list列表内\n");           returnhost(&hstate);           o.numhosts_up--;           break;         }         o.decoys[o.decoyturn] = currenths->source();       }       Targets.push_back(currenths);     }     // 没有发现主机     if (Targets.size() == 0)     {       printf("没有发现主机, break\n");       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.RawScan())     {       printf("Raw扫描:RawScan\n");       o.decoys[o.decoyturn] = Targets[0]->source();     }     /* I now have the group for scanning in the Targets vector */     // 定义了端口扫描,进入扫描的主体     if (!o.noportscan) {       printf("定义了端口扫描,进入扫描的主体\n");       // Ultra_scan sets o.scantype for us so we don't have to worry       if (o.synscan)       {         printf("syn扫描:synscan\n");         ultra_scan(Targets, &ports, SYN_SCAN);       }       if (o.ackscan)       {         printf("ack扫描:acksan\n");         ultra_scan(Targets, &ports, ACK_SCAN);       }       if (o.windowscan)       {         printf("windows扫描:windowscan\n");         ultra_scan(Targets, &ports, WINDOW_SCAN);       }       if (o.finscan)       {         printf("fin扫描:finscan\n");         ultra_scan(Targets, &ports, FIN_SCAN);       }       if (o.xmasscan)       {         printf("xmas扫描:xmasscan\n");         ultra_scan(Targets, &ports, XMAS_SCAN);       }       if (o.nullscan)       {         printf("空扫描:nullscan\n");         ultra_scan(Targets, &ports, NULL_SCAN);       }       if (o.maimonscan)       {         printf("maimon 扫描:maimonscan\n");         ultra_scan(Targets, &ports, MAIMON_SCAN);       }       if (o.udpscan)       {         printf("udp扫描:udpscan\n");         ultra_scan(Targets, &ports, UDP_SCAN);       }       if (o.connectscan)       {         printf("连接扫描:connectscan\n");         ultra_scan(Targets, &ports, CONNECT_SCAN);       }       if (o.sctpinitscan)       {         printf("sctp init 扫描:sctpinitscan\n");         ultra_scan(Targets, &ports, SCTP_INIT_SCAN);       }       if (o.sctpcookieechoscan)       {         printf("sctp cookit 回显扫描:sctpcookieechoscan\n");         ultra_scan(Targets, &ports, SCTP_COOKIE_ECHO_SCAN);       }       if (o.ipprotscan)       {         printf("ip端口扫描:ipprotscan\n");         ultra_scan(Targets, &ports, IPPROT_SCAN);       }       /* These lame functions can only handle one target at a time */       // 这些蹩脚的函数一次只能处理一个目标       if (o.idlescan) {         printf("idlescan:这些蹩脚的函数一次只能处理一个目标\n");         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) {         printf("bouncescan:这些蹩脚的函数一次只能处理一个目标\n");         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)             bounce_scan(Targets[targetno], ports.tcp_ports, ports.tcp_count, &ftp);         }       }       // 服务扫描       if (o.servicescan) {         printf("servicescan:服务扫描\n");         o.current_scantype = SERVICE_SCAN;         service_scan(Targets);       }     }     // 系统扫描     if (o.osscan) {       printf("osscan:系统扫描\n");       OSScan os_engine;       os_engine.os_scan(Targets);     }     if (o.traceroute)     {       printf("traceroute:跟踪路由\n");       traceroute(Targets);     } #ifndef NOLUA     if (o.script || o.scriptversion) {       printf("script:脚本扫描\n");       script_scan(Targets, SCRIPT_SCAN);     } #endif     // 输出扫描结果     for (targetno = 0; targetno < Targets.size(); targetno++) {       printf("输出扫描结果\n");       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\n",                   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();     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); #ifndef NOLUA   if (o.script) {     script_scan(Targets, SCRIPT_POST_SCAN);     printscriptresults(script_scan_results, SCRIPT_POST_SCAN);     while (!script_scan_results->empty()) {       script_scan_results->front().clear();       script_scan_results->pop_front();     }     delete new_targets;     new_targets = NULL;   } #endif   addrset_free(exclude_group);   if (o.inputfd != NULL)     fclose(o.inputfd);   printdatafilepaths();   printfinaloutput();   free_scan_lists(&ports);   eth_close_cached();   if (o.release_memory) {     nmap_free_mem();   }   return 0; }

再看一下端口扫描源码分析 代码流程图

流程解析

  以TCPSYN为例,在nmap_main()中调用ultra_scan(Targets, &ports,SYN_SCAN),此处传入参数目标主机Targets(是vector容器保存的);ports是struct scan_list类型的指针,指向解析出来的端口列表;SYN_SCAN是预先定义的枚举值,让ultra_scan能够辨别出扫描类型。

  进入ultra_scan()后,第一个重要步骤是加载UDP扫描需要的负载,即UDP探测方式需要发送的包的内容(该内容从数据库文件nmap-payloads中读取出来)。此步骤在init_payloads()中完成。

  判断是否是在Windows平台扫描环回接口(loopback),若是,则打印出提示信息:Windows平台无法支持扫描自己的环回接口。

  创建UltraScanInfo对象,使用Targets,ports,scantype初始化。

  开始嗅探(begin_sniffer),启动libpcap库对网络数据包进行嗅探,以便能够接收到目标机的回复包。主要调用libpcap的API:pcap_open_live打开实时嗅探,然后再设置libpcap抓包的过滤器(Filter),最终调用pcap_setfilter()具体设置。

  进入端口扫描的主循环,只要UltraScanInfo中的未完成列表不为空,都将继续执行循环结构体。

  1) 首先进行PING探测操作,发送必要的探测包到目标机特定端口。

  2) 然后重传所有未完成的探测包。处于outstanding状态,并允许重传的探测包将在这里进行重传。

  3) 重传retry_stack中探测包。retry_stack中探测包是从探测包工作台(probe bench)中移动出来的,是重新获得重传机会的探测包。所以在此处检查retry_stack中是否有探测包等待重传。

  4) 检查是否需要传输新的探测包。Nmap扫描时候,是对一批主机扫描同一个端口,然后推进到下一个端口进行扫描。所以,在此处检查是否有主机需要进行新的端口的探测。

  5) 获取时间,并打印出端口扫描状态。

  6) 等待接收回复包。根据不同类型的探测方式,等待接收不同类型的回复包。这里是通过libpcap的API:pcap_next()函数来读取到回复包的。

  7) 获取时间,并对接收到的数据进行处理。

  8) 检测是否按键,若有允许的按键按下(v增加verbose,V减少verbose;d增加debugginglevel,D减少debugginglevel;p是打开packet trace,P是关闭packet trace),则执行相应功能,否则仅仅打印出扫描进度信息。

  退出循环后,首先停止发送频率度量,USI->send_rate_meter.stop(&USI->now),因为此时真正的扫描已经结束,所以此处可以停止度量。

  保存计算出的超时信息,并将扫描过程的详细信息与调试信息打印出来。

  删除UltraScanInfo对象,该对象仅仅用于每一个ultra_scan()函数调用。

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111/* 3rd generation Nmap scanning function. Handles most Nmap port scan types.    The parameter to gives group timing information, and if it is not NULL,    changed timing information will be stored in it when the function returns. It    exists so timing can be shared across invocations of this function. If to is    NULL (its default value), a default timeout_info will be used. */ void ultra_scan(vector &Targets, struct scan_lists *ports,         stype scantype, struct timeout_info *to) {   UltraScanInfo *USI = NULL;///扫描信息控制类   o.current_scantype = scantype;///标记当前扫描类型,用于输出     init_payloads(); /* Load up _all_ payloads into a mapped table */     if (Targets.size() == 0) {     return;   }   #ifdef WIN32   if (scantype != CONNECT_SCAN && Targets[0]->ifType() == devt_loopback) {     log_write(LOG_STDOUT, "Skipping %s against %s because Windows does not support scanning your own machine (localhost) this way.\n", scantype2str(scantype), Targets[0]->NameIP());     return;   } #endif     // Set the variable for status printing   o.numhosts_scanning = Targets.size();     startTimeOutClocks(Targets);   USI = new UltraScanInfo(Targets, ports, scantype);     /* Use the requested timeouts. */   if (to != NULL)     USI->gstats->to = *to;     if (o.verbose) {     char targetstr[128];     bool plural = (Targets.size() != 1);     if (!plural) {       (*(Targets.begin()))->NameIP(targetstr, sizeof(targetstr));     } else Snprintf(targetstr, sizeof(targetstr), "%d hosts", (int) Targets.size());     log_write(LOG_STDOUT, "Scanning %s [%d port%s%s]\n", targetstr, USI->gstats->numprobes, (USI->gstats->numprobes != 1)? "s" : "", plural? "/host" : "");   }   ///begin_sniffer()开启libpcap并设置pcap filter,以便接收目标主机返回的数据包   begin_sniffer(USI, Targets);   while(!USI->incompleteHostsEmpty()) {     ///向目标机发送探测包(probe)     doAnyPings(USI);     ///重传未完成探测过程的数据包     doAnyOutstandingRetransmits(USI); // Retransmits from probes_outstanding     /* Retransmits from retry_stack -- goes after OutstandingRetransmits for        memory consumption reasons */     ///     doAnyRetryStackRetransmits(USI);     ///检查需要进行的新的探测包类型。     doAnyNewProbes(USI);     gettimeofday(&USI->now, NULL);     // printf("TRACE: Finished doAnyNewProbes() at %.4fs\n", o.TimeSinceStartMS(&USI->now) / 1000.0);     printAnyStats(USI);     ///在waitForResponses()中接收libpcap中抓取到的数据包     waitForResponses(USI);     gettimeofday(&USI->now, NULL);     // printf("TRACE: Finished waitForResponses() at %.4fs\n", o.TimeSinceStartMS(&USI->now) / 1000.0);     ///对整个扫描进行统计处理:标记过期的探测包、判断探测是否完毕     processData(USI);         ///扫描过程中,若检测到按键,打印出扫描进度信息     if (keyWasPressed()) {        // This prints something like        // SYN Stealth Scan Timing: About 1.14% done; ETC: 15:01 (0:43:23 remaining);        USI->SPM->printStats(USI->getCompletionFraction(), NULL);        if (o.debugging) {          /* Don't update when getting the current rates, otherwise we can get             anomalies (rates are too low) from having just done a potentially             long waitForResponses without sending any packets. */          USI->log_current_rates(LOG_STDOUT, false);        }                log_flush(LOG_STDOUT);     }   }     USI->send_rate_meter.stop(&USI->now);     /* Save the computed timeouts. */   if (to != NULL)     *to = USI->gstats->to;     ///输出详细信息与调试信息   if (o.verbose) {     char additional_info[128];     if (USI->gstats->num_hosts_timedout == 0)       if (USI->ping_scan) {         Snprintf(additional_info, sizeof(additional_info), "%lu total hosts",                   (unsigned long) Targets.size());       } else {         Snprintf(additional_info, sizeof(additional_info), "%lu total ports",                   (unsigned long) USI->gstats->numprobes * Targets.size());       }     else Snprintf(additional_info, sizeof(additional_info), "%d %s timed out",            USI->gstats->num_hosts_timedout,            (USI->gstats->num_hosts_timedout == 1)? "host" : "hosts");     USI->SPM->endTask(NULL, additional_info);   }   if (o.debugging)     USI->log_overall_rates(LOG_STDOUT);     if (o.debugging > 2 && USI->pd != NULL)     pcap_print_stats(LOG_PLAIN, USI->pd);     delete USI;   USI = NULL; } 再看一下操作系统扫描源码分析

操作系统侦测,包括识别出操作系统类型、版本号、目标机硬件平台类型及附加信息(如TCP序号产生方式、IPID产生方式、启动时间等)。目前Nmap 拥有丰富的系统指纹数据库 (nmap-os-db),能够识别出2600多种操作系统与设备类型。

扫描原理

Nmap使用TCP/IP协议栈指纹来识别不同的操作系统和设备。在RFC规范中,有些地方对TCP/IP的实现并没有强制规定,由此不同的TCP/IP方案中可能都有自己的特殊的处理方式。Nmap主要是根据这些细节上的差异来判断操作系统的类型的。

具体实现方式如下:

Nmap内部包含了2600多已知系统的指纹特征(在文件nmap-os-db文件中)。将此指纹数据库作为进行指纹对比的样本库。 分别挑选一个open和closed的端口,向其发送经过精心设计的TCP/UDP/ICMP数据包,根据返回的数据包生成一份系统指纹。 将探测生成的指纹与nmap-os-db中指纹进行对比,查找匹配的系统。如果无法匹配,以概率形式列举出可能的系统。 命令行选项

OS侦测的用法简单,Nmap提供的命令比较少。

-O: 指定Nmap进行OS侦测。 --osscan-limit: 限制Nmap只对确定的主机的进行OS探测(至少需确知该主机分别有一个open和closed的端口)。 --osscan-guess: 大胆猜测对方的主机的系统类型。由此准确性会下降不少,但会尽可能多为用户提供潜在的操作系统。

流程文件

操作系统侦测功能主要在os_scan2.h/os_scan2.cc文件中实现(os_scan.h/ os_scan.cc是第一代操作系统侦测的代码,目前默认不使用其流程);IPv6的操作系统探测过程主要FPengine.h/FPengine.cc文件中实现。

数据库文件

Nmap操作系统指纹数据库文件nmap-os-db,里面包含了2600多种操作系统与设备的指纹。所谓的指纹,即由特定的回复包提取出的数据特征。

下面摘取其中片段,简单了解其结构。

123456789101112131415161718192021222324252627282930313233#Windows 7 Professional Version 6.1 Build 7600   Fingerprint MicrosoftWindows 7 Professional   ClassMicrosoft | Windows | 7 | general purpose   CPEcpe:/o:microsoft:windows_7::professional   SEQ(SP=FC-106%GCD=1-6%ISR=108-112%TI=I%II=I%SS=S%TS=7)   OPS(O1=M5B4NW8ST11%O2=M5B4NW8ST11%O3=M5B4NW8NNT11%O4=M5B4NW8ST11%O5=M5B4NW8ST11%O6=M5B4ST11)   WIN(W1=2000%W2=2000%W3=2000%W4=2000%W5=2000%W6=2000)   ECN(R=Y%DF=Y%T=7B-85%TG=80%W=2000%O=M5B4NW8NNS%CC=N%Q=)   T1(R=Y%DF=Y%T=7B-85%TG=80%S=O%A=S+%F=AS%RD=0%Q=)   T2(R=N)   T3(R=N)   T4(R=N)   T5(R=Y%DF=Y%T=7B-85%TG=80%W=2000%S=Z%A=S+%F=AR%O=%RD=0%Q=)   T6(R=N)   T7(R=N)   U1(DF=N%T=7B-85%TG=80%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)   IE(DFI=N%T=7B-85%TG=80%CD=Z)

  以上是Windows 7 professional版本的指纹特征。

  第一行为注释行,说明此指纹对应的操作系统与版本。

  Fingerprint关键字定义一个新的指纹,紧随其后的是指纹名字Microsoft Windows 7Professional。

  Class行用于指定该指纹所属的类别,依次指定该系统的vendor(生产厂家), OS family(系统类别), OS generation(第几代操作系统), and device type(设备类型),如此处Vendor为Microsoft, OS family为Windows,OS generation为7,设备类型为通用设备(普通PC或服务器)。

  接下来是CPE行,此行非常重要,使用CPE(Common Platform Enumeration,通用平台枚举)格式描述该系统的信息。以标准的CPE格式来描述操作系统类型,便于Nmap与外界信息的交换,比如可以很快从网上开源数据库查找到CPE描述的操作系统具体信息。

  关于CPE标准介绍:http://cpe.mitre.org/

  此处作为指纹描述字段的CPE格式如下:

cpe:/::::::

  接下来从SEQ到IE的13行都是具体指纹数据描述行,在对比指纹时,就是对比这13行里面的具体数据,如果匹配则目标机为指纹所描述的系统类型。

  SEQ描述顺序产生方式;OPS描述TCP包中可选字段的值;WIN描述TCP包的初始窗口大小;ECN(Explicit Congestion Notification)描述TCP明确指定拥塞通知时的特征;T1-T7描述TCP回复包的字段特征;U1描述向关闭的UDP发包产生的回复的特征;IE描述向目标机发送ICMP包产生的特征。

核心类

下面简要介绍操作系统扫描部分涉及到的Class。

OSScan

  OSScan是管理操作系统扫描过程的类,将IPv4和IPv6的操作系统扫描过程封装起来,为Nmap主程序提供统一的调用接口os_scan()。

  以下是该类主要的内容:

提供操作系统扫描接口os_scan() 提供重置函数接口(初始化必要的变量)。 保存ip的协议版本 执行分块与扫描过程,确定并发执行的数量 针对IPv4进行操作系统扫描 针对IPv6进行操作系统扫描 123456789101112131415161718/** This is the class that performs OS detection (both IPv4 and IPv6).   * Using it is simple, just call os_scan() passing a list of targets.   * The results of the detection will be stored inside the supplied   * target objects. */ class OSScan {    private:   int ip_ver;             /* IP version for the OS Scan (4 or 6) */   int chunk_and_do_scan(std::vector &Targets, int family);   int os_scan_ipv4(std::vector &Targets);   int os_scan_ipv6(std::vector &Targets);           public:    OSScan();    ~OSScan();    void reset();    int os_scan(std::vector &Targets); }; OsScanInfo

  OsScanInfo类整体管理全部主机的扫描过程,其中维护操作系统扫描未完成列表。

  下面是其中主要的内容:

未完成扫描列表std::listincompleteHosts。 扫描起始时间starttime. 未完成列表访问接口,读取总数、获取下一个、 查找主机、重置迭代器。 移除已经完成的主机。 HostOsScanInfo

  HostOsScanInfo管理单个主机的操作系统扫描的信息。

  主要包含以下具体内容:

对应的目标机Target *target. 被包含的OsScanInfo对象地址。 当前主机产生的指纹信息FingerPrint **FPs 记录是否超时、是否完成 单个OS扫描每一轮(one round)的统计信息HostOsScanStats *hss。(同一个主机可能被扫描多个回合,所以这里使用该对象来管理每一轮扫描的信息) 指纹扫描结果与匹配情况。 1234567891011121314151617181920/* The overall os scan information of a host:  *  - Fingerprints gotten from every scan round;  *  - Maching results of these fingerprints.  *  - Is it timeout/completed?  *  - ... */ class HostOsScanInfo {    public:   HostOsScanInfo(Target *t, OsScanInfo *OSI);   ~HostOsScanInfo();     Target *target;       /* The target                                  */   FingerPrintResultsIPv4 *FPR;   OsScanInfo *OSI;      /* The OSI which contains this HostOsScanInfo  */   FingerPrint **FPs;    /* Fingerprints of the host                    */   FingerPrintResultsIPv4 *FP_matches; /* Fingerprint-matching results      */   bool timedOut;        /* Did it time out?                            */   bool isCompleted;     /* Has the OS detection been completed?        */   HostOsScanStats *hss; /* Scan status of the host in one scan round   */ }; HostOsScanStats

  HostOsScanStats管理每个主机每一轮OS扫描的统计信息。

  内容概括起来如下:

扫描探测包的管理 以太网信息管理 指纹信息的管理 TCP序号、IPID、启动时间等信息管理 其他杂项信息 OFProbe

  OFProbe管理OS扫描过程需要的探测包信息,该对象中本身只包含用于构建探测包的关键属性,而不包含探测包本身。另外该对象也包含时序信息。

该对象主要在HostOsScanStats中使用。

代码流程

  在nmap.cc中的nmap_main()函数如果检测用户配置了OS扫描选项(或-A选项),那么将启动扫描系统扫描功能。Nmap将会创建OSScan对象,并调用OSScan::os_scan()进入详细的探测过程。

代码流程图

流程解析

  OSScan::os_scan()的执行流程非常简单,只有短短30行代码。

  首先将传入的Targets参数依据地址类型划分两个小组,IPv4和IPv6。因为对于两类的地址扫描的方式不同。

  随后调用os_scan_ipv4()做IPv4的操作系统扫描过程。

  然后调用os_scan_ipv6()做IPv6的操作系统扫描过程。

  判断ipv4和ipv6两类操作系统扫描执行的结果,返回最终结果。

  因为os_scan_ipv4()才是完成ipv4类的操作系统扫描真正的地方,这里我们也简要描述其过程。

  首先,定义未匹配主机管理列表listunMatchedHosts,用于管理超时未匹配的主机。

  随后,初始化扫描性能变量scan_perforamance_vars,以便对整个操作系统扫描过程时序与性能进行控制。

  创建OsScanInfo对象、并初始化必要的时间值。

  begin_sniffer()打开libpcap,设置相应filter,进行回复包的监听。

  随后进入主循环过程,直到所有的主机都完成扫描,才退出循环。下面是主要循环步骤:

  A. 根据进行的扫描次数,适当休眠

  B. 准备该轮扫描的所需的环境,清理垃圾数据并初始化必要的变量

  C. 做顺序产生测试(Sequence generationtests),提取指纹的SEQ/OPS/WIN/T1几行数据。

  D. 做TCP/UDP/ICMP综合探测,提取指纹数据。

  E. 处理此轮探测的结果,匹配相应的系统指纹,移除已完成。

  F. 移除超时不匹配的主机到unMatchedHosts列表中。

  退出循环后,将unMatchedHosts列表中主机移动到未完成列表,然后统一对其进行最接近指纹匹配。

  返回扫描执行结果。

代码注释

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115/* This function performs the OS detection. It processes the supplied list of  * targets and classifies it into two groups: IPv4 and IPv6 targets. Then,  * OS detection is carried out for those two separate groups. It returns  * OP_SUCCESS on success or OP_FAILURE in case of error. */ int OSScan::os_scan(vector &Targets) {   vector ip4_targets; ///IPv4类型地址的目标机   vector ip6_targets; ///IPv6类型地址的目标机   int res4 = OP_SUCCESS, res6 = OP_SUCCESS;     /* Make sure we have at least one target */   if (Targets.size() af() == AF_INET6)           ip6_targets.push_back(Targets[i]);       else           ip4_targets.push_back(Targets[i]);   }     /* Do IPv4 OS Detection */   ///在os_scan_ipv4()函数中具体实现IPv4的操作系统探测的过程   if (ip4_targets.size() > 0)       res4 = this->os_scan_ipv4(ip4_targets);     /* Do IPv6 OS Detection */   ///在os_scan_ipv6()函数中具体实现IPv6的操作系统探测的过程   if (ip6_targets.size() > 0)       res6 = this->os_scan_ipv6(ip6_targets);     /* If both scans were succesful, return OK */   if (res4 == OP_SUCCESS && res6 == OP_SUCCESS)     return OP_SUCCESS;   else     return OP_FAILURE; }     /* Performs the OS detection for IPv4 hosts. This method should not be called  * directly. os_scan() should be used instead, as it handles chunking so  * you don't do too many targets in parallel */  ///IPv4的操作系统探测的实现函数,由os_scan()来调用。 int OSScan::os_scan_ipv4(vector &Targets) {   int itry = 0;   /* Hosts which haven't matched and have been removed from incompleteHosts because    * they have exceeded the number of retransmissions the host is allowed. */   list unMatchedHosts; ///记录超时或超过最大重传而未匹配的主机扫描信息     /* Check we have at least one target*/   if (Targets.size() == 0) {     return OP_FAILURE;   }     perf.init();///初始化扫描性能变量     ///操作系统扫描的管理对象,维护未完成扫描列表std::list incompleteHosts;   OsScanInfo OSI(Targets);   if (OSI.numIncompleteHosts() == 0) {     /* no one will be scanned */     return OP_FAILURE;   }   ///设置起始时间与超时   OSI.starttime = o.TimeSinceStart();   startTimeOutClocks(&OSI);     ///创建HOS对象,负责管理单个主机的具体扫描过程   HostOsScan HOS(Targets[0]);     /* Initialize the pcap session handler in HOS */   ///打开libpcap,设置对应的BPF filter,以便接收目标的回复包   begin_sniffer(&HOS, Targets);   while (OSI.numIncompleteHosts() != 0) {     if (itry > 0)       sleep(1);     if (itry == 3)       usleep(1500000); /* Try waiting a little longer just in case it matters */     if (o.verbose) {       char targetstr[128];       bool plural = (OSI.numIncompleteHosts() != 1);       if (!plural) {     (*(OSI.incompleteHosts.begin()))->target->NameIP(targetstr, sizeof(targetstr));       } else Snprintf(targetstr, sizeof(targetstr), "%d hosts", (int) OSI.numIncompleteHosts());       log_write(LOG_STDOUT, "%s OS detection (try #%d) against %s\n", (itry == 0)? "Initiating" : "Retrying", itry + 1, targetstr);       log_flush_all();     }     ///准备第itry轮的OS探测:删除陈旧信息、初始化必要变量     startRound(&OSI, &HOS, itry);     ///执行顺序产生测试(发送6个TCP探测包,每隔100ms一个)     doSeqTests(&OSI, &HOS);     ///执行TCP/UDP/ICMP探测包测试     doTUITests(&OSI, &HOS);     ///对该轮探测的结果做指纹对比,获取OS扫描信息     endRound(&OSI, &HOS, itry);     ///将超时未匹配的主机移动到unMatchedHosts列表中     expireUnmatchedHosts(&OSI, &unMatchedHosts);     itry++;   }     /* Now move the unMatchedHosts array back to IncompleteHosts */   ///对没有找到匹配的主机,将之移动的未完成列表,并查找出最接近的指纹(以概率形式展现给用户)   if (!unMatchedHosts.empty())     OSI.incompleteHosts.splice(OSI.incompleteHosts.begin(), unMatchedHosts);     if (OSI.numIncompleteHosts()) {     /* For hosts that don't have a perfect match, find the closest fingerprint      * in the DB and, if we are in debugging mode, print them. */     findBestFPs(&OSI);     if (o.debugging > 1)       printFP(&OSI);   }     return OP_SUCCESS; } 推荐博客:

https://blog.csdn.net/whatday/article/details/89181478

https://blog.csdn.net/xundh/article/details/46571971



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

      专题文章
        CopyRight 2018-2019 实验室设备网 版权所有