【Nmap 源码学习】Nmap 源码深度解析:nmap_main() 函数逐行详解

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:设置参数总数为 3

    • argv[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:参数数量少于 2
    • argc = 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 动态增长时会重新分配内存
  • 每次重新分配都需要:
    1. 分配更大的内存块
    2. 复制旧数据到新内存
    3. 释放旧内存
  • 这个过程很耗时
  • 预留空间可以避免这个过程

实际应用示例:

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"];  // 目标主机

解析过程:

  1. 遍历 argv 数组
  2. 识别选项(以 - 开头)
  3. 解析选项参数
  4. 更新全局选项结构 o
  5. 验证选项的有效性

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, &currenths->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 扫描引擎的核心控制部分,协调各个扫描阶段。主要步骤包括:

  1. 主机分组大小调整:确保 ping 扫描分组大小不小于最小主机分组大小
  2. 主机状态管理主机状态管理初始化:初始化主机组状态管理器
  3. 扫描主循环控制:动态计算理想的并行扫描目标数量
  4. 目标获取与筛选:循环获取下一个目标主机,执行主机发现
  5. 特殊扫描模式处理:处理无端口扫描或列表扫描模式
  6. 主机状态检查:检查主机是否可达
  7. 原始扫描模式准备:为原始扫描模式准备目标
  8. 扫描执行阶段:执行端口扫描、服务检测、OS 检测等
  9. 结果输出与清理:输出扫描结果,清理资源

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 安全扫描器的核心函数,负责执行所有的扫描逻辑。它接受命令行参数,初始化扫描环境,执行主机发现、端口扫描、服务识别和操作系统检测等功能,并生成扫描报告。

核心功能

  1. 参数解析:解析命令行参数,设置扫描选项
  2. 初始化:初始化扫描环境,包括时间、日志、终端等
  3. 主机发现:执行 ping 扫描,发现存活主机
  4. 端口扫描:执行各种类型的端口扫描
  5. 服务检测:识别开放端口的服务和版本
  6. OS 检测:确定目标主机的操作系统
  7. 脚本扫描:执行 NSE 脚本
  8. 结果输出:输出扫描结果

设计特点

  1. 模块化设计:各个扫描阶段独立,易于维护
  2. 高性能:支持并行扫描,优化扫描效率
  3. 可扩展性:支持 Lua 脚本,允许用户扩展功能
  4. 平台兼容性:支持多种操作系统

学习要点

通过逐行分析 nmap_main() 函数,我们可以学到:

  1. 程序架构:如何设计复杂的扫描引擎
  2. 资源管理:如何高效管理内存和网络资源
  3. 错误处理:如何设计健壮的错误处理机制
  4. 性能优化:如何优化扫描性能
  5. 可扩展性:如何设计可扩展的程序结构

📖 参考资料


相关推荐
Rabbit_QL1 小时前
【NLP学习】IMDB 情感分类实战:Word2Vec + 逻辑回归完整解析
学习·自然语言处理·分类
执行部之龙1 小时前
TCP八股完结篇
网络·笔记·网络协议·tcp/ip
爱吃rabbit的mq1 小时前
第13章:神经网络基础 - 感知机到多层网络
网络·人工智能·神经网络
EnglishJun1 小时前
数据结构的学习(五)---树和二叉树
数据结构·学习·算法
新新学长搞科研2 小时前
【CCF主办 | 高认可度会议】第六届人工智能、大数据与算法国际学术会议(CAIBDA 2026)
大数据·开发语言·网络·人工智能·算法·r语言·中国计算机学会
近津薪荼2 小时前
优选算法——前缀和(1):一维前缀和
c++·学习·算法
珠海西格电力科技3 小时前
微电网控制策略基础:集中式、分布式与混合式控制逻辑
网络·人工智能·分布式·物联网·智慧城市·能源
进阶小白猿10 小时前
Java技术八股学习Day36
学习
syseptember10 小时前
Linux网络基础
linux·网络·arm开发