Nmap源码分析(主机发现)
来源:互联网 发布:.moe域名注册 编辑:程序博客网 时间:2024/06/05 05:14
Nmap源码分析(主机发现)
2012年8月9日
Nmap在进行真正的端口扫描之前,通常需要确定目标主机是否在线(主机发现过程),以免发送大量探测包到不在线的主机。主机发现作为Nmap的基本功能之一,用户也可以单独运用。例如,仅仅需要确定局域网内哪些IP在线,那么可用“主机发现”功能扫描所有机器,枚举出在线主机即可,而没有必要进行端口扫描、服务侦测、OS侦测等更加详细的操作。
1 简单回顾
命令行参数
Nmap提供的主机发现参数相对较少,易于掌握:
-sL: List Scan 列表扫描,仅将指定的目标的IP列举出来,不进行主机发现。-sn: Ping Scan 只进行主机发现,不进行端口扫描。-Pn: 将所有指定的主机视作开启的,跳过主机发现的过程。-PS/PA/PU/PY[portlist]: 使用TCP SYN/ACK或SCTP INIT/ECHO方式进行发现。-PE/PP/PM: 使用ICMP echo, timestamp, and netmask 请求包发现主机。-PO[protocol list]: 使用IP协议包探测对方主机是否开启。-n/-R: -n表示不进行DNS解析;-R表示总是进行DNS解析。--dns-servers <serv1[,serv2],...>: 指定DNS服务器。--system-dns: 指定使用系统的DNS服务器--traceroute: 追踪每个路由节点
2 实现框架
Nmap主机发现部分的源码比较简洁。在nmap_main()函数的主循环部分,通过nexthost()函数进行具体的主机发现过程,在nexthost()函数中主要分为两个阶段:地址解析阶段、实际探测阶段。地址解析阶段:主要负责从主机表达式中解析出目标主机地址,将之存放在hostbatch中,并配置该主机所需的路由、网口、MAC地址、源IP等信息。实际发现阶段:分别对解析出来的目标主机,进行实际的探测以及获取RDNS相关信息,例如采用ARP包发现局域网内主机是否在线。
流程图如下所示:
2.1 地址解析阶段
从主机表达式中获取目标主机地址,主要思想包括以下几个方面:
批量进行主机发现
批量处理,可以加快主机发现的速率。默认配置以4096个目标地址作为一批(batch),若配置了--randomize-hosts选项,每个batch大小为4096*4(以便能有更多的IP地址混合洗牌、乱序扫描)。
从主机表达式获取目标主机地址
主机表达式(hostexpression),是Nmap用于管理主机的方式,该数据结构对应到用户在命令行中传入的目标机地址。例如,命令行nmap192.168.1-10.1-254 scanme.nmap.org/24中,192.168.1-10.1-254为一个主机表达式,而scanme.nmap.org/24为另一个主机表达式。Nmap需扫描的目标地址,即逐个解析该表达式包含的各个IP分别是多少,如scanme.nmap.org/24,首先需要进行DNS域名查询,获取scanme.nmap.org对应的IP地址,然后将与此地址的高24位相同的C类IP地址都将被获取出来。
跳过被排除的地址
如果使用--exclude或--exclude-file指定了排除地址,主机发现时应当跳过该类型地址。
设置已转换地址
若该地址在已经被转换解析,即在解析主机表达式过程中(parse_expr()函数),已经处理了该地址,那么设置该地址对应的转换的地址或名字。例如,在上述例子中,scanme.nmap.org/24表达式在解析过程中,scanme.nmap.org的地址会被DNS查询出来,记录在主机表达式中。如果在从该表达式过程取地址时,取出的地址正好对应的scanme.nmap.org的IP地址,那么说明该地址之前已被转换解析,此时让该主机记录被转换解析的表达式名字(此处为scanme.nmap.org/24),并记录转换地址列表(同一域名可能对应到多个不同IP地址)。
获取所需源IP与网络设备
需要配置源端的IP地址与网卡信息,当且仅当:用户具有系统权限(以root运行),并至少满足以下三个条件之一:
1. PING类型为TCP/UDP/SCTP/PROTOCOL/ARP packet;
2. Nmap进行RawScan,即会对原始TCPIP协议packet进行定制,如控制TCP的flag类型等;
3. Nmap在Windows平台运行并且PING类型为ICMP ECHO/ICMP TIMESTAMP/ICMP MASK类型。
获取源端IP与网络设备,需要进行路由信息查询,调用nmap_route_dst()函数。根据目的地址与查询的路由表对表,决定将采用哪个网卡发送数据包,设置直连状态(目标机与源端是否直接相连)、设置接口类型(包括devt_ethernet,devt_loopback, devt_p2p, devt_other)、设置MAC地址、源端IP地址、设置诱骗地址、设置设备名字、设置MTU等信息。
判断是否需要重新划分批次
批量进行主机发现是为了加快发现速度,如果新发现的主机与本批次中其他主机差异较大,那么在进行主机发现时,反而可能降低性能。所以,这里需要检查该目标是否需要新的批次。需要划分新批次的情况有以下几种:
1. 目标主机的地址类型不同。例如,批次内的目标机为IPv4地址,当前主机为IPv6。
2. 目标主机需要网卡不同。例如,批次内目标机需要网卡A进行探测,当前需要网卡B。
3. 目标主机需要不同IP地址。例如,用户指定欺骗的IP地址。
4. 目标主机与源主机直接相连,而其他主机不直接相连,反之亦然。
5. 目标主机的IP地址与当前批次的其他目标机相同(此种情况下,无法判定回复包到底来自哪个目标主机)。
更换主机表达式
若当前主机表达式包含的目标主机已经被获取完毕,而且当前批次允许的最大目标主机数量还未饱和,那么会更换下一个主机表达式继续解析目标主机地址(若此时还有剩余主机表达式)。
2.2 实际发现阶段
在从主机表达式获取完毕了目标主机后,就开始批量进行实际发现的过程。这里主要包含以下几个方面的内容:
检查该批次是否为空
如果在地址解析阶段,无法找到有效目标地址,那么该批次可能是空的。此处若检查到hostbatch为空,就结束主机发现过程。
随机打乱
如果用户在命令行中使用--randomize-hosts,那么在对目标地址进行探测时需要打乱顺序执行(为防止某些防火墙或IDS检测到用户的扫描)。
方式1:ARP方式探测
如果该批次内所有的目标主机都在源主机所在的以太网内,并且用户没有指定--send-ip(表示偏好通过发送IP数据包探测目标主机)选项,那么采用ARP REQUEST的数据包探测所有的目标主机是否在线。依次在局域网内广播ARP查询包,例如:Broadcast ARP Who has192.168.1.100? Tell 192.168.1.102
该方式在arpping()函数中实现,arpping()函数最终调用ultra_scan()进行扫描(ultra_scan()是Nmap中统一的扫描函数,能完成丰富的功能,在端口扫描阶段大量运用,在主机发现阶段也有被调用)。
ETH报文设置
若用户指定偏好以使用ethernet数据包进行探测(命令行选项:--send-eth),那么需要设置该目标主机的下一跳的MAC地址,以便在之后构建ethernet包时能够找到传送的目的MAC地址。
方式2:列表扫描与无PING扫描
此方式其实并没有进行真正扫描,而是直接将目标主机的状态设置为HOST_UP。当用户指定列表扫描(选项-sL,仅仅列举出所有IP地址而不做真正的扫描,直接将IP地址输出,以用于后续的其他操作),或者当用户指定不需进行主机发现(选项-Pn,当用户确知目标主机在线,那么可用该选项跳过主机发现,以便加快扫描速度),此处将目标主机标识为在线的。
方式3:其他方式探测
上述两种发现方式没有覆盖的所有情况,都在此种方式中进行处理。Nmap默认情况下,会发送四种数据包探测目标主机是否在线:
1. ICMPecho request
2. aTCP SYN packet to port 443
3. aTCP ACK packet to port 80
4. anICMP timestamp request
只要收到任何一个探测包的回复,就说明目标主机在线。
此方式在massping()函数中实现,最终该函数会调用到ultra_scan()函数进行端口扫描。
RDNS解析
若用户没有配置-n选项(表示Never Dns Resolution),那么会对该主机进行reverse dns解析,尝试查询出该IP地址对应的域名,因为可能此IP对应到某个固定域名。这样就可以识别到目标主机更多的信息,而且便于维护信息的一致性。
3 代码分析
主机发现部分核心函数nexthost()的具体实现代码:
Target *nexthost(HostGroupState *hs, const addrset *exclude_group, struct scan_lists *ports, int pingtype) { int i; struct sockaddr_storage ss; size_t sslen; struct route_nfo rnfo; bool arpping_done = false; struct timeval now; ///当已经批量地探测一组主机,并将主机缓存在hostbatch中时,直接返回该主机对象指针即可 if (hs->next_batch_no < hs->current_batch_sz) { /* Woop! This is easy -- we just pass back the next host struct */ return hs->hostbatch[hs->next_batch_no++]; } /* Doh, we need to refresh our array */ /* for (i=0; i < hs->max_batch_sz; i++) hs->hostbatch[i] = new Target(); */ ///进行新一批的主机探测,以下do{}while(1)循环是先产生各个IP的主机对象并放入hostbatch[]中 ///真正确定主机是否在线,是在batchfull:代码段内 hs->current_batch_sz = hs->next_batch_no = 0; do { /* Grab anything we have in our current_expression */ while (hs->current_batch_sz < hs->max_batch_sz && hs->current_expression.get_next_host(&ss, &sslen) == 0) { Target *t; ///以下跳过被排除地址 if (hostInExclude((struct sockaddr *)&ss, sslen, exclude_group)) { continue; /* Skip any hosts the user asked to exclude */ } t = new Target(); t->setTargetSockAddr(&ss, sslen); /* Special handling for the resolved address (for example whatever scanme.nmap.org resolves to in scanme.nmap.org/24). */ if (hs->current_expression.is_resolved_address(&ss)) { if (hs->current_expression.get_namedhost()) t->setTargetName(hs->current_expression.get_resolved_name()); t->resolved_addrs = hs->current_expression.get_resolved_addrs(); } /* We figure out the source IP/device IFF 1) We are r00t AND 2) We are doing tcp or udp pingscan OR 3) We are doing a raw-mode portscan or osscan or traceroute OR 4) We are on windows and doing ICMP ping */ if (o.isr00t && ((pingtype & (PINGTYPE_TCP|PINGTYPE_UDP|PINGTYPE_SCTP_INIT|PINGTYPE_PROTO|PINGTYPE_ARP)) || o.RawScan()#ifdef WIN32 || (pingtype & (PINGTYPE_ICMP_PING|PINGTYPE_ICMP_MASK|PINGTYPE_ICMP_TS))#endif // WIN32 )) { t->TargetSockAddr(&ss, &sslen); if (!nmap_route_dst(&ss, &rnfo)) { fatal("%s: failed to determine route to %s", __func__, t->NameIP()); } if (rnfo.direct_connect) { t->setDirectlyConnected(true); } else { t->setDirectlyConnected(false); t->setNextHop(&rnfo.nexthop, sizeof(rnfo.nexthop)); } t->setIfType(rnfo.ii.device_type); if (rnfo.ii.device_type == devt_ethernet) { if (o.spoofMACAddress()) t->setSrcMACAddress(o.spoofMACAddress()); else t->setSrcMACAddress(rnfo.ii.mac); } t->setSourceSockAddr(&rnfo.srcaddr, sizeof(rnfo.srcaddr)); if (hs->current_batch_sz == 0) /* Because later ones can have different src addy and be cut off group */ o.decoys[o.decoyturn] = t->v4source(); t->setDeviceNames(rnfo.ii.devname, rnfo.ii.devfullname); t->setMTU(rnfo.ii.mtu); // printf("Target %s %s directly connected, goes through local iface %s, which %s ethernet\n", t->NameIP(), t->directlyConnected()? "IS" : "IS NOT", t->deviceName(), (t->ifType() == devt_ethernet)? "IS" : "IS NOT"); } /* Does this target need to go in a separate host group? */ if (target_needs_new_hostgroup(hs, t)) { /* Cancel everything! This guy must go in the next group and we are out of here */ hs->current_expression.return_last_host(); delete t; goto batchfull; } hs->hostbatch[hs->current_batch_sz++] = t; } ///若当前batch数组还没有填满,并且还有更多主机表达式,那么尝试进行新的表达式解析 if (hs->current_batch_sz < hs->max_batch_sz && hs->next_expression < hs->num_expressions) { /* We are going to have to pop in another expression. */ while (hs->next_expression < hs->num_expressions) { const char *expr; expr = hs->target_expressions[hs->next_expression++]; if (hs->current_expression.parse_expr(expr, o.af()) != 0)///解析表达式 log_bogus_target(expr);///若解析出错,标记此表达式 else break;///解析成功,进行新一轮的目标地址IP解析 } } else break; } while(1);batchfull: if (hs->current_batch_sz == 0)///没有解析出有效地址,返回NULL return NULL; /* OK, now we have our complete batch of entries. The next step is to randomize them (if requested) */ if (hs->randomize) { ///若命令行指定randomize-hosts选项,那么将目标地址随机打乱 hoststructfry(hs->hostbatch, hs->current_batch_sz); } /* First I'll do the ARP ping if all of the machines in the group are directly connected over ethernet. I may need the MAC addresses later anyway. */ ///探测方式1:主机组内所有IP地址都直连在ethernet内,那么进行ARP PING报文探测 ///向局域网广播:ARP REQUEST包,询问谁持有xx.xx.xx.xxIP地址 if (hs->hostbatch[0]->ifType() == devt_ethernet && hs->hostbatch[0]->af() == AF_INET && hs->hostbatch[0]->directlyConnected() && o.sendpref != PACKET_SEND_IP_STRONG) { arpping(hs->hostbatch, hs->current_batch_sz);///局域网内主机发现的执行函数 arpping_done = true; } /* No other interface types are supported by ND ping except devt_ethernet at the moment. */ if (hs->hostbatch[0]->ifType() == devt_ethernet && hs->hostbatch[0]->af() == AF_INET6 && hs->hostbatch[0]->directlyConnected() && o.sendpref != PACKET_SEND_IP_STRONG) { arpping(hs->hostbatch, hs->current_batch_sz); arpping_done = true; } ///若命令行指定了--send-eth,并判断到当前接口类型为ethernet网卡, ///对每一个状态不是HOST_DOWN且未超时的主机,设置下一跳MAC地址 gettimeofday(&now, NULL); if ((o.sendpref & PACKET_SEND_ETH) && hs->hostbatch[0]->ifType() == devt_ethernet) { for (i=0; i < hs->current_batch_sz; i++) { if (!(hs->hostbatch[i]->flags & HOST_DOWN) && !hs->hostbatch[i]->timedOut(&now)) { if (!setTargetNextHopMAC(hs->hostbatch[i])) { fatal("%s: Failed to determine dst MAC address for target %s", __func__, hs->hostbatch[i]->NameIP()); } } } } /* TODO: Maybe I should allow real ping scan of directly connected ethernet hosts? */ /* Then we do the mass ping (if required - IP-level pings) */ ///探测方式2:若指定不进行PING操作(如命令行指定了-Pn或-sL都不会进行PING操作)而arpping_done为被标记 ///或指定扫描自己回环网口,那么都在此处将主机标记位HOST_UP. if ((pingtype == PINGTYPE_NONE && !arpping_done) || hs->hostbatch[0]->ifType() == devt_loopback) { for (i=0; i < hs->current_batch_sz; i++) { if (!hs->hostbatch[i]->timedOut(&now)) { initialize_timeout_info(&hs->hostbatch[i]->to); hs->hostbatch[i]->flags |= HOST_UP; /*hostbatch[i].up = 1;*/ if (pingtype == PINGTYPE_NONE && !arpping_done)///用户指定该主机为HOST_UP,例如用户已知某个目标已经开启, hs->hostbatch[i]->reason.reason_id = ER_USER;///就可以通过-Pn选项让Nmap不进行PING过程。 else hs->hostbatch[i]->reason.reason_id = ER_LOCALHOST;///本地主机,当然为HOST_UP } } } else if (!arpping_done) {///探测方式3:其他情况,则采用massping方式探测主机是否在线 massping(hs->hostbatch, hs->current_batch_sz, ports); } ///若命令行没有指定-n选项(含义是不做DNS/RDNS解析),那么这里对rdns进行解析 if (!o.noresolve) nmap_mass_rdns(hs->hostbatch, hs->current_batch_sz); ///返回hostbatch中当前next_batch_no所在的主机(next_host()会批量解析主机IP,下一次进入时直接返回已解析的地址)。 return hs->hostbatch[hs->next_batch_no++];}
- Nmap源码分析(主机发现)
- Nmap主机发现
- nmap学习之主机发现
- Nmap源码分析(端口扫描)
- Nmap源码分析(基本框架)
- Nmap源码分析(操作系统扫描)
- Nmap源码分析(脚本引擎)
- Nmap源码分析(基本框架)
- Nmap源码分析(基本框架)
- Nmap源码分析(操作系统扫描)
- Nmap源码分析(脚本引擎)
- Nmap源码分析(基本框架)
- Nmap源码分析
- [Nmap渗透测试指南]第一章(Nmap基础)+第二章(Nmap主机发现)
- Nmap源码分析(服务与版本扫描)
- Nmap 源码学习三 nmap_main主程序分析
- Nmap分析
- Nmap 6.47源码分析(一)编译环境搭建和程序功能学习
- 第03章 面向对象 35 hashcode解释
- HDU3722 Card Game 二分图之最优匹配 KM算法
- 公约数
- DB2 分区
- poj 1663 Number Steps
- Nmap源码分析(主机发现)
- 第03章 面向对象 36 Object类之equals方法
- 各类发票中英文模板
- mexopencv-利用orb feature detector 提取和匹配特征
- 搜索专题题目推荐
- Objective-C中一种消息处理方法performSelector: withObject:
- 折半查找
- 暗时间-读后感之一
- linux下破解 Beyond Compare 3.3.2