Nmap源码分析(整体架构)
整體架構
功能目錄
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()函數。下面是精簡后的源碼:
int 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 < Targets.size(); targetno++) {
currenths = Targets[targetno];
前后的代碼都比較多,下次再抽時間細致分析。
這里引用一個別人做的流程圖:
主體程序位置在nmap.cc內的nmap_main函數
新建一個主機的單例對象
#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時,使用了單例模式。
int nmap_main(int argc, char *argv[]) {int i;std::vector<Target *> 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; #endifunsigned int ideal_scan_group_sz = 0;Target *currenths;char myname[FQDN_LEN + 1];int sourceaddrwarning = 0; /* Have we warned them yet about unguessablesource 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);}} #endifnow = time(NULL);local_time = localtime(&now);if (o.debugging)nbase_set_log(fatal, error);elsenbase_set_log(fatal, NULL);if (argc < 2){printusage();exit(-1);}Targets.reserve(100); #ifdef WIN32win_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.isr00twin_init(); #endif// 延遲處理的操作printf("延遲處理的操作\n");apply_delayed_options();/* 這里用到的變量route_dst_hosts是由參數 --route-dst debugging模式定義的目標列表。定義如下: static std::vector<std::string> 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");elseprintf(" 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);// 掃描的簡要信息 記錄到xmlStrncpy(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 machineparseable 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 becauseof it, but we really shouldn't get an unexpectedSIGPIPE */ #endifif (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 parametersif (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 NOLUAif (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();}} #endifif (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;elsebreak;}// -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 somerare cases, such IPs CAN be port successfully scanned and evenconnected to */// 一些情況下,主機有返回狀態,全狀態為HOST_DOWNif (!(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;elsebreak;}// RawScan ,如SYN/FIN/ARPif (o.RawScan()) {printf("RawScan ,如SYN/FIN/ARP \n");if (currenths->SourceSockAddr(NULL, NULL) != 0) {if (o.SourceSockAddr(&ss, &sslen) == 0) {// 直接設置IPprintf("直接設置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 <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())fatal("Do not have appropriate device name for target");/* Hosts in a group need to be somewhat homogeneous. Put this host inthe next group if necessary. See target_needs_new_hostgroup for thedetails 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 printingo.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 worryif (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 printedidle_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 printedif (ftp.sd <= 0)ftp_anon_connect(&ftp);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 NOLUAif (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 NOLUAprinthostscriptresults(currenths); #endifif (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 NOLUAif (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;} #endifaddrset_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; }?
總結
以上是生活随笔為你收集整理的Nmap源码分析(整体架构)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 浅谈渗透测试之前期信息搜集
- 下一篇: twisted系列教程十二–为serve