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;
#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<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");
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 <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 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)
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 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;
}
下一篇: 强行扩展Unity原生组件