Nmap是一款非常强大的开源扫描工具。
这里我们以阅读nmap6.0的代码作为主线来分析Nmap源码的实现框架。
源码下载地址:http://nmap.org/dist/nmap-6.01.tar.bz2
SVN检出:
svn co https://svn.nmap.org/nmap
1 文件组织
我们先从总体上了解Nmap的文件组织方式及分析源码需要关注的重点。
1.1 目录结构
解压Nmap的源码包后,可以看到根目录下有许多的子目录和文件。
文件分析
放在Nmap/根目录下包括几种类型文件:
1) Nmap核心功能的源码(如nmap.cc/ scan_engine.cc/ service_scan.cc/osscan2.cc/ nse_main.lua等)。
2) Nmap的核心数据库文件(nmap-os-db/ nmap-service-probes/ nmap-rpc/nmap-protocols等)。
3) 编译链接相关的Makefile或CONFIG文件。
4) 其他杂项文件(如安装提示:README-WIN32)
目录分析
使用Windows的tree命令列举出Nmap的目录结构.
为了避免目录树过长,这里只显示了3个级别的目录,目录的主要作用在名字后面简略说明。
可以看到Nmap工具也使用到很多其他开源项目的成果,例如libdnet/ liblinear/ liblua/libpcap/ libpcre等;另外Nmap自身也实现很多有用的程序库,如nsock/ libnetutil/;Nmap项目包括附带几个小工具:1)ncat,这是根据TCPIP协议栈瑞士军刀netcat(该工具已停止维护更新)开发扩展出来的工具。2)nping,类似于Hping的工具,用于进行主机探测和发包收包。3)ndiff,用于比较两次nmap扫描结果之间差异。
- Nmap/
- ├─docs(Nmap相关文档,包括License、usage说明及XMLschema文件等)
- │ ├─licenses
- │ └─man-xlate
- ├─libdnet-stripped(libdnet:简单的网络接口开源库)
- │ ├─config
- │ ├─include
- │ └─src
- ├─liblinear(LIBLINEAR:负责大型线性分类的开源库)
- │ └─blas
- ├─liblua(Lua脚本语言源码库)
- ├─libnetutil(Nmap实现的基本的网络实用函数)
- ├─libpcap(开源的抓包代码库libpcap)
- │ ├─bpf
- │ ├─ChmodBPF
- │ ├─lbl
- │ ├─missing
- │ ├─msdos
- │ ├─NMAP_MODIFICATIONS
- │ ├─packaging
- │ ├─pcap
- │ ├─SUNOS4
- │ ├─tests
- │ └─Win32
- ├─libpcre(Perl兼容的正则表达式开源库libpcre)
- ├─macosx(该目录负责支持苹果的操作系统MACOS X)
- │ └─nmap.pmdoc
- ├─mswin32(该目录负责支持Windows操作系统)
- │ ├─lib
- │ ├─license-format
- │ ├─NET
- │ ├─NETINET
- │ ├─nsis
- │ ├─OpenSSL
- │ ├─pcap-include
- │ ├─RPC
- │ └─winpcap
- ├─nbase(Nmap封装的基础使用程序库,包括string/path/random等)
- ├─ncat(Ncat是Nmap项目组实现的新版的netcat:强大的网络工具)
- │ ├─certs
- │ ├─docs
- │ └─test
- ├─ndiff(Ndiff是用于比较Nmap扫描结果的实用命令)
- │ ├─docs
- │ └─test-scans
- ├─nmap-update(负责Nmap更新相关操作)
- ├─nping(Nping是Nmap项目组实现的新版的Hping:网络探测与构建packet)
- │ └─docs
- ├─nselib(Nmap使用Lua语言编写的常用的脚本库)
- │ └─data
- ├─nsock(Nmap实现的并行的SocketEvent处理库)
- │ ├─include
- │ └─src
- ├─scripts(Nmap提供常用的扫描检查的lua脚本)
- ├─todo(介绍Nmap项目将来开发的具体任务)
- └─zenmap(Nmap的官方的图形界面程序,由python语言编写)
- ├─install_scripts
- ├─radialnet
- ├─share
- ├─test
- ├─zenmapCore
- └─zenmapGUI
1.2 文件类型
下面从源码类型的角度上浏览一下Nmap项目的特点:
Nmap6.0工程内总共包括1300多个文件。
1) C和C++文件有600多个,主要实现Nmap最核心的功能:主机发现、端口扫描、服务侦测、OS侦测及搭建脚本引擎框架;也包括其他开源项目如libpcap的源码。
2) Python文件有100多个,主要实现Zenmap图形界面,Zenmap会调用到Nmap基本命令,也实现一些新的功能:例如确定网络拓扑结构、Profile的管理(常用的命令保存为Profile)等。
3) Lua与NSE文件400多个,负责构建Nmap脚本引擎及提供常用的扫描脚本。其中NSE格式为Nmap定制的Lua文件,方便用户自行编写脚本进行功能扩展。
4) XML文件数十个,用于辅助描述Nmap的内容或Zenmap的测试等工作。
5) 其他文件,其他辅助工具操作的文件。
2 源码分析
2.1 执行流程框架
Nmap的执行流程简单清晰,主要的工作在nmap.cc文件中完成,而main.cc负责简单地包装nmap_main()函数。
nmap_main()函数是执行流程的核心。
- 准备阶段:在其中会执行参数解析、资源分配、基本扫描信息的输出、端口与地址列表的初始化、NSE环境准备及pre-scripts的运行等基本的准备操作。
- 工作阶段:然后进入主循环,每次循环对一组目标地址进行主机发现、端口扫描、服务与版本侦测、OS侦测及脚本扫描等操作,直到所有的目标地址都被扫描完毕才推出主循环。
- 善后阶段:在完成所有的扫描操作后,调用post-script完成相应处理,然后打印出扫描的最终结果,并释放掉分配的资源。
2.2 nmap_main()函数
以下代码是对nmap_main()函数基本的分析。
其中以///开头是新添加的注释。而以类似于///<Start------创建主机组状态,进入主循环--------Start>的形式出现的注释用于标注一个比较大的功能代码段。
Nmap的所有的功能都在此nmap_main()设有入口,以此为基础可以更深入地分析Nmap的其他模块。
- int nmap_main(int argc, char *argv[]) {
- int i;
- vector<Target *> Targets;
- time_t now;
- struct hostent *target = NULL;
- time_t timep;
- char mytime[128];
- addrset exclude_group;
- #ifndef NOLUA
- /* Only NSE scripts can add targets */
- NewTargets *new_targets = NULL;///NewTargets为Singleton模式,产生单个实例
- /* Pre-Scan and Post-Scan script results datastructure */
- ScriptResults *script_scan_results = NULL;
- #endif
- char **host_exp_group;
- int num_host_exp_groups;
- HostGroupState *hstate = NULL;
- unsigned int ideal_scan_group_sz = 0;
- Target *currenths;
- char *host_spec = NULL;
- char myname[MAXHOSTNAMELEN + 1];
- int sourceaddrwarning = 0; /* Have we warned them yet about unguessable
- source addresses? */
- unsigned int targetno;
- char hostname[MAXHOSTNAMELEN + 1] = "";
- struct sockaddr_storage ss;
- size_t sslen;
- char **fakeargv = NULL;
- now = time(NULL);
- local_time = localtime(&now);
- ///设置错误log输出函数
- if(o.debugging)
- nbase_set_log(fatal,error);
- else
- nbase_set_log(fatal,NULL);
- if (argc < 2 ) printusage(-1);
- /* argv faking silliness */
- fakeargv = (char **) safe_malloc(sizeof(char *) * (argc + 1));
- for(i=0; i < argc; i++) {
- fakeargv[i] = strdup(argv[i]);
- }
- fakeargv[argc] = NULL;
- Targets.reserve(100);
- #ifdef WIN32
- win_pre_init();
- #endif
- ///调用parse_options进行命令参数的解析
- parse_options(argc, fakeargv);
- ///在Linux下设置终端为只读非阻塞方式,在Windows平台为空函数。
- tty_init(); // Put the keyboard in raw mode
- ///将解析命令时需要延迟执行的操作在此处处理
- apply_delayed_options();
- #ifdef WIN32
- ///调用WSAStartup启动Winsock DLL,后续网络解析等需要用到。
- win_init();
- #endif
- ///如果用户使用了参数--iflist,那么会在此处打印网卡和路由表信息,然后退出。
- ///该选项对于显示指定发送网卡非常有帮助,可以提供基本的网络设备信息。
- if (delayed_options.iflist) {
- print_iflist();
- exit(0);
- }
- ///quashargv部分用于修改命令行参数,将程序名字更改为FAKE_ARGV(默认为“pine”),
- ///并将剩余的各个参数都清空。
- ///在命令中加入-q可实现quashargv功能。这最初是为了逃避ps等程序名称显示,便于隐蔽Nmap。