基于C++的网络扫描器课程设计:VC6.0 MFC源码解析与避坑指南
简介面向C课程设计与网络安全实践这份资料提供基于MFC框架的网络扫描器完整设计与实现方案覆盖主机发现、端口探测、NetBIOS/SNMP扫描、弱口令检测、网络嗅探、DOS攻击模拟及注入检测等主流模块适合高校学生或安全入门者对照学习扫描器的模块划分与代码组织。压缩包共96个文件、约5.1MB以h/cpp源码、png/jpg界面截图、pdf设计论文、txt/html说明文档等为主目录按功能模块划分可快速定位到主机扫描、端口扫描、嗅探器、报告生成等实现并附有大量登录、扫描过程的运行截图与测试结果便于对照验证效果。项目基于Windows XP及以上环境与VC6.0开发从对话框界面到底层网络调用均有完整代码并附带课程设计论文可帮助梳理各模块测试流程理解扫描原理与MFC事件驱动机制。已有294人学习下载适合作为课程设计参考或安全工具二次开发基础。1. 基于 C 网络扫描器这份课程设计源码拿到手先看什么做课程设计最抓狂的不是写不出代码而是拿到一份源码包不知道里面到底有什么、能不能一次跑起来。这份基于 C 网络扫描器的设计与实现解压之后是典型的 VC6.0 时代 MFC 对话框工程源码、论文、报告和整套模块截图都齐了。功能也比想象中满除了常规的主机扫描和端口扫描还带了 NetBIOS、SNMP、弱口令、SQL 注入检测、DoS 演示和嗅探抓包几乎把一个终端安全工具该有的模块都塞进去了。适合课程设计要交代码和论文的同学研究它的模块划分也适合刚开始接触安全工具的朋友看老一代扫描器是怎么组织功能的。拿到手建议先编译 Release 版跑通一遍再把九个模块逐个过一遍心里就有底了。2. 工程骨架与编译选型VC6.0、MFC 对话框与九大扫描模块2.1 源码包结构与模块分工先看文件结构。这个包的文件命名很规整几乎每个功能对应一个 cpp 加一个头文件主控逻辑在 Scanner 和 ScannerDlg 两个文件里。我把主要文件列出来方便你对照源码包快速定位文件模块角色Scanner.cpp / ScannerDlg.cpp程序入口与主对话框负责所有模块的调度HostScan.cpp / HostScan.h主机存活扫描通常走 ICMPPortScan.cpp / PortScan.h端口开放扫描NetBIOSScan.cpp / NetBIOSScan.hNetBIOS 137/139 信息获取SNMPScan.cpp / SNMPScan.hSNMP 161 端口探测与 community 猜测WeakKeyScan.cpp / WeakKeyScan.h弱口令 / 弱密钥密码尝试SQLScan.cpp / SQLScan.hWeb 注入特征检测DosScan.cpp / DosScan.hDoS 压力演示模块SnifferScan.cpp / SnifferScan.h抓包嗅探模块CmdDlg.cpp / SSSelectIP.cpp命令窗口与 IP 选择辅助对话框rebort.html扫描汇总报告输出源码包里的拼写如此这个结构非常典型一个基于对话框的 MFC 程序把每个扫描动作隔离成类界面层通过按钮触发网络操作放在线程里跑结束后往界面控件里填结果。对于课程设计评审来说模块划分本身就是得分点。答辩时你不用把九个模块都讲透能说清楚三个模块的实现逻辑就足够应付提问了。从入口往下看调用链也清晰Scanner.cpp 里初始化 CWinAppScannerDlg.cpp 里创建主对话框各个扫描按钮的点击事件里分别 new 对应的 Scan 类实例传入目标 IP 和参数后启动线程。这个“按按钮起线程”的写法是 MFC 网络工具类课设的标准模板后面你换成自己的扫描器也能复用同一套框架。2.2 编译环境选型与工程迁移源码包里同时出现了 .dsw/.dsp 和 .sln/.vcproj 两套工程文件。前者是 Visual C 6.0 的标准格式后者是 Visual Studio 2005 及以后版本的标准格式说明原始开发环境是 VC6.0后来有人用新版 IDE 迁移过。摘要里写的开发工具是“Microsoft Visual C 6.0 Notepad”所以我的建议是如果你手头只有新版 Visual Studio别硬刚 .dsp直接打开 .sln 让 IDE 走一遍转换向导基本能过。迁移时有几个地方要提前处理否则会卡在编译阶段。第一VC6 默认用 ANSI/MBCS 字符集新版 VS 默认 Unicode。对话框资源里如果有中文字符串或者网络缓冲区里有把 char* 直接转 CString 的逻辑会出现乱码甚至编译报错建议在项目属性里把字符集改回“使用多字节字符集”。第二代码里用了 Iphlpapi.h新版 SDK 还带这个头文件但链接库不一定自动带上需要确认项目属性里的附加依赖项有 iphlpapi.lib 和 ws2_32.lib。第三如果换到高版本 VS 后提示找不到 afxwin.h多半是只装了 C 桌面开发组件没装 MFC 组件去 VS Installer 里把“适用于最新 v143 生成工具的 C MFC”勾上就行。我一般会先用 Release 配置编译一遍Debug 配置留着断点调试。课程设计演示用 Release 更稳别拿 Debug 版去做长时间扫描断言和调试信息会拖慢速度偶尔还会在 MFC 内部触发断点场面不好收拾。2.3 Iphlpapi.h 依赖为什么老工程都会碰它包里单独放了一个 Iphlpapi.h 和 IPTYPES.H这不是随便复制进来的。IphlpapiIP Helper API是 Windows 自带的网络辅助库用来取本机网卡信息、ARP 表、路由表等。这个扫描器在做主机扫描前需要枚举本机 IP 段代码里大概率调了 GetAdaptersInfo 或 GetIpAddrTable返回的数据结构定义就在 iptypes.h 里。VC6 时代这两个头文件不是默认就有的要装 Platform SDK 才会有所以作者直接把头文件塞进了工程目录避免评审机器上编译不过。这里有个小坑头文件拷进工程目录后源文件里写的还是#include Iphlpapi.h。尖括号会让编译器优先找系统 include 目录找不到才会看工程目录所以要么把 include 路径指到工程目录要么把尖括号改成双引号。链接库也一样VC6 里记得在 工程 - 设置 - Link - 对象/库模块 里手工补 ws2_32.lib 和 iphlpapi.lib否则会报一堆 unresolved external symbol。这个知识点几乎每个用 VC6 写网络程序的课设都会考答辩时老师经常问答上来就是印象分。注意改完头文件引用方式后记得把工程缓存文件.ncb、.opt、.suo删掉再重新编译VC6 经常因为缓存不刷新导致改了头文件也不生效。3. 主机发现与端口扫描从 Ping 到 TCP Connect 的落地实现3.1 ICMP 主机发现IcmpSendEcho 与超时参数主机扫描模块的第一步通常是发现存活主机。老课设里最简单可靠的做法不是自己组 ICMP 包而是调用系统 API IcmpSendEcho。Windows 封装了完整的 ICMP 发送与回显处理不用关心校验和计算传一个 IP 和超时时间就行。HostScan.cpp 里应该就是类似的逻辑而且是整个模块最核心的一段代码思路如下#include winsock2.h #include iphlpapi.h #include icmpapi.h #pragma comment(lib, iphlpapi.lib) #pragma comment(lib, ws2_32.lib) BOOL CheckHostAlive(const char* ip, DWORD timeoutMs, int retries) { HANDLE hIcmp IcmpCreateFile(); if (hIcmp INVALID_HANDLE_VALUE) return FALSE; DWORD destAddr inet_addr(ip); char sendData[32] {0}; BYTE replyBuffer[sizeof(ICMP_ECHO_REPLY) 32]; BOOL alive FALSE; for (int i 0; i retries; i) { DWORD ret IcmpSendEcho(hIcmp, destAddr, sendData, sizeof(sendData), NULL, replyBuffer, sizeof(replyBuffer), timeoutMs); if (ret 0) { PICMP_ECHO_REPLY pReply (PICMP_ECHO_REPLY)replyBuffer; if (pReply-Status IP_SUCCESS) { alive TRUE; break; } } Sleep(200); // 重试间隔避免连续发包触发网关限速 } IcmpCloseHandle(hIcmp); return alive; }几个参数是答辩时老师喜欢追问的timeoutMs 是单次回显等待时间内网环境设 800~1000ms 比较合适设太短会漏掉延迟稍高的主机设太长扫一个 C 段 254 个 IP 会慢得没法演示。retries 一般取 2~3 次因为 ICMP 回显是尽力而为的丢包一次不代表主机不在线。Sleep(200) 是重试间隔防止连续发包把自己网卡跑满。IcmpCreateFile 创建的句柄不需要真实文件它只是 ICMP 驱动的句柄用完记得关。要注意IcmpSendEcho 在防火墙丢弃 ICMP 的网段里会全军覆没所以“主机扫描全灭”先别怀疑代码先看目标网段是否允许 ICMP 出站和入站。课程设计演示最好选一个允许 ICMP 的实验网段否则截图出来一排“离线”很难解释。3.2 TCP 端口扫描超时控制、线程调度与结果回调端口扫描模块是这份课设里最值得展开的部分。常见实现是 TCP Connect 扫描对目标 IP 的每个端口调用 connect连接成功就认为端口开放。Windows 下阻塞套接字的 connect 会受系统 TCP 超时影响可能等 21 秒才返回所以正规做法是先设置非阻塞模式再配合 select 做超时控制。#include winsock2.h #pragma comment(lib, ws2_32.lib) // 检查单个端口是否开放timeoutMs 为毫秒级超时 BOOL IsPortOpen(const char* ip, int port, int timeoutMs) { SOCKET sock socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sock INVALID_SOCKET) return FALSE; // 非阻塞模式下connect 立即返回用 select 判断最终结果 u_long mode 1; ioctlsocket(sock, FIONBIO, mode); sockaddr_in addr; addr.sin_family AF_INET; addr.sin_port htons(port); addr.sin_addr.s_addr inet_addr(ip); int ret connect(sock, (sockaddr*)addr, sizeof(addr)); if (ret SOCKET_ERROR WSAGetLastError() ! WSAEWOULDBLOCK) { closesocket(sock); return FALSE; } fd_set writeSet; FD_ZERO(writeSet); FD_SET(sock, writeSet); timeval tv; tv.tv_sec timeoutMs / 1000; tv.tv_usec (timeoutMs % 1000) * 1000; ret select(0, NULL, writeSet, NULL, tv); BOOL open (ret 0); // 可写说明 connect 已建立 closesocket(sock); return open; }逻辑说明一下socket 设为非阻塞后connect 会先返回 WSAEWOULDBLOCK表示连接在后台继续建立随后 select 监控这个 socket 是否可写如果在 timeoutMs 内可写说明 TCP 三次握手已经完成端口就是开放的超时未响应才视为关闭。这是所有端口扫描器的基础去掉超时控制直接同步 connect 是新手最容易犯的错扫一个主机能把界面卡到“未响应”。线程调度方面课设里通常按端口段开多个工作线程避免一个长循环卡住 UI。我的建议是把端口按 100 个一组分片每组一个线程线程内部串行探测。扫描结果不要在线程里直接操作控件而是通过 PostMessage 发给主窗口否则多个线程同时写 CListCtrl 会崩溃。这个逻辑对应 PortScan.cpp 里界面下方的结果列表刷新理解成“工作线程只负责探测UI 线程只负责显示”就够了。超时参数上局域网设 1000ms 足够跨网段或扫公网时提高到 2000ms 更稳线程数不要贪多8 到 16 个就能跑满一个 C 段的端口开太多反而把网卡打满。4. 五类特殊扫描模块NetBIOS、SNMP、弱口令、注入检测与 DoS4.1 NetBIOS 与 SNMP两个基于 UDP 的协议侦查模块NetBIOS 扫描走的是 UDP 137 端口作用是获取目标主机的机器名、所属域和工作组信息。老内网里开着 137 端口的机器很多这个模块在局域网场景下辨识度很高。实现思路是向 137 端口发一个 NetBIOS Name Service 查询请求目标会回一个包含名字表的数据报解析后就能看到注册名列表。NetBIOSScan.cpp 的核心工作量就在这段二进制响应解析上每个名字记录以 0x20 分隔前 15 字节是补空格后的名字第 16 字节是名字类型0x00 代表工作站0x20 代表文件服务。把这一段按偏移拆开再用一个格式化函数转成可读字符串答案就出来了。答辩时能讲清楚名字表和类型字段的含义这个模块就过关了。SNMP 扫描走 UDP 161 端口目标是识别网络设备类型和读取系统描述。SNMP 查询本质是发一个 ASN.1 BER 编码的 GET 请求community 字符串猜对了才能拿到数据。课设里会带一组常见 community比如 public、private、cisco逐个尝试。下面是 UDP 探测的骨架对应 SNMPScan.cpp 里通过 161 端口发包的部分// 向目标 161 端口发送 SNMP community 探测 BOOL ProbeSNMP(const char* ip, const char* community, int timeoutMs) { SOCKET sock socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sock INVALID_SOCKET) return FALSE; sockaddr_in addr; addr.sin_family AF_INET; addr.sin_port htons(161); addr.sin_addr.s_addr inet_addr(ip); // 构造简化 SNMPv1 GET 请求目标是 system.sysDescr.0 BYTE pdu[64] {0}; int len BuildSnmpGetPacket(pdu, community); sendto(sock, (char*)pdu, len, 0, (sockaddr*)addr, sizeof(addr)); // UDP 无连接状态必须靠 select 控制收包超时 BYTE recvBuf[512] {0}; int recvLen RecvWithTimeout(sock, (char*)recvBuf, sizeof(recvBuf), timeoutMs); closesocket(sock); // 返回非零说明收到响应community 很可能猜中 return recvLen 0; }这个骨架里 BuildSnmpGetPacket 是核心它要按 BER 编码把 SNMP 版本号、community 字符串、PDU 类型、OID 拼成字节流。编码规则不复杂每个字段前面先写类型标签再写长度最后写内容字符串字段按“类型 长度 内容”三段式排布。网上有现成的 SNMP 报文构造代码课程设计直接把 OID 换成 system.sysDesk.0 那一串字节就行。UDP 探测比 TCP 麻烦的地方在于没有连接状态sendto 之后必须靠 select 控制收包超时RecvWithTimeout 就是干这个的。超时建议 1500mscommunity 字典每个词都试一遍如果实验网段有设备开了只读 community很快就能扫出来。这个模块的亮点在 BER 编码和无连接特性短板是现在很多设备默认关闭 SNMP 响应演示前先在实验网段找一台打印机或路由器确认能回包。4.2 弱口令与 SQL 注入规则驱动型检测WeakKeyScan 模块是弱口令尝试课程设计里通常针对开放远程终端、共享服务或 Web 登录的资产用内置小字典试账号口令。它的本质是一个循环从字典里取一组账号密码拼成认证请求看返回结果。Windows 下常见的演示对象有 139/445 的 SMB 登录、3389 的 RDP 登录还有 Web 后台的 POST 登录。登录成功的判定逻辑通常看响应码或返回页面是否出现特定关键词对应源码包里的 login.jpg、login_succeed.jpg、login_failed.jpg 三张截图。演示时把正确的账号密码放字典最后一行前面故意放几个错误组合能看到失败和成功两种截图报告里对比写更有说服力。SQLScan 模块是注入检测它更像一个规则匹配器向目标 URL 拼接注入特征串比较响应内容是否出现数据库错误特征。常见的特征组合是 or 11--这类闭合单引号后跟永真条件的 payload检测端则看返回页面里是否有 SQL 语法错误提示。下面给一个简化但能讲清思路的片段const char* payloads[] { %27%20or%201%3D1--, // or 11-- %27%20and%201%3D1--, // and 11-- %27%22%20or%20%221%22%3D%221%22, // or 11 }; BOOL DetectSQLInjection(const char* url, const char* param) { for (int i 0; i 3; i) { string fullUrl string(url) ? param payloads[i]; string resp HttpGet(fullUrl); // 命中数据库错误关键字即认为存在注入特征 if (resp.find(SQL syntax) ! string::npos || resp.find(mysql_fetch) ! string::npos || resp.find(syntax error) ! string::npos) { return TRUE; } } return FALSE; }这段代码的 payload 是经典老思路现代站点的 WAF 基本都会拦但作为课程设计讲“注入检测原理”完全够用。核心在“响应差异”四个字未加 payload 的页面和加了 payload 的页面如果有结构差异说明参数很可能被拼进了 SQL。课设里能讲清这个判断逻辑比堆二十个 payload 更有价值。注意这类检测只建议在你自己搭的实验环境或授权靶场上跑。对着公网站点扫既是法律风险也是把课设做成反面教材最快的路径。4.3 DoS 与嗅探演示尺度与模块边界DosScan 和 SnifferScan 这两个模块名称听起来吓人实际在课程设计里都收敛在演示尺度内。DosScan 常见做法是创建大量 socket 对目标发起连接请求配合循环观察目标响应变慢。但课设不追求真实打挂目标更看重两点一是代码里对套接字资源的管理开了多少个 socket、内存怎么释放是不是每次循环结束就 closesocket二是目标选择必须有边界只能打自己建的虚拟机或本机回环地址不能对别人的机器做任何压力测试这是红线。答辩时被问到“这个模块会不会产生真实危害”你就把实验条件讲清楚说明只在本机验证流程就没什么问题。SnifferScan 模块走原始套接字抓包用 SOCK_RAW 绑定本地网卡抓取流经本机的数据包并解析出 IP 头、TCP/UDP 端口和负载长度。Windows 下原始套接字抓包前必须用管理员权限运行程序否则 socket 创建直接失败。这个模块在答辩里适合讲协议栈分层以太网头 14 字节、IP 头默认 20 字节、TCP 头默认 20 字节每个偏移量对应哪个字段按顺序解析就能还原出五元组。能不能抓到包取决于网卡是否支持并开启混杂模式、以及是否在允许抓包的实验网段。MFC 的界面刷新在这里也最容易出问题抓包是持续不断的回调如果每个包都直接操作界面控件窗口会卡住正确做法是把包内容先塞进队列用定时器批量刷新列表。5. 避坑与常见问题排查VC6.0 时代遗留的五类翻车现场5.1 编译期链接库、头文件与字符集现象编译报错fatal error C1083: Cannot open include file: Iphlpapi.h或者链接时报一堆unresolved external symbol。原因VC6.0 默认没有把 Platform SDK 的头文件和库目录加进工程Iphlpapi.h 和 ws2_32.lib 都要依赖额外的 SDK 路径或手工链接配置。解决先把工程目录下自带的 Iphlpapi.h 和 IPTYPES.H 拷到源文件同目录把 include 写法从#include Iphlpapi.h改成#include Iphlpapi.h让编译器优先在当前目录找头文件。然后在 工程 - 设置 - 链接 - 对象/库模块 里补上 ws2_32.lib 和 iphlpapi.lib。新版 VS 对应在“链接器 - 输入 - 附加依赖项”里加。改完这两处大部分编译错误能消掉。现象程序能编译但对话框里的中文按钮和提示全部乱码。原因VC6 工程默认 ANSI 编码新版 VS 默认 Unicode。MFC 里 CString 在两种字符集下底层分别是 char 和 wchar_t资源脚本里的中文在 Unicode 工程下也会出现编码错位。解决项目属性里把字符集设置成“使用多字节字符集”。如果源码里用了_T()宏包裹字符串代码本身不用改改完重新编译乱码基本消失。5.2 运行期界面假死、扫描全灭与嗅探无输出现象点“开始扫描”后窗口没反应拖动窗口直接显示“未响应”。原因扫描逻辑跑在了 UI 线程里MFC 的主消息循环被网络等待阻塞重绘消息排不上队。解决把耗时网络操作挪进 AfxBeginThread 创建的工作线程线程里只做探测通过 PostMessage 把结果发回主对话框由主窗口的消息处理函数刷新列表控件。判断逻辑是否阻塞 UI 有个土办法扫描循环里加一句if (::PeekMessage(msg, NULL, 0, 0, PM_NOREMOVE))看循环过程中这条消息能不能被及时处理处理不了就说明消息泵被堵住了。现象端口扫描全部显示关闭但目标明明开着 Web 服务。原因最常见两类一是目标机器开了防火墙TCP 探测被静默丢弃二是超时时间设太短比如小于 500msconnect 还没完成就被 select 判定为超时。解决先在命令行用 telnet 目标IP 端口验证可达性排除了网络问题再把 IsPortOpen 的 timeoutMs 调到 1000 甚至 2000ms 重扫。另外确认扫描端和目标在同一网段且没有 VLAN 隔离跨网段扫描很容易出现假阴性。现象SnifferScan 点击开始后一行数据都抓不到也不报错。原因Windows 下 SOCK_RAW 抓包必须管理员权限普通权限创建原始套接字会失败另外如果网卡不支持混杂模式只能抓到发给本机的包。解决右键以管理员身份运行程序。演示前先 ping 一下网关产生流量确认有包进入网卡再做判断。如果用的是虚拟机把网卡设为桥接模式比 NAT 模式更容易抓到真实流量。5.3 报告生成与结果截图交作业前最后一道关现象扫描完成后 rebort.html 没生成或者生成了但页面空白。原因报告模块的输出路径写的是相对路径程序工作目录不在源码包目录时文件写到了别处或者是报告内容在扫描完成前就被界面清理逻辑清空了。解决扫描完成后再触发报告生成输出路径用绝对路径或 GetModuleFileName 拼出程序所在目录。页面空白时先看生成的 HTML 文件大小如果只有几十字节多半是结果集对象已经释放把报告生成逻辑放到扫描线程结束后的主线程里重新取一遍数据。源码包里那张 respone.jpg 就是报告页面的效果截图提交报告时把截图和 rebort.html 放同一目录保证文档里引用的图片路径能对上。现象杀毒软件把编译出来的 exe 直接删了。原因扫描器里的端口探测、抓包、弱口令尝试这些功能特征会被安全软件识别成潜在恶意工具。解决编译时先加入杀毒软件信任区或者关闭实时防护等演示完再开回来。这不是代码问题是安全工具的天然特征和是不是课程设计没关系。6. 把课设改顺手报告模板、参数与验证清单一条龙6.1 报告模板与相对路径源码包里 rebort.html 是运行时拼出来的静态报告改模板时最省事的方法是保留原来的 HTML 骨架只替换结果表格的填充逻辑。我一般是这样做的扫描结果先存到临时结构体数组全部扫描结束后再一次性生成 HTML避免边扫边写导致内容互相覆盖。图片和报告放在同一目录HTML 里用相对路径引用 login_succeed.jpg、login_failed.jpg 这些截图。答辩拷贝时整个文件夹一起拷走链接不会断。6.2 参数与验证清单提交前花二十分钟把下面几项过一遍能避免大多数答辩翻车端口扫描超时设 1000ms重试 1 次主机扫描超时设 800ms重试 2 次用netstat -ano对照本机端口验证扫描结果实验网段选一台开了 NetBIOS 和 SNMP 的设备先跑通这两个模块弱口令和 SQL 注入只对本地靶机演示。线程数调到 8 左右扫描时界面保持可拖动证明线程调度是有效的。报告生成后打开 HTML 检查一遍表格和图片再提交。我自己做这类课设时吃过一次亏演示前十分钟发现 DoS 模块把实验虚拟机打到了不响应只能当场重启从那以后就再也不敢把线程数拉满做演示。每次改完参数我都会先在最小网段跑一遍完整流程确认界面不卡、报告能生成、截图位置正确再上演示机。这套源代码包里有论文、有报告、有全套模块截图和完整工程把工程在 VC6 里重新编译起来对照这份说明逐个模块过一遍你对 MFC 线程调度和常见网络协议的理解会比看十篇教程都具体。希望帮到你。本文还有配套的精品资源点击获取