Nmap 源码深度解析:nmap_main() 函数逐行详解
本文将逐行深入分析 Nmap 的核心函数
nmap_main(),带你理解扫描引擎的完整工作流程和每个关键步骤的实现细节。
📚 目录
- [Nmap 源码深度解析:nmap_main() 函数逐行详解](#Nmap 源码深度解析:nmap_main() 函数逐行详解)
- [📚 目录](#📚 目录)
- [1. 函数定义与参数模拟](#1. 函数定义与参数模拟)
- [📖 代码详解](#📖 代码详解)
- [2. 变量声明](#2. 变量声明)
- [📖 代码详解](#📖 代码详解)
- [📖 Lua 脚本相关变量](#📖 Lua 脚本相关变量)
- [📖 其他变量](#📖 其他变量)
- [3. 平台特定代码](#3. 平台特定代码)
- [Linux 平台特定代码](#Linux 平台特定代码)
- [📖 代码详解](#📖 代码详解)
- [4. 时间和本地化初始化](#4. 时间和本地化初始化)
- [📖 代码详解](#📖 代码详解)
- [5. 参数检查](#5. 参数检查)
- [📖 代码详解](#📖 代码详解)
- [6. 向量预留空间](#6. 向量预留空间)
- [📖 代码详解](#📖 代码详解)
- [7. Windows 平台初始化](#7. Windows 平台初始化)
- [📖 代码详解](#📖 代码详解)
- [8. 选项解析](#8. 选项解析)
- [📖 代码详解](#📖 代码详解)
- [9. 日志系统初始化](#9. 日志系统初始化)
- [📖 代码详解](#📖 代码详解)
- [10. 终端初始化](#10. 终端初始化)
- [📖 代码详解](#📖 代码详解)
- [11. 延迟选项应用](#11. 延迟选项应用)
- [📖 代码详解](#📖 代码详解)
- [12. 路由信息打印](#12. 路由信息打印)
- [📖 代码详解](#📖 代码详解)
- [13. 接口列表打印](#13. 接口列表打印)
- [📖 代码详解](#📖 代码详解)
- [14. FTP 跳转扫描初始化](#14. FTP 跳转扫描初始化)
- [📖 代码详解](#📖 代码详解)
- [15. 时间格式化](#15. 时间格式化)
- [📖 代码详解](#📖 代码详解)
- [16. XML 输出初始化](#16. XML 输出初始化)
- [📖 代码详解](#📖 代码详解)
- [17. 日志输出初始化](#17. 日志输出初始化)
- [📖 代码详解](#📖 代码详解)
- [18. 端口信息输出](#18. 端口信息输出)
- [📖 代码详解](#📖 代码详解)
- [19. 信号处理](#19. 信号处理)
- [📖 代码详解](#📖 代码详解)
- [20. 并行度检查](#20. 并行度检查)
- [📖 代码详解](#📖 代码详解)
- [21. 调试信息输出](#21. 调试信息输出)
- [📖 代码详解](#📖 代码详解)
- [22. 定时参数打印](#22. 定时参数打印)
- [📖 代码详解](#📖 代码详解)
- [23. 端口列表初始化](#23. 端口列表初始化)
- [📖 代码详解](#📖 代码详解)
- [24. 端口随机化](#24. 端口随机化)
- [📖 代码详解](#📖 代码详解)
- [25. 排除目标初始化](#25. 排除目标初始化)
- [📖 代码详解](#📖 代码详解)
- [26. Lua 脚本初始化](#26. Lua 脚本初始化)
- [📖 代码详解](#📖 代码详解)
- [27. 主机发现与扫描循环](#27. 主机发现与扫描循环)
- [📖 代码详解](#📖 代码详解)
- [28. 后扫描脚本执行](#28. 后扫描脚本执行)
- [📖 代码详解](#📖 代码详解)
- [29. 资源清理](#29. 资源清理)
- [📖 代码详解](#📖 代码详解)
- [30. 总结](#30. 总结)
- [📖 参考资料](#📖 参考资料)
1. 函数定义与参数模拟
cpp
int nmap_main(int argc, char *argv[]) {
// 临时模拟指令参数:nmap -O scanme.nmap.org
argc = 3; // 参数总数(程序名、-O、目标地址)
// 定义参数数组(字符串存储在程序可访问内存中)
static char* mock_argv[] = {
(char*)"nmap.exe", // argv[0]:程序名
(char*)"-O", // argv[1]:启用OS检测的核心参数
(char*)"scanme.nmap.org" // argv[2]:扫描目标(Nmap测试靶机)
};
argv = mock_argv; // 覆盖原argv
📖 代码详解
函数定义:
cpp
int nmap_main(int argc, char *argv[])
nmap_main()是 Nmap 的核心函数- 接受命令行参数
argc(参数数量)和argv(参数数组) - 返回
int类型,表示程序退出状态(0 = 成功,非 0 = 失败)
临时参数模拟:
cpp
argc = 3;
static char* mock_argv[] = {
(char*)"nmap.exe",
(char*)"-O",
(char*)"scanme.nmap.org"
};
argv = mock_argv;
详细解释:
-
argc = 3:设置参数总数为 3argv[0]:程序名"nmap.exe"argv[1]:扫描选项"-O"(启用操作系统检测)argv[2]:扫描目标"scanme.nmap.org"(Nmap 官方测试靶机)
-
static char* mock_argv[]:- 定义一个静态参数数组
static关键字确保数组在程序整个生命周期内存在- 字符串存储在程序的只读数据段中
-
argv = mock_argv:- 将原命令行参数指针替换为模拟参数数组
- 这样可以在调试时使用固定的测试参数
实际应用示例:
bash
# 正常执行
nmap -O scanme.nmap.org
# argc = 3
# argv[0] = "nmap"
# argv[1] = "-O"
# argv[2] = "scanme.nmap.org"
# 使用模拟参数(调试时)
argc = 3;
argv = ["nmap.exe", "-O", "scanme.nmap.org"];
2. 变量声明
cpp
int i;
std::vector<Target *> Targets;
time_t now;
time_t timep;
char mytime[128];
struct addrset *exclude_group;
📖 代码详解
1. int i;
- 通用循环计数器
- 用于遍历数组、向量等数据结构
- 在整个函数中多次使用
2. std::vector<Target *> Targets;
- 存储目标主机对象的向量容器
Target *是指向目标主机对象的指针std::vector是 C++ 标准库的动态数组- 自动管理内存,支持动态增删元素
实际应用示例:
cpp
// 添加目标主机
Target *host1 = new Target("192.168.1.1");
Target *host2 = new Target("192.168.1.2");
Targets.push_back(host1);
Targets.push_back(host2);
// 访问目标主机
for (size_t i = 0; i < Targets.size(); i++) {
Target *host = Targets[i];
// 处理主机
}
// 清理内存
for (size_t i = 0; i < Targets.size(); i++) {
delete Targets[i];
}
Targets.clear();
3. time_t now;
- 用于存储当前时间的变量
time_t是 C 标准库定义的时间类型- 通常表示从 1970 年 1 月 1 日开始的秒数(Unix 时间戳)
4. time_t timep;
- 用于格式化时间的临时变量
- 在时间格式化过程中使用
5. char mytime[128];
- 存储格式化后的时间字符串
- 最大长度为 127 字节(预留 1 字节给字符串结束符
\0) - 例如:
"Wed Feb 5 10:30:45 2026"
6. struct addrset *exclude_group;
- 指向地址集合的指针
- 用于存储需要排除的目标地址
addrset是 Nmap 自定义的地址集合数据结构
实际应用示例:
cpp
// 初始化排除集合
exclude_group = addrset_new();
// 添加排除地址
addrset_add(exclude_group, "192.168.1.100");
addrset_add(exclude_group, "192.168.1.101");
// 检查地址是否在排除集合中
if (addrset_contains(exclude_group, "192.168.1.100")) {
printf("该地址在排除列表中\n");
}
// 释放资源
addrset_free(exclude_group);
📖 Lua 脚本相关变量
cpp
#ifndef NOLUA
/* Pre-Scan and Post-Scan script results datastructure */
ScriptResults *script_scan_results = NULL;
#endif
详细解释:
script_scan_results:仅在启用 Lua 支持时存在- 用于存储脚本扫描结果的数据结构
- 包括预扫描(Pre-Scan)和后扫描(Post-Scan)阶段的结果
- 初始化为
NULL,避免野指针
条件编译:
#ifndef NOLUA:如果未定义NOLUA宏- 表示启用了 Lua 脚本支持
- 如果定义了
NOLUA,则不包含这段代码
📖 其他变量
cpp
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;
int err;
详细解释:
1. unsigned int ideal_scan_group_sz = 0;
- 理想的扫描组大小
- 用于优化扫描性能
- 根据已扫描主机数量动态调整
- 平衡扫描效率和结果输出延迟
2. Target *currenths;
- 指向当前处理的目标主机对象的指针
- 在扫描循环中使用
- 每次处理一个目标主机时更新
3. char myname[FQDN_LEN + 1];
- 存储本地主机名
FQDN_LEN是完全限定域名(Fully Qualified Domain Name)的最大长度- 通常为 255 字节
+1用于字符串结束符
4. int sourceaddrwarning = 0;
- 源地址警告标志
- 防止重复警告
- 当无法确定源地址时发出警告
- 只警告一次
5. unsigned int targetno;
- 目标主机编号
- 用于循环遍历目标列表
- 在输出结果时使用
6. char hostname[FQDN_LEN + 1] = "";
- 存储目标主机名
- 初始化为空字符串
- 在输出结果时使用
7. struct sockaddr_storage ss;
- 通用套接字地址结构
- 支持 IPv4 和 IPv6
- 用于存储网络地址信息
8. size_t sslen;
- 套接字地址长度
- 表示
ss结构的实际大小 - 在网络编程中常用
9. int err;
- 错误代码变量
- 存储函数调用的返回值
- 用于错误检查和处理
3. 平台特定代码
Linux 平台特定代码
cpp
#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
📖 代码详解
1. 条件编译
cpp
#ifdef LINUX
- 只在 Linux 平台编译这段代码
- 其他平台(Windows、macOS 等)会跳过
2. 获取系统信息
cpp
struct utsname uts;
if (!uname(&uts)) {
详细解释:
-
struct utsname:存储系统信息的结构sysname:操作系统名称(如 "Linux")nodename:网络节点主机名release:操作系统发布版本(如 "5.4.0-42-generic")version:操作系统版本machine:硬件架构(如 "x86_64")
-
uname(&uts):- 获取系统信息
- 成功返回 0,失败返回 -1
!uname(&uts)表示成功获取系统信息
3. 检测 WSL
cpp
if (strstr(uts.release, "Microsoft") != NULL) {
详细解释:
-
strstr(uts.release, "Microsoft"):- 在版本字符串中查找 "Microsoft"
- WSL 的版本字符串包含 "Microsoft"
- 例如:
"5.10.16.3-microsoft-standard-WSL2"
-
!= NULL:- 如果找到 "Microsoft",返回非 NULL 指针
- 表示运行在 WSL 环境中
4. 输出警告
cpp
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);
详细解释:
error()函数:输出警告信息%s:字符串占位符NMAP_NAME:Nmap 程序名称(如 "Nmap")NMAP_URL:Nmap 官方网站 URL(如 "https://nmap.org")
实际输出示例:
Warning: Nmap may not work correctly on Windows Subsystem for Linux.
For best performance and accuracy, use the native Windows build from https://nmap.org/download.html#windows.
为什么需要这个警告?
- WSL 的网络栈与原生 Linux 不同
- 某些网络功能可能无法正常工作
- 原生 Windows 版本性能更好
- 建议使用原生 Windows 版本
4. 时间和本地化初始化
cpp
tzset();
now = time(NULL);
err = n_localtime(&now, &local_time);
if (err) {
fatal("n_localtime failed: %s", strerror(err));
}
📖 代码详解
1. 初始化时区信息
cpp
tzset();
详细解释:
tzset():初始化时区信息- 从环境变量读取时区设置
- 环境变量:
TZ:时区设置(如"Asia/Shanghai")- 如果未设置,使用系统默认时区
- 影响
localtime()、gmtime()等函数的行为
实际应用示例:
bash
# 设置时区
export TZ="Asia/Shanghai"
# 程序中使用
tzset(); // 初始化时区
time_t now = time(NULL);
struct tm *local = localtime(&now); // 使用设置的时区
2. 获取当前时间
cpp
now = time(NULL);
详细解释:
time(NULL):获取当前系统时间- 返回 Unix 时间戳(从 1970 年 1 月 1 日开始的秒数)
NULL表示不需要存储时间到其他变量- 返回值存储在
now变量中
实际应用示例:
cpp
time_t now = time(NULL);
printf("当前时间戳: %ld\n", now);
// 输出:当前时间戳: 1707127845
// 转换为可读格式
char *time_str = ctime(&now);
printf("当前时间: %s", time_str);
// 输出:当前时间: Mon Feb 5 10:30:45 2026
3. 转换为本地时间
cpp
err = n_localtime(&now, &local_time);
if (err) {
fatal("n_localtime failed: %s", strerror(err));
}
详细解释:
-
n_localtime(&now, &local_time):- Nmap 自定义的本地时间转换函数
- 将 Unix 时间戳转换为本地时间结构
&now:输入的时间戳&local_time:输出的本地时间结构
-
err:- 函数返回值
- 0 表示成功,非 0 表示失败
-
fatal():- 致命错误处理函数
- 打印错误信息并终止程序
strerror(err):将错误代码转换为错误消息
实际应用示例:
cpp
time_t now = time(NULL);
struct tm local_time;
int err = n_localtime(&now, &local_time);
if (err) {
printf("时间转换失败: %s\n", strerror(err));
exit(1);
}
printf("本地时间: %d-%02d-%02d %02d:%02d:%02d\n",
local_time.tm_year + 1900,
local_time.tm_mon + 1,
local_time.tm_mday,
local_time.tm_hour,
local_time.tm_min,
local_time.tm_sec);
// 输出:本地时间: 2026-02-05 10:30:45
5. 参数检查
cpp
if (argc < 2){
printusage();
exit(-1);
}
📖 代码详解
1. 检查参数数量
cpp
if (argc < 2)
详细解释:
argc:命令行参数数量argc < 2:参数数量少于 2argc = 1:只有程序名,没有其他参数argc = 0:理论上不可能(至少有程序名)
2. 打印使用说明
cpp
printusage();
详细解释:
printusage():打印 Nmap 使用说明- 显示所有可用的选项和参数
- 帮助用户了解如何正确使用 Nmap
实际输出示例:
Nmap 7.98 ( https://nmap.org )
Usage: nmap [Scan Type(s)] [Options] {target specification}
TARGET SPECIFICATION:
Can pass hostnames, IP addresses, networks, etc.
Ex: scanme.nmap.org, 192.168.0.0/24, 10.0.0-255.1-254
...
3. 退出程序
cpp
exit(-1);
详细解释:
exit(-1):退出程序- 返回码
-1表示错误 - 非 0 返回码表示程序异常退出
实际应用示例:
bash
# 没有参数
$ nmap
# 输出使用说明
# 退出,返回码 -1
# 有参数
$ nmap -sT 127.0.0.1
# 正常执行扫描
6. 向量预留空间
cpp
Targets.reserve(100);
📖 代码详解
详细解释:
Targets.reserve(100):- 为
Targets向量预留 100 个元素的空间 - 避免频繁的内存重分配
- 提高性能
- 为
为什么需要预留空间?
std::vector动态增长时会重新分配内存- 每次重新分配都需要:
- 分配更大的内存块
- 复制旧数据到新内存
- 释放旧内存
- 这个过程很耗时
- 预留空间可以避免这个过程
实际应用示例:
cpp
std::vector<int> vec;
// 不预留空间(性能较差)
for (int i = 0; i < 1000; i++) {
vec.push_back(i); // 可能多次重新分配内存
}
// 预留空间(性能较好)
vec.reserve(1000);
for (int i = 0; i < 1000; i++) {
vec.push_back(i); // 不会重新分配内存
}
性能对比:
- 不预留空间:O(n²) 时间复杂度(最坏情况)
- 预留空间:O(n) 时间复杂度
7. Windows 平台初始化
cpp
#ifdef WIN32
win_pre_init();
#endif
📖 代码详解
1. 条件编译
cpp
#ifdef WIN32
- 只在 Windows 平台编译这段代码
- 其他平台会跳过
2. Windows 预初始化
cpp
win_pre_init();
详细解释:
- Windows 平台特定的预处理初始化函数
- 在解析选项之前执行
- 可能包括:
- 初始化 Windows 套接字库(Winsock)
- 设置控制台编码
- 初始化 Windows 特定的资源
实际应用示例:
cpp
// win_pre_init() 可能的实现
void win_pre_init() {
// 初始化 Winsock
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
// 设置控制台编码为 UTF-8
SetConsoleOutputCP(CP_UTF8);
SetConsoleCP(CP_UTF8);
// 其他 Windows 特定初始化
}
8. 选项解析
cpp
parse_options(argc, argv);
📖 代码详解
详细解释:
parse_options(argc, argv):- 解析命令行参数
- 将解析结果存储在全局选项结构
o中 - 处理各种 Nmap 选项
支持的选项示例:
-sS:SYN 扫描-sT:TCP 连接扫描-O:操作系统检测-p:指定端口范围-v:详细模式-A:激进扫描模式
实际应用示例:
bash
# 命令行
nmap -sS -O -p 1-1000 192.168.1.1
# 解析后的选项
o.synscan = true; // 启用 SYN 扫描
o.osscan = true; // 启用 OS 检测
o.tcp_ports = [1..1000]; // 扫描端口 1-1000
o.targets = ["192.168.1.1"]; // 目标主机
解析过程:
- 遍历
argv数组 - 识别选项(以
-开头) - 解析选项参数
- 更新全局选项结构
o - 验证选项的有效性
9. 日志系统初始化
cpp
if (o.debugging)
nbase_set_log(fatal, error);
else
nbase_set_log(fatal, NULL);
📖 代码详解
1. 检查调试模式
cpp
if (o.debugging)
详细解释:
o.debugging:调试级别0:不启用调试> 0:启用调试,数字越大调试信息越详细
2. 设置日志函数
cpp
nbase_set_log(fatal, error);
详细解释:
nbase_set_log(fatal, error):- 设置日志输出函数
fatal:致命错误处理函数error:错误处理函数- 所有日志信息都会通过这些函数输出
3. 只输出致命错误
cpp
nbase_set_log(fatal, NULL);
详细解释:
nbase_set_log(fatal, NULL):- 只输出致命错误
NULL表示不输出普通错误- 减少日志输出量
实际应用示例:
cpp
// 调试模式(-d 或 -dd)
nmap -d -sT 127.0.0.1
// 输出所有调试信息
// 普通模式
nmap -sT 127.0.0.1
// 只输出致命错误
10. 终端初始化
cpp
tty_init(); // Put the keyboard in raw mode
📖 代码详解
详细解释:
tty_init():- 初始化终端
- 将键盘设置为原始模式(raw mode)
- 以便更好地处理用户输入
原始模式 vs 规范模式:
-
规范模式(Canonical Mode):
- 终端默认模式
- 按行缓冲输入
- 需要按回车键才发送输入
- 支持特殊字符(如 Ctrl+C、Ctrl+Z)
-
原始模式(Raw Mode):
- 按字符缓冲输入
- 立即发送输入
- 不处理特殊字符
- 适合实时交互
为什么需要原始模式?
- 实时检测按键(如按
q键退出) - 更好的用户交互体验
- 支持状态显示和进度更新
实际应用示例:
cpp
// 初始化终端
tty_init();
// 检测按键
if (keyWasPressed()) {
// 用户按下了某个键
if (getchar() == 'q') {
// 用户按下了 q 键,退出扫描
exit(0);
}
}
// 恢复终端
tty_restore();
11. 延迟选项应用
cpp
apply_delayed_options();
📖 代码详解
详细解释:
apply_delayed_options():- 应用延迟选项
- 这些选项需要在其他初始化完成后才能处理
- 例如:依赖于其他选项的选项
为什么需要延迟选项?
- 某些选项的值依赖于其他选项
- 需要在解析完所有选项后才能确定
- 例如:扫描超时时间依赖于扫描类型
实际应用示例:
cpp
// 延迟选项示例
void apply_delayed_options() {
// 根据扫描类型设置超时时间
if (o.synscan) {
o.maxRttTimeout = 1000; // SYN 扫描超时 1 秒
} else if (o.connectscan) {
o.maxRttTimeout = 2000; // 连接扫描超时 2 秒
}
// 根据目标数量调整并行度
if (o.targets.size() > 100) {
o.max_parallelism = 50; // 目标多时降低并行度
}
}
12. 路由信息打印
cpp
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();
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();
📖 代码详解
1. 遍历路由目标主机
cpp
for (unsigned int i = 0; i < route_dst_hosts.size(); i++) {
详细解释:
route_dst_hosts:路由目标主机列表- 用户通过
--route-dst选项指定 - 遍历每个目标主机
2. 变量声明
cpp
const char *dst;
struct sockaddr_storage ss;
struct route_nfo rnfo;
size_t sslen;
int rc;
详细解释:
dst:目标主机名字符串ss:目标主机的套接字地址rnfo:路由信息结构sslen:套接字地址长度rc:函数返回值(错误代码)
3. 解析目标主机名
cpp
dst = route_dst_hosts[i].c_str();
rc = resolve(dst, 0, &ss, &sslen, o.af());
if (rc != 0)
fatal("Can't resolve %s: %s.", dst, gai_strerror(rc));
详细解释:
-
route_dst_hosts[i].c_str():- 获取目标主机名字符串
- 例如:
"192.168.1.1"或"www.example.com"
-
resolve(dst, 0, &ss, &sslen, o.af()):- 解析主机名到 IP 地址
dst:主机名0:端口号(0 表示不指定)&ss:输出的套接字地址&sslen:输出的地址长度o.af():地址族(IPv4 或 IPv6)
-
gai_strerror(rc):- 将错误代码转换为错误消息
- 例如:
"Name or service not known"
4. 打印 IP 地址
cpp
printf("%s\n", inet_ntop_ez(&ss, sslen));
详细解释:
inet_ntop_ez(&ss, sslen):- 将二进制 IP 地址转换为字符串
- 例如:
"192.168.1.1"或"2001:db8::1"
5. 获取路由信息
cpp
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)));
}
详细解释:
-
route_dst(&ss, &rnfo, o.device, o.SourceSockAddr()):- 获取到目标主机的路由信息
&ss:目标地址&rnfo:输出的路由信息o.device:网络接口o.SourceSockAddr():源地址
-
路由信息包括:
devname:设备名称(如"eth0")devfullname:设备完整名称srcaddr:源地址direct_connect:是否直接连接nexthop:下一跳地址(如果不是直接连接)
6. 清空路由目标列表
cpp
route_dst_hosts.clear();
详细解释:
- 清空路由目标主机列表
- 释放内存
实际应用示例:
bash
# 命令行
nmap --route-dst 192.168.1.1 --route-dst 192.168.1.2
# 输出
192.168.1.1
eth0 Intel(R) PRO/1000 MT Network Connection srcaddr 192.168.1.100 direct
192.168.1.2
eth0 Intel(R) PRO/1000 MT Network Connection srcaddr 192.168.1.100 nexthop 192.168.1.1
13. 接口列表打印
cpp
if (delayed_options.iflist) {
print_iflist();
exit(0);
}
📖 代码详解
1. 检查接口列表选项
cpp
if (delayed_options.iflist)
详细解释:
delayed_options.iflist:是否打印接口列表- 用户通过
--iflist选项指定
2. 打印接口列表
cpp
print_iflist();
详细解释:
print_iflist():- 打印所有网络接口的信息
- 包括接口名称、IP 地址、MAC 地址等
3. 退出程序
cpp
exit(0);
详细解释:
- 打印完接口列表后退出程序
- 不执行扫描
实际应用示例:
bash
# 命令行
nmap --iflist
# 输出
Starting Nmap 7.98 ( https://nmap.org )
INTERFACES: NONE FOUND
14. FTP 跳转扫描初始化
cpp
/* If he wants to bounce off of an FTP site, that site better damn well be reachable! */
if (o.bouncescan) {
int rc = resolve(ftp.server_name, 0, &ss, &sslen, AF_INET);
if (rc != 0)
fatal("Failed to resolve FTP bounce proxy hostname/IP: %s",
ftp.server_name);
memcpy(&ftp.server, &((sockaddr_in *)&ss)->sin_addr, 4);
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);
📖 代码详解
1. 检查 FTP 跳转扫描选项
cpp
if (o.bouncescan)
详细解释:
o.bouncescan:是否启用 FTP 跳转扫描- 用户通过
-b选项指定
2. 解析 FTP 服务器名称
cpp
int rc = resolve(ftp.server_name, 0, &ss, &sslen, AF_INET);
if (rc != 0)
fatal("Failed to resolve FTP bounce proxy hostname/IP: %s",
ftp.server_name);
详细解释:
ftp.server_name:FTP 服务器名称resolve():解析主机名到 IP 地址AF_INET:IPv4 地址族- 如果解析失败,打印错误信息并退出
3. 复制 IP 地址
cpp
memcpy(&ftp.server, &((sockaddr_in *)&ss)->sin_addr, 4);
详细解释:
memcpy():复制内存&ftp.server:目标地址(FTP 服务器 IP)&((sockaddr_in *)&ss)->sin_addr:源地址(解析出的 IP)4:复制 4 字节(IPv4 地址长度)
4. 打印解析结果
cpp
if (o.verbose) {
log_write(LOG_STDOUT, "Resolved FTP bounce attack proxy to %s (%s).\n",
ftp.server_name, inet_ntoa(ftp.server));
}
详细解释:
- 如果启用详细模式,打印解析结果
inet_ntoa():将 IP 地址转换为字符串
5. 刷新输出流
cpp
fflush(stdout);
fflush(stderr);
详细解释:
fflush():刷新输出缓冲区- 确保所有输出都显示出来
实际应用示例:
bash
# 命令行
nmap -b ftp.example.com -p 21 192.168.1.1
# 输出(详细模式)
Resolved FTP bounce attack proxy to ftp.example.com (192.168.1.100).
15. 时间格式化
cpp
timep = time(NULL);
err = n_ctime(mytime, sizeof(mytime), &timep);
if (err) {
fatal("n_ctime failed: %s", strerror(err));
}
chomp(mytime);
📖 代码详解
1. 获取当前时间
cpp
timep = time(NULL);
详细解释:
- 获取当前系统时间
- 存储在
timep变量中
2. 格式化时间字符串
cpp
err = n_ctime(mytime, sizeof(mytime), &timep);
if (err) {
fatal("n_ctime failed: %s", strerror(err));
}
详细解释:
n_ctime():- Nmap 自定义的时间格式化函数
- 将时间戳转换为可读字符串
mytime:输出的时间字符串sizeof(mytime):缓冲区大小&timep:输入的时间戳
3. 去除换行符
cpp
chomp(mytime);
详细解释:
chomp():- 去除字符串末尾的换行符
- 类似于 Perl 的
chomp()函数
实际应用示例:
cpp
time_t timep = time(NULL);
char mytime[128];
n_ctime(mytime, sizeof(mytime), &timep);
// mytime = "Wed Feb 5 10:30:45 2026\n"
chomp(mytime);
// mytime = "Wed Feb 5 10:30:45 2026"
16. XML 输出初始化
cpp
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);
}
📖 代码详解
1. 检查是否恢复扫描
cpp
if (!o.resuming)
详细解释:
o.resuming:是否正在恢复扫描- 如果不是恢复扫描,创建完整的 XML 文档
- 如果是恢复扫描,只创建
nmaprun标签
2. 获取 XSL 样式表
cpp
char *xslfname = o.XSLStyleSheet();
详细解释:
o.XSLStyleSheet():- 获取 XSL 样式表文件名
- 用于美化 XML 输出
3. 开始 XML 文档
cpp
xml_start_document("nmaprun");
详细解释:
- 开始 XML 文档
- 根元素为
nmaprun
4. 添加 XSL 样式表
cpp
if (xslfname) {
xml_open_pi("xml-stylesheet");
xml_attribute("href", "%s", xslfname);
xml_attribute("type", "text/xsl");
xml_close_pi();
xml_newline();
}
详细解释:
- 如果有 XSL 样式表,添加样式表引用
xml-stylesheet:XML 处理指令href:样式表文件路径type:样式表类型
5. 添加注释
cpp
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();
详细解释:
- 添加扫描信息注释
- 包括程序名称、版本、启动时间、命令行参数
6. 添加 nmaprun 标签
cpp
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();
详细解释:
- 添加
nmaprun标签及其属性 scanner:扫描器名称args:命令行参数start:启动时间戳startstr:启动时间字符串version:Nmap 版本xmloutputversion:XML 输出版本
7. 输出扫描信息
cpp
output_xml_scaninfo_records(&ports);
详细解释:
- 输出扫描信息到 XML
- 包括扫描类型、端口信息等
8. 添加详细级别
cpp
xml_open_start_tag("verbose");
xml_attribute("level", "%d", o.verbose);
xml_close_empty_tag();
xml_newline();
详细解释:
- 添加详细级别信息
level:详细级别(0-9)
9. 添加调试级别
cpp
xml_open_start_tag("debugging");
xml_attribute("level", "%d", o.debugging);
xml_close_empty_tag();
xml_newline();
详细解释:
- 添加调试级别信息
level:调试级别(0-9)
10. 恢复扫描
cpp
else {
xml_start_tag("nmaprun", false);
}
详细解释:
- 如果是恢复扫描,只创建
nmaprun标签 - 不添加其他信息
实际应用示例:
xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="nmap.xsl" type="text/xsl"?>
<!-- Nmap 7.98 scan initiated Wed Feb 5 10:30:45 2026 as: nmap -sT 127.0.0.1 -->
<nmaprun scanner="nmap" args="nmap -sT 127.0.0.1" start="1707127845" startstr="Wed Feb 5 10:30:45 2026" version="7.98" xmloutputversion="1.04">
<scaninfo type="connect" protocol="tcp" services="1-1024"/>
<verbose level="0"/>
<debugging level="0"/>
17. 日志输出初始化
cpp
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");
📖 代码详解
详细解释:
log_write():- 写入日志
LOG_NORMAL | LOG_MACHINE:日志类型- 输出扫描初始化信息
实际应用示例:
# Nmap 7.98 scan initiated Wed Feb 5 10:30:45 2026 as: nmap -sT 127.0.0.1
18. 端口信息输出
cpp
/* Before we randomize the ports scanned, lets output them to machine parseable output */
if (o.verbose)
output_ports_to_machine_parseable_output(&ports);
📖 代码详解
详细解释:
- 如果启用详细模式,输出扫描的端口信息
- 机器可解析格式(grepable output)
实际应用示例:
# Ports scanned: TCP(1-1024;1-1024) UDP(0;) SCTP(0;) PROTOCOLS(0;)
19. 信号处理
cpp
#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
📖 代码详解
详细解释:
- 忽略
SIGPIPE信号 - 避免程序因为管道关闭而崩溃
SIGPIPE:当向已关闭的管道写入数据时触发
为什么需要忽略 SIGPIPE?
- 网络编程中常见问题
- 例如:客户端断开连接,服务器仍尝试写入
- 忽略信号可以优雅地处理错误
20. 并行度检查
cpp
if (o.max_parallelism && (i = max_sd()) > 0 && i < o.max_parallelism) {
error("WARNING: max_parallelism is %d, but your system says it might only give us %d sockets. Trying anyway", o.max_parallelism, i);
}
📖 代码详解
详细解释:
- 检查系统支持的最大文件描述符数量
- 如果用户设置的最大并行度超过系统限制,发出警告
实际应用示例:
WARNING: max_parallelism is 1000, but your system says it might only give us 512 sockets. Trying anyway
21. 调试信息输出
cpp
if (o.debugging > 1)
log_write(LOG_STDOUT, "The max # of sockets we are using is: %d\n", o.max_parallelism);
📖 代码详解
详细解释:
- 如果启用高级调试模式,输出最大并行度信息
22. 定时参数打印
cpp
// 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");
}
📖 代码详解
详细解释:
- 打印定时参数报告(仅在调试模式下)
- 包括主机组大小、超时设置、并行度等信息
实际应用示例:
--------------- Timing report ---------------
hostgroups: min 2, max 1024
rtt-timeouts: init 1000, min 100, max 10000
max-scan-delay: TCP 1000, UDP 1000, SCTP 1000
parallelism: min 1, max 1000
max-retries: 10, host-timeout: 0
min-rate: 0, max-rate: 0
---------------------------------------------
23. 端口列表初始化
cpp
/* 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);
📖 代码详解
详细解释:
- 初始化 PortList 类
- 为每个协议(IP、TCP、UDP、SCTP)设置端口映射
24. 端口随机化
cpp
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);
}
📖 代码详解
详细解释:
- 随机化扫描的端口顺序
- 对于 TCP 端口,还会将一些常见端口移到前面,以加快扫描速度
25. 排除目标初始化
cpp
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);
📖 代码详解
详细解释:
- 初始化排除目标集合
- 加载排除文件和排除字符串
- 如果启用高级调试模式,打印排除目标信息
26. Lua 脚本初始化
cpp
#ifndef NOLUA
if (o.scriptupdatedb) {
o.max_ips_to_scan = o.numhosts_scanned; // disable warnings?
}
if (o.servicescan)
o.scriptversion = true;
if (o.scriptversion || o.script || o.scriptupdatedb)
open_nse();
/* Run the script pre-scanning phase */
if (o.script) {
script_scan_results = get_script_scan_results_obj();
script_scan(Targets, SCRIPT_PRE_SCAN);
printscriptresults(script_scan_results, SCRIPT_PRE_SCAN);
for (ScriptResults::iterator it = script_scan_results->begin();
it != script_scan_results->end(); it++) {
delete (*it);
}
script_scan_results->clear();
}
#endif
📖 代码详解
详细解释:
- Lua 脚本引擎初始化
- 打开 Nmap 脚本引擎 (NSE)
- 运行预扫描脚本
- 打印脚本结果
- 清理脚本结果
27. 主机发现与扫描循环
cpp
if (o.ping_group_sz < o.minHostGroupSz())
o.ping_group_sz = o.minHostGroupSz();
HostGroupState hstate(o.ping_group_sz, o.randomize_hosts,
o.generate_random_ips, o.max_ips_to_scan, argc, (const char **) argv);
do {
ideal_scan_group_sz = determineScanGroupSize(o.numhosts_scanned, &ports);
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 */
if (currenths->flags & HOST_UP || (o.verbose && !o.openOnly())) {
xml_start_tag("host");
write_host_header(currenths);
printmacinfo(currenths);
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;
}
if (o.spoofsource) {
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 */
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++;
if (!o.max_ips_to_scan || o.max_ips_to_scan > o.numhosts_scanned + Targets.size())
continue;
else
break;
}
if (o.RawScan()) {
if (currenths->SourceSockAddr(NULL, NULL) != 0) {
if (o.SourceSockAddr(&ss, &sslen) == 0) {
currenths->setSourceSockAddr(&ss, sslen);
} else {
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. */
if (Targets.size() && target_needs_new_hostgroup(&Targets[0], Targets.size(), currenths)) {
returnhost(&hstate);
o.numhosts_up--;
break;
}
o.decoys[o.decoyturn] = currenths->source();
}
Targets.push_back(currenths);
}
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.RawScan())
o.decoys[o.decoyturn] = Targets[0]->source();
/* I now have the group for scanning in the Targets vector */
if (!o.noportscan) {
// 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);
}
}
if (o.servicescan) {
o.current_scantype = SERVICE_SCAN;
service_scan(Targets);
}
}
if (o.osscan) {
OSScan os_engine;
os_engine.os_scan(Targets);
}
if (o.traceroute)
traceroute(Targets);
#ifndef NOLUA
if (o.script || o.scriptversion) {
script_scan(Targets, SCRIPT_SCAN);
}
#endif
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_attribute("timedout", "true");
xml_close_start_tag();
write_host_header(currenths);
printtimes(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);
📖 代码详解
这是 Nmap 扫描引擎的核心控制部分,协调各个扫描阶段。主要步骤包括:
- 主机分组大小调整:确保 ping 扫描分组大小不小于最小主机分组大小
- 主机状态管理主机状态管理初始化:初始化主机组状态管理器
- 扫描主循环控制:动态计算理想的并行扫描目标数量
- 目标获取与筛选:循环获取下一个目标主机,执行主机发现
- 特殊扫描模式处理:处理无端口扫描或列表扫描模式
- 主机状态检查:检查主机是否可达
- 原始扫描模式准备:为原始扫描模式准备目标
- 扫描执行阶段:执行端口扫描、服务检测、OS 检测等
- 结果输出与清理:输出扫描结果,清理资源
28. 后扫描脚本执行
cpp
#ifndef NOLUA
if (o.script) {
script_scan(Targets, SCRIPT_POST_SCAN);
printscriptresults(script_scan_results, SCRIPT_POST_SCAN);
for (ScriptResults::iterator it = script_scan_results->begin();
it != script_scan_results->end(); it++) {
delete (*it);
}
script_scan_results->clear();
}
#endif
📖 代码详解
详细解释:
- 执行后扫描脚本
- 打印脚本结果
- 清理脚本结果
29. 资源清理
cpp
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;
📖 代码详解
详细解释:
- 释放资源
- 关闭输入文件
- 打印数据文件路径
- 打印最终输出
- 释放扫描列表内存
- 关闭缓存的以太网连接
- 释放内存(如果启用)
- 返回 0 表示成功
30. 总结
nmap_main() 函数是 Nmap 安全扫描器的核心函数,负责执行所有的扫描逻辑。它接受命令行参数,初始化扫描环境,执行主机发现、端口扫描、服务识别和操作系统检测等功能,并生成扫描报告。
核心功能
- ✅ 参数解析:解析命令行参数,设置扫描选项
- ✅ 初始化:初始化扫描环境,包括时间、日志、终端等
- ✅ 主机发现:执行 ping 扫描,发现存活主机
- ✅ 端口扫描:执行各种类型的端口扫描
- ✅ 服务检测:识别开放端口的服务和版本
- ✅ OS 检测:确定目标主机的操作系统
- ✅ 脚本扫描:执行 NSE 脚本
- ✅ 结果输出:输出扫描结果
设计特点
- ✅ 模块化设计:各个扫描阶段独立,易于维护
- ✅ 高性能:支持并行扫描,优化扫描效率
- ✅ 可扩展性:支持 Lua 脚本,允许用户扩展功能
- ✅ 平台兼容性:支持多种操作系统
学习要点
通过逐行分析 nmap_main() 函数,我们可以学到:
- 程序架构:如何设计复杂的扫描引擎
- 资源管理:如何高效管理内存和网络资源
- 错误处理:如何设计健壮的错误处理机制
- 性能优化:如何优化扫描性能
- 可扩展性:如何设计可扩展的程序结构