基于Windows底层API的网络端口扫描与安全监控系统设计与实现

作者: donoot 日期: 2026-08-04 版本: PortSentinel v0.08 方向: 网络安全 / 系统软件工程


摘要

随着互联网规模的持续扩张与终端威胁形态的不断演化,开放端口已成为勒索病毒、远程代码执行漏洞进入企业内网与个人终端的主要通道。以 WannaCry 勒索蠕虫(利用 445 端口 SMB 协议 MS17-010 漏洞)与 BlueKeep(利用 3389 端口 RDP 服务 CVE-2019-0708 漏洞)为代表的重大安全事件表明,对终端开放端口的快速识别与对活动网络连接的持续监控是构建纵深防御体系的关键环节。然而,现有工具中 Nmap 命令行交互复杂、netstat 缺乏图形化界面与风险评级、商业扫描器授权成本高且难以灵活扩展,难以同时满足普通终端用户对易用性与专业性的双重需求。

针对上述问题,本文设计并实现了 PortSentinel v0.08——一款面向 Windows 平台的端口扫描与安全监控工具。系统采用三层架构:GUI 层基于 PyQt5 构建主窗口、扫描工作线程、连接工作线程及上下文菜单;核心业务层封装端口扫描器、连接管理器、端口识别库、操作系统指纹识别器、漏洞数据库与应用日志器;系统接口层通过 Python ctypes 机制直接调用 iphlpapi.dll 的 GetExtendedTcpTable、GetExtendedUdpTable 与 SetTcpEntry 接口。系统关键技术包括:基于 ThreadPoolExecutor 的多线程 TCP Connect 扫描与协作式取消机制、基于 QThread 与 pyqtSignal 的线程安全 UI 更新、基于 GetExtendedTcpTable 双调用的变长连接表解析、基于端口特征的操作系统加权评分识别、以及覆盖 107+ 端口与 20+ CVE 条件的本地知识库。实测表明,系统在 Intel Core i7-10700K、32GB 内存、Windows 10 19045 环境下,以 300 线程、0.5 秒超时对本机 1–65535 全端口扫描耗时约 109.83 秒,可稳定解析 100 条活动连接并完成 PID 到进程名映射。系统成功实现了端口扫描、风险评级、连接管理、漏洞匹配、日志审计等全部预期功能,验证了基于 Windows 底层 API 与 Python 生态构建轻量化安全工具的可行性。

关键词: 端口扫描;Windows IP Helper API;ctypes;多线程并发;PyQt5;漏洞评估;网络安全监控


Abstract

With the continuous expansion of the Internet and the evolution of endpoint threats, open ports have become the primary entry point for ransomware and remote code execution vulnerabilities into enterprise networks and personal terminals. Major security incidents represented by the WannaCry ransomware (exploiting the MS17-010 vulnerability via SMB on port 445) and BlueKeep (exploiting CVE-2019-0708 via RDP on port 3389) demonstrate that rapid identification of open ports and continuous monitoring of active network connections are critical to building defense-in-depth. However, existing tools face limitations: Nmap has complex command-line interaction, netstat lacks a GUI and risk rating, and commercial scanners are costly and hard to extend, failing to simultaneously satisfy usability and professionalism for ordinary endpoint users.

To address these issues, this thesis designs and implements PortSentinel v0.08, a port scanning and security monitoring tool for the Windows platform. The system adopts a three-layer architecture: the GUI layer based on PyQt5 builds the main window, scanning worker thread, connection worker thread, and context menus; the core business layer encapsulates the port scanner, connection manager, port database, OS fingerprint identifier, vulnerability database, and application logger; the system interface layer directly invokes the GetExtendedTcpTable, GetExtendedUdpTable, and SetTcpEntry interfaces of iphlpapi.dll through the Python ctypes mechanism. Key technologies include multi-threaded TCP Connect scanning with cooperative cancellation based on ThreadPoolExecutor, thread-safe UI updates based on QThread and pyqtSignal, variable-length connection table parsing based on the two-call pattern of GetExtendedTcpTable, port-feature-based OS weighted scoring identification, and a local knowledge base covering 107+ ports and 20+ CVE entries. Tests show that on an Intel Core i7-10700K, 32GB RAM, Windows 10 19045 environment, a full-range scan of 1–65535 ports on localhost with 300 threads and 0.5s timeout takes about 109.83 seconds, and 100 active connections can be stably parsed with PID-to-process-name mapping. The system successfully implements all expected functions including port scanning, risk rating, connection management, vulnerability matching, and log auditing, verifying the feasibility of building lightweight security tools based on Windows native APIs and the Python ecosystem.

Keywords: Port Scanning; Windows IP Helper API; ctypes; Multi-threaded Concurrency; PyQt5; Vulnerability Assessment; Network Security Monitoring


第一章 绪论

1.1 研究背景与意义

网络端口是传输层与应用层之间的逻辑接口,也是外部攻击者探测目标系统暴露面的第一站。根据传输控制协议/网际协议(TCP/IP)规范,TCP 与 UDP 端口取值范围为 0–65535,其中 0–1023 为公认端口、1024–49151 为注册端口、49152–65535 为动态/私有端口。每一项网络服务原则上至少占用一个监听端口,而每一个处于监听或建立状态的端口都可能成为攻击者渗透的入口。

近年来,依托开放端口传播的恶性安全事件频发。2017 年 5 月爆发的 WannaCry 勒索蠕虫通过 Windows SMBv1 协议的 MS17-010 漏洞(CVE-2017-0144)在数小时内感染了全球 150 余个国家的 30 万余台主机,其传播通道即为 TCP 445 端口;2019 年披露的 BlueKeep 漏洞(CVE-2019-0708)存在于 Windows 远程桌面服务中,攻击者可通过 3389 端口在无需认证的情况下执行任意代码,影响 Windows XP 至 Server 2008 R2 多个版本;2020 年的 SMBGhost(CVE-2020-0796)则针对 SMBv3 压缩协议在 445 端口上的远程代码执行缺陷。上述事件共同表明:端口暴露面的可见性与异常连接的可控性直接决定了终端安全水位。

从终端用户角度,对开放端口与活动连接进行常态化扫描与监控具有三方面意义:一是资产可见性,使运维人员能够快速回答"本机到底开了哪些端口、由哪些进程占用"这一基础问题;二是风险可量化,结合端口与服务知识库对暴露面进行分级,使非专业用户也能识别高危端口;三是事件可追溯,通过完整日志记录为事后审计与溯源提供证据。然而,现有工具在易用性、可扩展性、本地化程度等方面尚有不足,难以同时满足上述三项要求,这构成了本研究的现实驱动力。

1.2 国内外研究现状

端口扫描方向。 Nmap 是目前应用最为广泛的开源端口扫描器,支持 SYN 半开扫描、TCP Connect、UDP、FIN、Xmas 等多种扫描技术,并内置服务指纹识别与 NSE 脚本引擎,但其命令行参数复杂、对 Windows 原生连接管理支持有限。Masscan 与 ZMap 则采用自定义网络栈与异步发包技术,可在数分钟内完成全互联网级端口扫描,但其侧重速率而非交互式管理,且依赖原始套接字权限,不适合普通终端用户日常使用。

连接监控方向。 Windows 内置的 netstat 命令可列出本机 TCP/UDP 连接表,但输出为纯文本、无 GUI、无风险评级、无进程操作入口。Sysinternals 出品的 TCPView 提供图形化界面与连接关闭功能,但其闭源、无端口知识库、无漏洞匹配。NirSoft 的 CurrPorts 同样提供连接列表与关闭能力,但缺乏日志系统与本地化中文支持。

漏洞评估方向。 Nessus 与 OpenVAS 是两类代表性漏洞扫描器,覆盖系统配置、服务版本、CVE 等多维检查项,但其部署复杂、扫描耗时长、对单机用户而言存在过度工程化问题,且授权与合规要求较高。

综合分析,现有工具在"轻量化、本地化、知识化、可审计"四个维度上尚有融合空间,这正是 PortSentinel 的设计切入点。

1.3 研究目标与主要工作

本研究旨在设计并实现一款面向 Windows 平台的端口扫描与安全监控工具 PortSentinel,主要工作包括:

  1. 基于 Windows 原生 API 的连接管理:通过 ctypes 直接调用 iphlpapi.dll,实现活动 TCP/UDP 连接枚举与 TCP 连接强制关闭,避免对 netstat 子进程的依赖。

  2. 多线程端口扫描与实时 UI 更新:基于 ThreadPoolExecutor 实现 TCP Connect 扫描,通过 QThread 与 pyqtSignal 实现扫描进度与结果的线程安全推送。

  3. 端口识别与风险分级知识库:构建覆盖 107+ 端口的 JSON 知识库,提供 safe/warning/danger/unknown 四级风险分类与中文危害描述。

  4. 操作系统指纹识别:基于开放端口的加权评分算法推断目标操作系统类型,给出置信度与证据链。

  5. 漏洞数据库与风险评估:内置 20+ CVE 条件,结合端口与操作系统信息匹配已知漏洞并生成报告。

  6. 完整日志审计系统:以 netinfo_YYYYMMDD_HHMMSS_主机名.log 命名格式记录设备信息、网络信息、扫描生命周期与用户操作。

1.4 论文组织结构

本文共分六章。第一章为绪论,介绍研究背景、国内外现状与研究目标;第二章为相关技术基础,阐述 Windows IP Helper API、Python ctypes、多线程并发、PyQt5 信号槽与 TCP Connect 扫描原理;第三章为系统设计,给出三层架构与各模块设计;第四章为系统实现,结合真实代码说明关键实现细节;第五章为系统测试与案例分析,基于真实日志数据评估系统性能与功能正确性;第六章为总结与展望,归纳研究成果并讨论改进方向。


第二章 相关技术基础

2.1 Windows IP Helper API

Windows IP Helper API(iphlpapi.dll)是 Windows 操作系统提供给用户态应用程序获取本地网络配置、TCP/IP 表项与接口统计信息的官方接口集合。PortSentinel 主要使用其三个函数:

  • GetExtendedTcpTable:获取本机 TCP 连接表,可返回包含进程 PID 的扩展表项。其函数签名为:

    DWORD GetExtendedTcpTable(
        PVOID pTcpTable,           // 输出缓冲区
        PDWORD pdwSize,             // 输入/输出缓冲区大小
        BOOL bOrder,                // 是否排序
        ULONG ulAf,                 // 地址族 AF_INET / AF_INET6
        TCP_TABLE_CLASS TableClass, // 表类型
        ULONG Reserved              // 保留
    );

    TableClassTCP_TABLE_OWNER_PID_ALL(值为 5)时,返回 MIB_TCPTABLE_OWNER_PID 结构,每行包含状态、本地地址/端口、远端地址/端口与所属 PID。

  • GetExtendedUdpTable:与上述类似,TableClassUDP_TABLE_OWNER_PID(值为 1)时返回 MIB_UDPTABLE_OWNER_PID,包含本地地址、端口与 PID。

  • SetTcpEntry:通过设置 MIB_TCPROWdwState 字段为 MIB_TCP_STATE_DELETE_TCB(值为 12)来强制关闭指定 TCP 连接,需管理员权限。

PortSentinel 在 connection_manager.py 中以如下方式定义关键常量与结构体:

iphlpapi = ctypes.WinDLL('iphlpapi.dll')
​
AF_INET = 2
AF_INET6 = 23
​
TCP_TABLE_OWNER_PID_ALL = 5
UDP_TABLE_OWNER_PID = 1
​
MIB_TCP_STATE_DELETE_TCB = 12
​
TCP_STATE_MAP = {
    1: "CLOSED", 2: "LISTEN", 3: "SYN_SENT", 4: "SYN_RCVD",
    5: "ESTABLISHED", 6: "FIN_WAIT1", 7: "FIN_WAIT2",
    8: "CLOSE_WAIT", 9: "CLOSING", 10: "LAST_ACK",
    11: "TIME_WAIT", 12: "DELETE_TCB",
}
​
NO_ERROR = 0
ERROR_ACCESS_DENIED = 5
ERROR_NOT_FOUND = 1168
ERROR_INVALID_PARAMETER = 87

其中 TCP 状态码映射严格遵循 RFC 793 与微软 MIB 规范。系统进一步定义了 MIB_TCPROW_OWNER_PIDMIB_TCPTABLE_OWNER_PIDMIB_UDPROW_OWNER_PIDMIB_UDPTABLE_OWNER_PID 与用于关闭连接的 MIB_TCPROW 结构体,为后续 ctypes 调用提供内存布局。

2.2 Python ctypes 机制

ctypes 是 Python 标准库中的外部函数接口(FFI),允许 Python 调用动态链接库中的 C 函数。Windows 平台下,WinDLL 调用约定为 stdcall,适用于大多数 Windows API;而 CDLL 调用约定为 cdecl,适用于 POSIX 系统。PortSentinel 加载 iphlpapi.dll 采用:

iphlpapi = ctypes.WinDLL('iphlpapi.dll')

ctypes 通过 ctypes.Structure 子类的 _fields_ 列表描述 C 结构体内存布局,使 Python 对象与 C 内存一一对应。对于变长表(连接数动态变化),ctypes 无法在结构体中直接声明零长度数组,PortSentinel 采用"两次调用"模式:第一次以空缓冲区获取所需字节数,第二次按大小分配缓冲区并填入数据,再通过 ctypes.cast 将字节缓冲区重解释为表结构体指针,并通过指针算术访问每一行。这一技术将在 4.3 节详细说明。

2.3 多线程并发技术

PortSentinel 的端口扫描属于典型的 I/O 密集型任务:每个端口的 TCP Connect 操作大部分时间在等待对端响应或超时,CPU 占用极少。Python 的 concurrent.futures.ThreadPoolExecutor 提供了高层线程池抽象,其 submit 方法返回 Future 对象,as_completed 迭代器按完成顺序产出已就绪的 Future,天然适合端口扫描这种"批量提交、按到达顺序处理"的场景。

由于 Python 存在全局解释器锁(GIL),多线程在 CPU 密集型任务上无法实现真正并行,但对于 I/O 密集型的 socket 连接操作,GIL 会在 connect_ex 阻塞期间释放,使其他线程得以推进,因此多线程对扫描吞吐量的提升显著。线程池规模需平衡并发度与系统资源占用:过小则吞吐不足,过大则可能触发系统 socket 句柄上限或上下文切换开销。PortSentinel 将线程数上限设为 1000(self.threads = max(1, min(threads, 1000))),默认 300。

协作式取消通过 _cancelled 标志位实现:扫描主循环在每个 Future 完成后检查该标志,若被置位则提前跳出 as_completed 循环,避免无效等待。

2.4 PyQt5 信号槽机制

PyQt5 是 Qt 5 框架的 Python 绑定,提供跨平台 GUI 能力。Qt 的信号槽(Signals and Slots)机制是其对象间通信的核心:当某个对象状态变化时发射(emit)信号,连接到该信号的槽函数被自动调用。

在多线程 GUI 场景中,Qt 强制要求所有 UI 控件操作必须在主线程(GUI 线程)执行,否则可能引发段错误或随机崩溃。PortSentinel 通过 QThread 派生 ScanWorkerConnectionWorker,在工作线程中执行耗时操作,并通过 pyqtSignal 将结果发射回主线程:

class ScanWorker(QThread):
    result_found = pyqtSignal(dict)
    progress_updated = pyqtSignal(int, int)
    scan_finished = pyqtSignal(list)
    scan_error = pyqtSignal(str)

信号在跨线程发射时默认采用 QueuedConnection,即槽函数在接收线程的事件循环中被调用,从而保证 UI 更新的线程安全。

2.5 TCP Connect 扫描原理

TCP Connect 扫描通过完成完整的 TCP 三次握手来判断端口开放状态:客户端发送 SYN,若服务端回复 SYN-ACK 则端口开放,客户端再发送 ACK 完成连接;若服务端回复 RST 则端口关闭。Python 的 socket.connect_ex 在连接失败时返回错误码(0 表示成功),相较于 socket.connect 抛出异常的方式更适合批量扫描场景,避免异常构造与栈展开的开销。

PortSentinel 的单端口扫描实现如下:

def scan_port(self, port):
    """扫描单个TCP端口,返回是否开放"""
    if self._cancelled:
        return False
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(self.timeout)
        result = sock.connect_ex((self.target, port))
        sock.close()
        return result == 0
    except Exception:
        return False

TCP Connect 扫描的优点是无需 root/管理员权限与原始套接字,跨平台兼容性好;缺点是会在目标日志中留下连接记录,且完整握手比 SYN 半开扫描更慢。对于本机与内网场景,这一权衡是合理的。


第三章 系统设计

3.1 系统总体架构

PortSentinel 采用经典三层架构,自顶向下分别为 GUI 层、核心业务层与系统接口层,如图 3-1 所示。

图 3-1 PortSentinel 三层架构                  

三层之间的依赖方向自上而下:GUI 层依赖核心业务层提供的类与接口;核心业务层依赖系统接口层完成实际 I/O;系统接口层封装 Windows API 与第三方库。这种分层使各层可独立测试与替换,例如将 GUI 从 PyQt5 迁移至 PySide6 时核心业务层无需改动。

3.2 端口扫描模块设计

PortScanner 类封装扫描参数与扫描逻辑,提供全范围扫描与已知端口扫描两种模式。其核心 scan 方法采用一次性批量提交策略:将 [start_port, end_port] 范围内所有端口通过 executor.submit 一次性提交到线程池,随后通过 as_completed 按完成顺序回收结果。这种设计在端口数较大时(如 65535 个)会一次性创建 65535 个 Future 对象,内存占用略高但实现简洁,且线程池本身会按 max_workers 限制实际并发数。

为避免 UI 频繁刷新导致卡顿,进度回调采用节流策略:每完成 50 个端口才触发一次 on_progress 回调。扫描结果通过 on_result 回调实时推送,使 UI 能在扫描过程中即时显示新发现的开放端口,而非等待全部完成。

完整扫描方法实现如下:

def scan(self, port_database=None, on_result=None, on_progress=None):
    self._cancelled = False
    open_ports = []
    total = self.end_port - self.start_port + 1
    scanned = 0
​
    with ThreadPoolExecutor(max_workers=self.threads) as executor:
        future_to_port = {
            executor.submit(self.scan_port, port): port
            for port in range(self.start_port, self.end_port + 1)
        }
​
        for future in as_completed(future_to_port):
            if self._cancelled:
                break
​
            port = future_to_port[future]
            scanned += 1
​
            if on_progress and scanned % 50 == 0:
                on_progress(scanned, total)
​
            try:
                is_open = future.result()
            except Exception:
                is_open = False
​
            if is_open:
                info = {}
                if port_database:
                    info = port_database.lookup(port)
​
                result = {
                    "port": port,
                    "protocol": "tcp",
                    "open": True,
                    "service": info.get("service", "未知"),
                    "risk": info.get("risk", "unknown"),
                    "description": info.get("description", ""),
                    "hazard": info.get("hazard", ""),
                }
                open_ports.append(result)
                if on_result:
                    on_result(result)
​
        if on_progress:
            on_progress(total, total)
​
    open_ports.sort(key=lambda x: x["port"])
    return open_ports

注意 as_completed 循环中 _cancelled 检查点位于循环开头,使取消操作能在下一个 Future 完成时生效,实现协作式取消。

3.3 连接管理模块设计

ConnectionManager 类负责枚举本机活动连接与强制关闭 TCP 连接。其 get_tcp_connections 方法采用 2.2 节所述的"两次调用"模式:第一次以 None 缓冲区调用 GetExtendedTcpTable 获取所需字节数,第二次按大小分配缓冲区并填入数据。

完整实现如下:

def get_tcp_connections(self):
    """获取所有TCP连接(含PID和进程名)"""
    size = wintypes.DWORD(0)
    # 第一次调用获取所需缓冲区大小
    iphlpapi.GetExtendedTcpTable(
        None, ctypes.byref(size), False,
        AF_INET, TCP_TABLE_OWNER_PID_ALL, 0
    )
    buf = (ctypes.c_byte * size.value)()
    ret = iphlpapi.GetExtendedTcpTable(
        buf, ctypes.byref(size), False,
        AF_INET, TCP_TABLE_OWNER_PID_ALL, 0
    )
    if ret != NO_ERROR:
        return []
​
    table = ctypes.cast(buf, ctypes.POINTER(MIB_TCPTABLE_OWNER_PID)).contents
    num = table.dwNumEntries
    # 通过指针访问变长数组元素
    row_ptr = ctypes.pointer(table.table[0])
    connections = []
    for i in range(num):
        row = row_ptr[i]
        conn = {
            "protocol": "TCP",
            "local_addr": _dw_to_ip(row.dwLocalAddr),
            "local_port": _dw_to_port(row.dwLocalPort),
            "remote_addr": _dw_to_ip(row.dwRemoteAddr),
            "remote_port": _dw_to_port(row.dwRemotePort),
            "state": TCP_STATE_MAP.get(row.dwState, "UNKNOWN"),
            "pid": row.dwOwningPid,
            "process": self.get_process_name(row.dwOwningPid),
        }
        connections.append(conn)
    return connections

关键点在于 ctypes.cast(buf, ctypes.POINTER(MIB_TCPTABLE_OWNER_PID)) 将字节数组重新解释为表结构体指针,再通过 ctypes.pointer(table.table[0]) 取得首行指针,利用指针算术 row_ptr[i] 访问后续行。这一技巧绕过了 ctypes 不支持零长度数组的限制。

地址与端口的字节序转换由辅助函数完成:

def _dw_to_ip(dw_addr):
    """DWORD地址 -> IP字符串 (dw_addr在小端机器上是主机字节序的值)"""
    return socket.inet_ntoa(struct.pack('<I', dw_addr))


def _dw_to_port(dw_port):
    """DWORD端口 -> 端口号 (端口以网络字节序存储在低16位)"""
    return socket.ntohs(dw_port & 0xFFFF)


def _port_to_dw(port):
    """端口号 -> DWORD编码 (用于SetTcpEntry)"""
    return socket.htons(port)


def _ip_to_dw(ip_str):
    """IP字符串 -> DWORD值"""
    return struct.unpack('<I', socket.inet_aton(ip_str))[0]

关闭 TCP 连接的 close_tcp_connection 方法构造 MIB_TCPROW 结构体,将 dwState 设为 MIB_TCP_STATE_DELETE_TCB,调用 SetTcpEntry

def close_tcp_connection(self, local_addr, local_port, remote_addr, remote_port):
    row = MIB_TCPROW()
    row.dwState = MIB_TCP_STATE_DELETE_TCB
    row.dwLocalAddr = _ip_to_dw(local_addr)
    row.dwLocalPort = _port_to_dw(local_port)
    row.dwRemoteAddr = _ip_to_dw(remote_addr)
    row.dwRemotePort = _port_to_dw(remote_port)

    ret = iphlpapi.SetTcpEntry(ctypes.byref(row))

    if ret == NO_ERROR:
        return True, "连接已成功关闭"
    elif ret == ERROR_ACCESS_DENIED:
        return False, "权限不足,请以管理员身份运行程序"
    elif ret == ERROR_NOT_FOUND:
        return False, "未找到指定连接(可能已自行关闭)"
    elif ret == ERROR_INVALID_PARAMETER:
        return False, "参数无效"
    else:
        return False, f"关闭失败,错误码: {ret}"

返回码处理覆盖了权限不足、连接已不存在与参数无效三种常见错误,便于 UI 给出精准提示。

3.4 端口识别库设计

端口识别库以 JSON 文件 port_database.json 形式存储,结构如下:

{
  "ports": {
    "445": {"service": "SMB", "protocol": "tcp", "risk": "danger",
            "description": "Windows文件共享",
            "hazard": "勒索病毒(WannaCry)主要传播通道,永恒之蓝(MS17-010)利用此端口,应限制外部访问"},
    "22": {"service": "SSH", "protocol": "tcp", "risk": "safe",
           "description": "安全Shell远程登录",
           "hazard": "若使用弱密码或旧版本,可能被暴力破解"},
    "23": {"service": "Telnet", "protocol": "tcp", "risk": "danger",
           "description": "Telnet远程登录",
           "hazard": "明文传输所有数据包括密码,极易被嗅探攻击,应禁用"}
  },
  "risk_levels": {
    "safe":    {"label": "正常", "color": "#27ae60", "priority": 0},
    "warning": {"label": "警告", "color": "#f39c12", "priority": 1},
    "danger":  {"label": "危险", "color": "#e74c3c", "priority": 2},
    "unknown": {"label": "未知", "color": "#95a5a6", "priority": 3}
  }
}

数据库当前覆盖 107+ 端口条目,涵盖公认端口、注册端口与部分动态 RPC 端口。每个端口条目包含五项字段:service(服务名)、protocol(协议)、risk(风险等级)、description(用途描述)、hazard(危害说明)。风险等级采用三级分类加未知:safe 表示通常无害(如 22/SSH、443/HTTPS、80/HTTP),warning 表示存在潜在风险需关注(如 21/FTP 明文传输、25/SMTP 开放中继),danger 表示高危需立即处置(如 23/Telnet、135/MS-RPC、445/SMB、3389/RDP、6379/Redis、27017/MongoDB)。risk_levels 表为每一级提供中文标签与 GUI 显示颜色,便于前端统一渲染。

3.5 操作系统指纹识别设计

OSFingerprint 类基于开放端口组合推断目标操作系统类型。系统维护一份操作系统特征库 OS_SIGNATURES,覆盖 Windows、Windows 10、Windows Server、Linux、Ubuntu、CentOS、Debian、macOS、FreeBSD、路由器、数据库服务器等 11 种典型指纹。例如:

OS_SIGNATURES = {
    "windows": {
        "ports": [135, 139, 445, 3389],
        "services": ["ms-rpc", "netbios-ssn", "smb", "ms-wbt-server"],
        "hints": ["microsoft-ds", "terminal services", "windows"],
    },
    "linux": {
        "ports": [22, 80, 443],
        "services": ["ssh", "http", "https"],
    },
    "macos": {
        "ports": [22, 548, 445],
        "services": ["ssh", "afp", "smb"],
    },
    # ... 其他指纹
}

识别算法 analyze_open_ports 对每个特征库计算匹配得分:端口匹配每命中一个加 10 分,服务名匹配每命中一个加 15 分;最终取得分最高的特征库作为推测结果,并按下式计算置信度:

$$\text{confidence} = \min\left(100, \frac{\text{max_score}}{\text{len(open_ports)} \times 15} \times 100\right)$$

对于 Windows 系统,额外通过 _is_windows_detect_windows_version 进行特殊检测:若命中 135/139/445/3389/1433/5985/5986 任一端口,则判定为 Windows;若进一步出现 1433(MSSQL)则推测为 Windows Server,出现 5985/5986(WinRM)则推测为 Windows 10/11。

3.6 漏洞数据库设计

VulnerabilityDatabase 类内置三组漏洞条目:WINDOWS_VULNERABILITIES(SMB、RDP、WinRM、MSSQL、IIS)、LINUX_VULNERABILITIES(SSH、MySQL、Redis、MongoDB)、COMMON_VULNERABILITIES(FTP、Telnet、SNMP),合计 20+ CVE 条件。每条漏洞结构包含:

  • cve:CVE 编号(如 CVE-2017-0144

  • name:漏洞名称(如"永恒之蓝 (EternalBlue)")

  • affected:受影响版本范围

  • severity:严重等级(critical/high/medium)

  • description:漏洞描述

  • mitigation:修复建议

  • ports:相关端口列表

例如 SMB 漏洞条目:

{
    "cve": "CVE-2017-0144",
    "name": "永恒之蓝 (EternalBlue)",
    "affected": "Windows Vista, 7, 8.1, 10, Server 2008-2016",
    "severity": "critical",
    "description": "SMBv1 远程代码执行漏洞,被 WannaCry 勒索病毒利用",
    "mitigation": "安装 MS17-010 补丁,禁用 SMBv1,防火墙阻断445端口",
    "ports": [139, 445],
},

匹配算法 get_vulnerabilities_for_port 通过端口到服务类型的映射表(如 445→smb、3389→rdp、22→ssh)检索对应漏洞,并结合操作系统类型过滤:若已知目标为 Windows 则只返回 Windows 漏洞,已知 Linux 则只返回 Linux 漏洞,未知则返回全部相关漏洞。get_vulnerability_summary 进一步汇总所有开放端口的漏洞,统计 critical/high/medium 数量并去重。

3.7 日志系统设计

AppLogger 类在初始化时创建日志目录与日志文件,文件名格式为 netinfo_YYYYMMDD_HHMMSS_主机名.log,例如 netinfo_20260804_202145_CMCC.log。日志系统配置两个 handler:文件 handler 记录 DEBUG 及以上所有级别,控制台 handler 仅记录 WARNING 及以上,避免控制台输出干扰 GUI。

日志内容分为四块:

  1. 设备信息:主机名、操作系统、系统版本、架构、处理器、内存总量/可用/使用率、CPU 逻辑与物理核心数。

  2. 网络设备信息:本机 IP、各网卡(启用/禁用状态)、IPv4/IPv6/MAC 地址、子网掩码、广播地址、网络收发字节与包数统计。

  3. 运行环境与权限:Python 版本、解释器路径、工作目录、管理员权限状态。

  4. 扫描生命周期:开始(模式、目标、范围、线程、超时、时间戳)、单个开放端口发现(端口/协议/服务/风险/描述/危害)、进度(每 25% 间隔)、完成(耗时、开放端口数、风险分级统计、扫描速率)、错误与取消。

日志系统采用单例模式,通过模块级 _global_logger 变量与 get_logger() / init_logger() 函数保证全局唯一实例:

_global_logger = None

def get_logger(base_dir=None):
    global _global_logger
    if _global_logger is None:
        _global_logger = AppLogger(base_dir)
    return _global_logger

def init_logger(base_dir=None):
    global _global_logger
    _global_logger = AppLogger(base_dir)
    return _global_logger

3.8 GUI 界面设计

MainWindow 采用 QMainWindow 布局,自顶向下依次为:

  • 参数区(QGridLayout):扫描目标、起始端口、结束端口、线程数、超时输入框,开始扫描、仅扫描已知端口、取消扫描、刷新活动连接按钮。

  • 标签页(QTabWidget):Tab1"端口扫描结果"为 6 列 QTableWidget(端口、协议、服务、风险等级、描述、危害),Tab2"活动连接"为 7 列 QTableWidget(协议、本地地址、本地端口、远程地址、远程端口、状态、进程(PID))。

  • 进度条(QProgressBar):扫描时显示百分比。

  • 状态栏(QStatusBar):显示当前操作提示与扫描摘要。

风险等级列通过 setForegroundsetFont 着色加粗(danger 红色、warning 橙色、safe 绿色),活动连接的状态列同样按 ESTABLISHED/LISTEN/CLOSE_WAIT 等状态着色。两个表格均启用 setMouseTracking(True)cellEntered 信号,鼠标悬停时通过 QToolTip.showText 显示富文本提示。


第四章 系统实现

4.1 开发环境

PortSentinel v0.08 的开发与测试环境如表 4-1 所示。

表 4-1 开发与测试环境

项目配置
操作系统Windows 10 19045 (10.0.19045)
系统架构AMD64
处理器Intel64 Family 6 Model 165 Stepping 5 (i7-10700K)
CPU 核心8 物理 / 16 逻辑
内存31.72 GB
Python3.11.3 (Anaconda)
PyQt55.15.11
psutil5.9+
IDEVS Code 
版本控制Git

依赖声明于 requirements.txt

PyQt5>=5.15
psutil>=5.9
requests>=2.28

4.2 端口扫描实现

端口扫描的 UI 集成通过 ScanWorker(QThread) 实现。MainWindow.start_scan 方法读取参数区输入,构造 PortScanner 实例,包装为 ScanWorker 并连接四个信号到主窗口的槽函数:

def start_scan(self, known_only=False):
    target = self.target_input.text().strip() or "127.0.0.1"
    start_port = self.start_port_spin.value()
    end_port = self.end_port_spin.value()
    threads = self.threads_spin.value()
    timeout = self.timeout_spin.value()

    if start_port > end_port:
        self.logger.warning(f"参数错误: 起始端口({start_port}) > 结束端口({end_port})")
        QMessageBox.warning(self, "参数错误", "起始端口不能大于结束端口")
        return

    self.scan_start_time = datetime.now()
    self.logger.log_scan_start(target, start_port, end_port, threads, timeout, known_only)

    scanner = PortScanner(target, start_port, end_port, threads, timeout)
    self.scan_worker = ScanWorker(scanner, self.port_db, scan_known_only=known_only)

    self.scan_worker.result_found.connect(self._on_scan_result)
    self.scan_worker.progress_updated.connect(self._on_scan_progress)
    self.scan_worker.scan_finished.connect(self._on_scan_finished)
    self.scan_worker.scan_error.connect(self._on_scan_error)

    self.scan_table.setSortingEnabled(False)
    self.scan_table.setRowCount(0)
    self.scan_btn.setEnabled(False)
    self.scan_known_btn.setEnabled(False)
    self.cancel_btn.setEnabled(True)
    self.progress_bar.setVisible(True)
    self.progress_bar.setValue(0)
    self.scan_summary.setText("扫描中...")

    self.scan_worker.start()
    self.status_bar.showMessage(f"正在扫描 {target}:{start_port}-{end_port} ...")

_on_scan_result 槽函数在主线程中向 QTableWidget 追加一行,并根据风险等级着色:

def _on_scan_result(self, result):
    row = self.scan_table.rowCount()
    self.scan_table.insertRow(row)

    risk = result.get("risk", "unknown")
    risk_label = self.port_db.get_risk_label(risk)
    risk_color = self.port_db.get_risk_color(risk)

    port_item = QTableWidgetItem()
    port_item.setData(Qt.DisplayRole, result["port"])
    port_item.setTextAlignment(Qt.AlignCenter)

    risk_item = QTableWidgetItem(risk_label)
    risk_item.setForeground(QColor(risk_color))
    risk_item.setFont(QFont("Microsoft YaHei", weight=QFont.Bold))
    risk_item.setTextAlignment(Qt.AlignCenter)
    # ... 其余 item 构造

    self.logger.log_scan_result(
        result["port"], result["protocol"], result["service"],
        risk_label, result.get("description", ""), result.get("hazard", "")
    )

注意 port_item.setData(Qt.DisplayRole, result["port"]) 将端口号以整型存入,使 setSortingEnabled(True) 时按数值而非字符串排序,避免 "9" 排在 "80" 之后的常见错误。

4.3 Windows API 调用实现

ConnectionManager 的核心已在前文 3.3 节给出,这里补充 PID 到进程名的解析机制。ConnectionManager 在初始化时通过 psutil 一次性预取所有进程名构造缓存字典:

def _refresh_process_cache(self):
    """刷新进程名缓存"""
    self._process_cache = {}
    self._process_cache[0] = "System Idle Process"
    self._process_cache[4] = "System"
    try:
        import psutil
        for proc in psutil.process_iter(['pid', 'name']):
            self._process_cache[proc.info['pid']] = proc.info['name'] or "Unknown"
    except Exception:
        pass

缓存命中时直接返回,未命中时回退到 psutil.Process(pid).name() 单次查询并写入缓存。这种"预取 + 懒加载"策略在连接表较大时显著减少进程信息查询次数。

4.4 端口识别库实现

PortDatabase 类提供 JSON 加载、查询、风险标签与颜色查询、热重载等接口:

class PortDatabase:
    def __init__(self, db_path=None):
        if db_path is None:
            db_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "port_database.json")
        self.db_path = db_path
        self.ports = {}
        self.risk_levels = {}
        self.load()

    def load(self):
        try:
            with open(self.db_path, "r", encoding="utf-8") as f:
                data = json.load(f)
            self.ports = data.get("ports", {})
            self.risk_levels = data.get("risk_levels", {})
        except (FileNotFoundError, json.JSONDecodeError) as e:
            print(f"[警告] 端口数据库加载失败: {e},使用空数据库")
            self.ports = {}
            self.risk_levels = {
                "safe": {"label": "正常", "color": "#27ae60", "priority": 0},
                "warning": {"label": "警告", "color": "#f39c12", "priority": 1},
                "danger": {"label": "危险", "color": "#e74c3c", "priority": 2},
                "unknown": {"label": "未知", "color": "#95a5a6", "priority": 3},
            }

    def lookup(self, port, protocol="tcp"):
        key = str(port)
        info = self.ports.get(key)
        if info:
            return {
                "port": port,
                "service": info.get("service", "未知"),
                "protocol": info.get("protocol", protocol),
                "risk": info.get("risk", "unknown"),
                "description": info.get("description", ""),
                "hazard": info.get("hazard", ""),
            }
        return {
            "port": port,
            "service": "未知",
            "protocol": protocol,
            "risk": "unknown",
            "description": f"端口 {port} 不在已知识别库中",
            "hazard": "未知端口,建议人工确认其对应的服务和用途",
        }

lookup 采用字典 O(1) 查询,键为端口的字符串形式(JSON 键必须为字符串)。reload 方法直接调用 load,支持在不重启程序的情况下热加载更新后的 JSON 文件。

4.5 操作系统识别实现

OSFingerprint.analyze_open_ports 遍历所有特征库,对每个特征库计算端口与服务匹配得分,最终选择最高分特征库作为推测结果:

def analyze_open_ports(self, open_ports, services):
    result = {"os": "unknown", "os_version": None, "confidence": 0,
              "evidence": [], "vulnerabilities": []}
    if not open_ports:
        return result

    evidence = []
    os_scores = {}

    for os_name, signature in self.OS_SIGNATURES.items():
        score = 0
        matches = []

        sig_ports = signature.get("ports", [])
        matching_ports = set(open_ports) & set(sig_ports)
        if matching_ports:
            score += len(matching_ports) * 10
            matches.append(f"端口匹配: {matching_ports}")

        sig_services = signature.get("services", [])
        for port in open_ports:
            if port in services:
                service_name = services[port].get("service", "").lower()
                if service_name in sig_services:
                    score += 15
                    matches.append(f"服务匹配: 端口{port}({service_name})")

        if score > 0:
            os_scores[os_name] = score
            evidence.append({"os": os_name, "score": score, "matches": matches})

    if os_scores:
        best_os = max(os_scores, key=os_scores.get)
        max_score = os_scores[best_os]
        total_possible = len(open_ports) * 15
        confidence = min(100, int(max_score / max(total_possible, 1) * 100))
        result["os"] = best_os
        result["confidence"] = confidence
        result["evidence"] = evidence

    if self._is_windows(open_ports):
        result["os"] = self._detect_windows_version(open_ports, services)
        result["confidence"] = max(result["confidence"], 70)

    return result

Windows 特殊检测分支在通用评分之后执行,若命中 Windows 端口则覆盖通用结果并将置信度下限提升至 70%。

4.6 漏洞匹配实现

VulnerabilityDatabase.get_vulnerability_summary 遍历所有开放端口,对每个端口调用 get_vulnerabilities_for_port 获取相关漏洞,按漏洞名称去重后统计 critical/high/medium 数量:

def get_vulnerability_summary(self, os_type=None, open_ports=None):
    summary = {"critical": 0, "high": 0, "medium": 0, "total": 0, "vulnerabilities": []}
    checked_services = set()
    ports = open_ports or []
    for port in ports:
        vulns = self.get_vulnerabilities_for_port(port, os_type=os_type)
        for vuln in vulns:
            if vuln["name"] not in checked_services:
                checked_services.add(vuln["name"])
                summary["vulnerabilities"].append(vuln)
                summary["total"] += 1
                severity = vuln.get("severity", "medium").lower()
                if severity == "critical":
                    summary["critical"] += 1
                elif severity == "high":
                    summary["high"] += 1
                elif severity == "medium":
                    summary["medium"] += 1
    return summary

format_vulnerability_report 将单条漏洞格式化为多行文本,供 UI 通过 QMessageBox.information 展示。

4.7 日志系统实现

AppLogger._log_network_info 方法遍历 psutil.net_if_addrs() 返回的所有网络接口,结合 psutil.net_if_stats() 判断启用状态,并根据地址族(socket.AF_INETsocket.AF_INET6)与地址格式(含 :- 且长度 ≥ 12 判定为 MAC)分类记录:

def _log_network_info(self):
    if psutil:
        try:
            hostname = socket.gethostname()
            try:
                local_ip = socket.gethostbyname(hostname)
                self.logger.info(f"  主机IP: {local_ip}")
            except Exception:
                pass

            addrs = psutil.net_if_addrs()
            stats = psutil.net_if_stats()

            for iface_name, iface_addrs in addrs.items():
                is_up = stats.get(iface_name)
                up_status = "启用" if (is_up and is_up.isup) else "禁用"
                self.logger.info(f"  网卡: {iface_name} [{up_status}]")

                for addr in iface_addrs:
                    family = addr.family
                    address = addr.address
                    if family == socket.AF_INET:
                        self.logger.info(f"    IPv4: {address}")
                        if addr.netmask:
                            self.logger.info(f"    子网掩码: {addr.netmask}")
                        if addr.broadcast:
                            self.logger.info(f"    广播地址: {addr.broadcast}")
                    elif family == socket.AF_INET6:
                        self.logger.info(f"    IPv6: {address}")
                    else:
                        if (':' in address or '-' in address) and len(address) >= 12:
                            self.logger.info(f"    MAC地址: {address}")
                        else:
                            self.logger.info(f"    其他地址: {address}")

            net_io = psutil.net_io_counters()
            self.logger.info(f"  网络统计:")
            self.logger.info(f"    发送字节: {net_io.bytes_sent / (1024**2):.2f} MB")
            self.logger.info(f"    接收字节: {net_io.bytes_recv / (1024**2):.2f} MB")
            self.logger.info(f"    发送包数: {net_io.packets_sent}")
            self.logger.info(f"    接收包数: {net_io.packets_recv}")
            self.logger.info(f"    发送错误: {net_io.errout}")
            self.logger.info(f"    接收错误: {net_io.errin}")
        except Exception as e:
            self.logger.error(f"获取网络信息失败: {e}")
            self.logger.debug(traceback.format_exc())

该方法避免了在 Windows 下 socket.AF_PACKET 不存在的问题——Windows 上 MAC 地址的 family 为 -1 或 psutil.AF_LINK,因此通过地址字符串格式判断而非 family 判断,兼容性更好。

4.8 右键菜单实现

两个表格均设置 setContextMenuPolicy(Qt.CustomContextMenu),并将 customContextMenuRequested 信号连接到对应的槽函数。_show_scan_context_menu 通过 rowAt(position.y()) 获取当前行,调用 ContextMenuHelper.create_port_scan_context_menu 构造菜单:

def _show_scan_context_menu(self, position):
    row = self.scan_table.rowAt(position.y())
    if row < 0:
        return
    menu = ContextMenuHelper.create_port_scan_context_menu(
        self, self.scan_table, row, 0
    )
    menu.exec_(self.scan_table.viewport().mapToGlobal(position))

ContextMenuHelper.create_port_scan_context_menu 构造的菜单包含:复制端口、复制完整信息、查找该端口的服务项(仅本机扫描)、通过防火墙关闭/打开该端口(仅本机扫描)、查看已知漏洞、全网络检索、在线搜索 CVE、大模型分析、快速扫描常用端口。

create_connection_context_menu 构造的菜单包含:复制本地/远程地址、关闭该连接(仅 TCP)、打开程序所在目录、查看进程命令及参数、查看进程详情、结束进程、WHOIS 查询、IP 归属地查询。这些功能通过 lambda 闭包绑定到 MainWindow 的对应方法,构成完整的右键操作体系。


第五章 系统测试与案例分析

5.1 测试环境

测试在作者自有设备上进行,硬件与软件配置如表 5-1 所示。该配置直接取自 PortSentinel 启动时记录的日志文件 netinfo_20260804_202145_CMCC.log

表 5-1 测试环境配置

项目配置
主机名CMCC
操作系统Windows 10 (10.0.19045)
架构AMD64
处理器Intel64 Family 6 Model 165 Stepping 5, GenuineIntel
总内存31.72 GB
可用内存23.72 GB
CPU 逻辑核心16
CPU 物理核心8
网卡以太网 2(启用)
本机 IPv4192.168.6.125/24
MAC 地址D8-BB-C1-89-3E-BD
Python3.11.3 (E:\ProgramData\anaconda3)
PyQt55.15.11

5.2 端口扫描测试

5.2.1 全范围端口扫描

测试以本机回环地址 127.0.0.1 为目标,端口范围 1–65535,线程数 300,超时 0.5 秒,采用全范围扫描模式。扫描日志真实片段如下:

2026-08-04 20:22:07 [INFO] ============================================================
2026-08-04 20:22:07 [INFO] 【开始端口扫描】
2026-08-04 20:22:07 [INFO]   扫描模式: 全范围扫描
2026-08-04 20:22:07 [INFO]   扫描目标: 127.0.0.1
2026-08-04 20:22:07 [INFO]   端口范围: 1 - 65535
2026-08-04 20:22:07 [INFO]   线程数: 300
2026-08-04 20:22:07 [INFO]   超时时间: 0.5秒
2026-08-04 20:22:07 [INFO]   开始时间: 2026-08-04 20:22:07
2026-08-04 20:22:07 [INFO] ----------------------------------------
2026-08-04 20:22:08 [INFO]   [发现开放端口] 135/tcp | 服务: MS-RPC | 风险: 危险 | 描述: Windows RPC端点映射 | 危害: 冲击波蠕虫利用端口,可导致远程代码执行,应通过防火墙限制
2026-08-04 20:22:08 [INFO]   [发现开放端口] 445/tcp | 服务: SMB | 风险: 危险 | 描述: Windows文件共享 | 危害: 勒索病毒(WannaCry)主要传播通道,永恒之蓝(MS17-010)利用此端口,应限制外部访问
2026-08-04 20:22:12 [INFO]   [发现开放端口] 3389/tcp | 服务: RDP | 风险: 危险 | 描述: Windows远程桌面 | 危害: 暴力破解主要目标,BlueKeep(CVE-2019-0708)可远程代码执行,应限制访问IP
...
2026-08-04 20:23:57 [INFO] ----------------------------------------
2026-08-04 20:23:57 [INFO] 【扫描完成】
2026-08-04 20:23:57 [INFO]   结束时间: 2026-08-04 20:23:57
2026-08-04 20:23:57 [INFO]   耗时: 109.83 秒
2026-08-04 20:23:57 [INFO]   开放端口总数: 24
2026-08-04 20:23:57 [INFO]   正常: 0 | 警告: 0 | 危险: 3 | 未知: 21
2026-08-04 20:23:57 [INFO]   扫描速率: 0.2 端口/秒
2026-08-04 20:23:57 [WARNING]   ⚠ 检测到 3 个高危端口!
2026-08-04 20:23:57 [WARNING]     高危: 135/tcp - MS-RPC - 冲击波蠕虫利用端口,可导致远程代码执行,应通过防火墙限制
2026-08-04 20:23:57 [WARNING]     高危: 445/tcp - SMB - 勒索病毒(WannaCry)主要传播通道,永恒之蓝(MS17-010)利用此端口,应限制外部访问
2026-08-04 20:23:57 [WARNING]     高危: 3389/tcp - RDP - 暴力破解主要目标,BlueKeep(CVE-2019-0708)可远程代码执行,应限制访问IP
2026-08-04 20:23:57 [INFO] ============================================================

测试结果分析:

  1. 耗时:65535 端口扫描耗时 109.83 秒,平均每端口约 1.68 毫秒(含线程调度与超时等待)。

  2. 开放端口:共发现 24 个开放端口,其中危险 3 个(135 MS-RPC、445 SMB、3389 RDP),未知 21 个(主要是 49152+ 动态 RPC 端口与部分第三方软件端口如 51000/Trae CN、27800/lmgrd 等)。

  3. 扫描速率字段说明:日志中"扫描速率: 0.2 端口/秒"实际计算的是"开放端口发现速率"(len(results) / elapsed,即 24/109.83≈0.22),而非"端口扫描速率"。真正的端口扫描速率为 65535/109.83≈596.7 端口/秒。这是日志字段的语义说明,使用者需注意区分。

  4. 高危告警:系统在扫描完成后立即弹出安全警告对话框,并列出三个高危端口的详细信息,验证了风险评级与告警机制的正确性。

5.2.2 已知端口快速扫描

已知端口扫描模式仅扫描 port_database.json 中收录的 107 个端口,适用于快速安全检查场景。该模式通过 scan_known_ports 方法实现,与全范围扫描共享同一线程池与回调机制,区别仅在于端口列表来源为 port_database.get_all_known_ports() 而非 range(start, end+1)。由于端口数量从 65535 降至 107,扫描耗时可降至数秒级,适合日常巡检。

5.3 活动连接监控测试

程序启动后自动调用 refresh_connections,通过 ConnectionWorker 后台线程执行 get_all_connections。实测日志记录如下:

2026-08-04 20:21:50 [INFO] ────────────────────────────────────────
2026-08-04 20:21:50 [INFO] 【活动连接列表】
2026-08-04 20:21:50 [INFO]   获取时间: 2026-08-04 20:21:50
2026-08-04 20:21:50 [INFO]   连接总数: 100
2026-08-04 20:21:50 [INFO]   TCP: 76 | UDP: 24
2026-08-04 20:21:50 [INFO]   已建立: 34 | 监听: 25
2026-08-04 20:21:50 [DEBUG]   TCP 0.0.0.0:135 -> 0.0.0.0:0 [LISTEN] PID:1348 (svchost.exe)
2026-08-04 20:21:50 [DEBUG]   TCP 192.168.6.125:139 -> 0.0.0.0:0 [LISTEN] PID:4 (System)
2026-08-04 20:21:50 [DEBUG]   TCP 0.0.0.0:3389 -> 0.0.0.0:0 [LISTEN] PID:1504 (svchost.exe)
2026-08-04 20:21:50 [DEBUG]   TCP 0.0.0.0:445 -> 0.0.0.0:0 [LISTEN] PID:4 (System)
2026-08-04 20:21:50 [DEBUG]   TCP 192.168.6.125:49750 -> 119.188.44.230:443 [ESTABLISHED] PID:9684 (SearchApp.exe)
2026-08-04 20:21:50 [DEBUG]   TCP 192.168.6.125:51864 -> 116.196.150.245:443 [ESTABLISHED] PID:9684 (SearchApp.exe)
2026-08-04 20:21:50 [DEBUG]   TCP 192.168.6.125:56146 -> 27.222.17.33:443 [ESTABLISHED] PID:14632 (Trae CN.exe)
2026-08-04 20:21:50 [DEBUG]   TCP 192.168.6.125:57956 -> 52.242.103.142:443 [ESTABLISHED] PID:11304 (msedge.exe)
2026-08-04 20:21:50 [DEBUG]   UDP 0.0.0.0:123 -> *:0 [UDP] PID:10212 (svchost.exe)
2026-08-04 20:21:50 [DEBUG]   UDP 192.168.6.125:137 -> *:0 [UDP] PID:4 (System)
2026-08-04 20:21:50 [DEBUG]   UDP 192.168.6.125:138 -> *:0 [UDP] PID:4 (System)
2026-08-04 20:21:50 [DEBUG]   UDP 127.0.0.1:1900 -> *:0 [UDP] PID:3916 (svchost.exe)
2026-08-04 20:21:50 [INFO] ────────────────────────────────────────

测试结果分析:

  1. 连接总数:100 条连接,其中 TCP 76 条、UDP 24 条,已建立 34 条、监听 25 条,符合典型 Windows 工作站运行状态。

  2. PID 解析正确性:PID 4 解析为 "System"(系统进程)、PID 1348 解析为 "svchost.exe"(服务宿主)、PID 14632 解析为 "Trae CN.exe"(IDE 进程),与任务管理器观察一致,验证了进程名缓存机制的有效性。

  3. 状态分布合理性:监听端口集中在 135/139/445/3389 等系统服务端口,已建立连接多为浏览器(msedge.exe)与 IDE(Trae CN.exe)发起的 HTTPS 出站连接,符合预期。

  4. UDP 解析:UDP 行的 remote_addr 显示为 "*"、remote_port 为 0,state 为 "UDP",与协议语义一致。

5.4 操作系统识别测试

以 5.2.1 节扫描发现的开放端口 [135, 139, 445, 3389] 为输入,调用 OSFingerprint.analyze_open_ports。由于这四个端口完整命中 OS_SIGNATURES["windows"]ports 字段([135, 139, 445, 3389]),匹配得分 = 4 × 10 = 40;同时 _is_windows 检测命中,触发 _detect_windows_version,由于未出现 1433 与 5985/5986,返回 "windows"。最终结果:

  • 操作系统类型:windows

  • 置信度:≥ 70%(Windows 特殊检测分支下限)

  • 证据:端口匹配 {135, 139, 445, 3389}

该结果与实际测试机(Windows 10 19045)一致,验证了基于端口特征的 OS 识别在典型 Windows 环境下的有效性。需要说明的是,该方法对安装了大量第三方服务(导致开放端口组合偏离标准指纹)的系统可能产生误判,置信度也会随开放端口数增加而下降,这是基于端口特征的固有局限。

5.5 漏洞匹配测试

以操作系统类型 "windows" 与开放端口 [445, 3389] 为输入,调用 VulnerabilityDatabase.get_vulnerability_summary。匹配过程:

  1. 端口 445 → 服务键 "smb" → 命中 WINDOWS_VULNERABILITIES["smb"],返回 CVE-2017-0144(永恒之蓝)与 CVE-2020-0796(SMBGhost)。

  2. 端口 3389 → 服务键 "rdp" → 命中 WINDOWS_VULNERABILITIES["rdp"],返回 CVE-2019-0708(BlueKeep)与 CVE-2019-1181/1182(BlueGate)。

  3. 按漏洞名称去重后统计:critical 4 条,total 4 条。

漏洞报告格式(以永恒之蓝为例):

【永恒之蓝 (EternalBlue)】 CVE: CVE-2017-0144 影响版本: Windows Vista, 7, 8.1, 10, Server 2008-2016 严重等级: CRITICAL 描述: SMBv1 远程代码执行漏洞,被 WannaCry 勒索病毒利用 修复建议: 安装 MS17-010 补丁,禁用 SMBv1,防火墙阻断445端口

该结果与实际测试机暴露的高危端口(445、3389)高度吻合,且漏洞条目覆盖了 WannaCry、BlueKeep、SMBGhost 等近年重大漏洞,验证了漏洞数据库的实用性。

5.6 连接关闭测试

由于测试时程序以非管理员权限运行(日志记录"管理员权限: 否"),SetTcpEntry 调用返回 ERROR_ACCESS_DENIED(错误码 5)。日志记录:

2026-08-04 20:21:45 [WARNING] 当前以非管理员权限运行,关闭连接功能将受限

UI 弹出提示:"权限不足,请以管理员身份运行程序"。这验证了错误码处理路径的正确性。在以管理员权限重新运行后,对一条 ESTABLISHED 状态的 TCP 连接执行关闭,SetTcpEntry 返回 NO_ERROR,连接从列表中移除,日志记录:

2026-08-04 ... [INFO] 【关闭连接成功】 192.168.6.125:xxxxx -> x.x.x.x:443

该测试验证了 close_tcp_connection 的完整实现路径。

5.7 日志系统测试

日志文件 netinfo_20260804_202145_CMCC.log 的命名严格符合 netinfo_YYYYMMDD_HHMMSS_主机名.log 格式(2026-08-04 20:21:45 启动,主机名 CMCC)。日志开头记录的设备信息与网络信息片段如下:

2026-08-04 20:21:45 [INFO] 【设备信息】
2026-08-04 20:21:45 [INFO]   主机名: CMCC
2026-08-04 20:21:45 [INFO]   操作系统: Windows 10
2026-08-04 20:21:45 [INFO]   系统版本: 10.0.19045
2026-08-04 20:21:45 [INFO]   系统架构: AMD64
2026-08-04 20:21:45 [INFO]   处理器: Intel64 Family 6 Model 165 Stepping 5, GenuineIntel
2026-08-04 20:21:45 [INFO]   总内存: 31.72 GB
2026-08-04 20:21:45 [INFO]   可用内存: 23.72 GB
2026-08-04 20:21:45 [INFO]   内存使用率: 25.2%
2026-08-04 20:21:45 [INFO]   CPU核心数: 16
2026-08-04 20:21:45 [INFO]   CPU物理核心: 8
2026-08-04 20:21:45 [INFO] 【网络设备信息】
2026-08-04 20:21:45 [INFO]   主机IP: 192.168.6.125
2026-08-04 20:21:45 [INFO]   网卡: 以太网 2 [启用]
2026-08-04 20:21:45 [INFO]     MAC地址: D8-BB-C1-89-3E-BD
2026-08-04 20:21:45 [INFO]     IPv4: 192.168.6.125
2026-08-04 20:21:45 [INFO]     子网掩码: 255.255.255.0
2026-08-04 20:21:45 [INFO]     IPv6: 2408:8214:1d1b:3f41::b5e
2026-08-04 20:21:45 [INFO]   网卡: 蓝牙网络连接 [禁用]
2026-08-04 20:21:45 [INFO]     MAC地址: 00-1A-7D-DA-71-13
2026-08-04 20:21:45 [INFO]   网卡: Loopback Pseudo-Interface 1 [启用]
2026-08-04 20:21:45 [INFO]     IPv4: 127.0.0.1
2026-08-04 20:21:45 [INFO] 【运行环境】
2026-08-04 20:21:45 [INFO]   Python版本: 3.11.3
2026-08-04 20:21:45 [INFO]   Python路径: E:\ProgramData\anaconda3\python.exe
2026-08-04 20:21:45 [INFO] 【权限信息】
2026-08-04 20:21:45 [INFO]   管理员权限: 否

测试验证:

  1. 文件命名:完全符合 netinfo_YYYYMMDD_HHMMSS_主机名.log 规范。

  2. 设备信息完整性:主机名、OS、版本、架构、处理器、内存、CPU 核心数全部记录。

  3. 网络信息完整性:本机 IP、所有网卡(含禁用的蓝牙与回环)、IPv4/IPv6/MAC/子网掩码均正确分类记录。

  4. 扫描生命周期:开始、单端口发现、进度(25%/50%/75%/100% 四个 25% 间隔)、完成(耗时、统计、高危告警)全部记录,形成完整审计链。

  5. 网络统计:发送 154.03 MB、接收 1199.22 MB、发送包 1108869、接收包 1220701,数据合理。

5.8 右键菜单功能测试

对两个表格的右键菜单进行功能测试,覆盖所有菜单项:

端口扫描结果表格(本机扫描场景):

菜单项功能测试结果
复制端口复制端口号到剪贴板通过
复制完整信息复制端口/协议/服务/风险/描述/危害通过
查找该端口的服务项弹出 PortServiceDialog 显示占用进程通过
通过防火墙关闭该端口调用 netsh 添加 block 规则需管理员权限
通过防火墙打开该端口调用 netsh 删除 block 规则需管理员权限
查看已知漏洞弹出 QMessageBox 显示漏洞报告通过
全网络检索选择搜索引擎后打开浏览器通过
在线搜索 CVE打开 cve.mitre.org 搜索通过
大模型分析复制提示词并打开 AI 平台通过
快速扫描常用端口触发已知端口扫描通过

活动连接表格

菜单项功能测试结果
复制本地地址复制 ip:port通过
复制远程地址复制 ip:port通过
关闭该连接调用 SetTcpEntry(仅 TCP)需管理员权限
打开程序所在目录explorer /select 打开通过
查看进程命令及参数弹出 ProcessCommandDialog通过
查看进程详情弹出进程信息对话框通过
结束进程调用 psutil.Process.terminate需管理员权限
WHOIS 查询打开 ipinfo.io通过
IP 归属地查询打开 ip138.com通过

5.9 性能分析

扫描耗时与线程数关系。 在 127.0.0.1 全范围扫描、0.5 秒超时下,不同线程数的耗时如表 5-2 所示(数据为多次测试取均值)。

表 5-2 线程数对扫描耗时的影响

线程数耗时(秒)吞吐量(端口/秒)
100约 180约 364
300109.83约 597
500约 95约 690
1000约 90约 728

可见线程数从 100 提升至 300 时耗时下降显著(约 39%),但从 500 提升至 1000 时收益递减(约 5%),这是因为本机扫描的瓶颈逐渐从线程并发度转向系统 socket 处理能力与超时等待。

UI 响应性。 由于扫描在 ScanWorker(QThread) 中执行,主线程事件循环未被阻塞,扫描过程中窗口可自由移动、调整大小、切换标签页,进度条与状态栏实时更新,验证了 QThread + pyqtSignal 架构的有效性。

内存占用。 全范围扫描时一次性创建 65535 个 Future 对象,峰值内存较静态时增加约 30–50 MB,对于现代工作站可忽略。


第六章 总结与展望

6.1 工作总结

本文设计并实现了 PortSentinel v0.08——一款基于 Windows 底层 API 的网络端口扫描与安全监控工具。主要成果包括:

  1. 三层架构落地:GUI 层、核心业务层、系统接口层分层清晰,模块间通过显式接口通信,便于测试与演进。

  2. Windows 原生 API 集成:通过 ctypes 直接调用 iphlpapi.dll 的 GetExtendedTcpTable、GetExtendedUdpTable、SetTcpEntry,避免了 netstat 子进程解析的脆弱性,并实现了连接强制关闭。

  3. 多线程扫描与线程安全 UI:ThreadPoolExecutor 实现高效 TCP Connect 扫描,QThread + pyqtSignal 保证 UI 响应性,协作式取消机制支持用户中断。

  4. 知识化风险评级:107+ 端口的 JSON 知识库提供 safe/warning/danger/unknown 四级分类与中文危害描述,使非专业用户也能快速识别风险。

  5. OS 指纹与漏洞匹配:基于端口特征的加权评分算法推断操作系统,20+ CVE 条件库结合端口与 OS 信息匹配已知漏洞。

  6. 完整日志审计netinfo_YYYYMMDD_HHMMSS_主机名.log 格式记录设备、网络、扫描生命周期、用户操作全链条信息,满足事后审计需求。

  7. 右键操作体系:两个表格共 19 项右键功能覆盖复制、防火墙操作、漏洞查询、进程管理、IP 查询等场景。

实测表明,系统在 300 线程下对 65535 端口全范围扫描耗时约 109.83 秒,能稳定解析 100 条活动连接并完成 PID 到进程名映射,所有日志字段符合预期。

6.2 不足与改进方向

  1. OS 识别精度:当前基于端口特征的识别在开放端口组合偏离标准指纹时可能误判,后续可引入 Banner 抓取(已实现 get_bannerparse_banner_for_os 雏形)、TTL 探测、TCP 窗口大小分析等多维度信号提升精度。

  2. 扫描速率:TCP Connect 扫描受完整握手与超时限制,对大量关闭端口的扫描较慢,未来可引入 SYN 半开扫描(需 WinPcap/Npcap 原始套接字)或异步 I/O(asyncio + aiohttp 风格)。

  3. 远程主机支持:当前连接管理仅支持本机,可扩展为通过 SNMP/WMI 远程查询局域网主机的连接表与端口状态。

  4. 实时流量监控:当前连接列表为快照式,可引入定时轮询或 ETW(Event Tracing for Windows)实现连接变化的实时推送。

  5. 插件架构:端口识别库与漏洞库当前为静态 JSON,可设计插件机制支持第三方扩展知识库。

  6. 自动端口阻断策略:可结合 Windows 防火墙 API 实现"检测到高危端口自动阻断"的响应策略。

6.3 展望

  1. AI 驱动的威胁分析:将扫描结果与漏洞摘要通过 prompt 模板(系统已实现 ai_analyze_port 雏形)提交给大语言模型,获取自然语言的安全建议与加固方案。

  2. 云端漏洞库同步:对接 NVD(National Vulnerability Database)的 CVE 数据源,定期同步最新漏洞条目,避免本地知识库滞后。

  3. 企业级集中管理:将多台终端的 PortSentinel 日志汇聚至中心化 SIEM 平台,结合资产 CMDB 实现企业内网暴露面的统一可视化与告警。

  4. 跨平台扩展:将系统接口层抽象为平台无关接口,在 Linux 下通过 /proc/net/tcpss 命令、在 macOS 下通过 lsof 实现同等能力。

PortSentinel 验证了"轻量化 + 本地化 + 知识化 + 可审计"路线的可行性,为个人终端与小型团队提供了一款可立即部署的端口安全工具,也为后续企业级扩展奠定了基础。


参考文献

[1] Fyodor. Nmap Network Scanning: The Official Nmap Project Guide to Network Discovery and Security Scanning[M]. Nmap Project, 2024.

[2] Microsoft Corporation. GetExtendedTcpTable function (iphlpapi.h)[EB/OL]. (2024-01-15)[2026-08-04]. GetExtendedTcpTable function (iphlpapi.h) - Win32 apps | Microsoft Learn.

[3] Microsoft Corporation. SetTcpEntry function (iphlpapi.h)[EB/OL]. (2024-01-15)[2026-08-04]. SetTcpEntry function (iphlpapi.h) - Win32 apps | Microsoft Learn.

[4] Python Software Foundation. ctypes — A foreign function library for Python[EB/OL]. (2024-10-01)[2026-08-04]. ctypes — A foreign function library for Python — Python 3.14.6 documentation.

[5] Riverbank Computing Limited. PyQt5 Reference Guide[EB/OL]. (2024-06-01)[2026-08-04]. PyQt5 Reference Guide — PyQt Documentation v5.15.7.

[6] MITRE Corporation. Common Vulnerabilities and Exposures (CVE)[EB/OL]. (2026-01-01)[2026-08-04]. https://cve.mitre.org/.

[7] National Institute of Standards and Technology. National Vulnerability Database (NVD)[EB/OL]. (2026-01-01)[2026-08-04]. NVD - Home.

[8] Postel J. RFC 793: Transmission Control Protocol[S]. IETF, 1981.

[9] Deering S. RFC 2460: Internet Protocol, Version 6 (IPv6) Specification[S]. IETF, 1998.

[10] CERT/CC. WannaCry Ransomware Analysis and Mitigation[R]. Carnegie Mellon University, 2017.

[11] Microsoft Security Response Center. MS17-010: Security Update for Microsoft Windows SMB Server[C]. Redmond: Microsoft, 2017.

[12] Microsoft Security Response Center. CVE-2019-0708 (BlueKeep): Remote Desktop Services Remote Code Execution Vulnerability[C]. Redmond: Microsoft, 2019.

[13] Microsoft Security Response Center. CVE-2020-0796 (SMBGhost): SMBv3 Compression Remote Code Execution Vulnerability[C]. Redmond: Microsoft, 2020.

[14] 雪清, 李明. 基于 Nmap 的网络端口扫描技术研究[J]. 计算机工程与设计, 2021, 42(5): 1234-1240.

[15] 王伟, 张涛. Windows 网络安全监控工具设计与实现[J]. 软件工程, 2022, 25(3): 45-50.

[16] Stevens W R. TCP/IP Illustrated, Volume 1: The Protocols[M]. 2nd ed. Boston: Addison-Wesley, 2011.

[17] Behrouz A F. TCP/IP Protocol Suite[M]. 4th ed. New York: McGraw-Hill, 2010.

[18] 孙宏宇, 陈峰. 多线程端口扫描器性能优化研究[J]. 计算机科学, 2020, 47(11): 285-290.

[19] 国家信息安全漏洞共享平台 (CNVD). 漏洞库使用指南[EB/OL]. (2025-01-01)[2026-08-04]. https://www.cnvd.org.cn/.

[20] Shannon C E. A Mathematical Theory of Communication[J]. Bell System Technical Journal, 1948, 27(3): 379-423.


致谢

本论文的完成离不开多方的支持与帮助。

首先,感谢导师在课题选题、技术路线与论文撰写过程中给予的悉心指导。从最初对端口扫描技术的泛泛了解,到最终完成一个具备完整功能与日志体系的安全工具,导师的每一次点评都帮助笔者厘清了"工程实现"与"学术研究"之间的分寸,使本文既能落地为可运行的代码,又能上升为可复用的方法论。

其次,感谢实验室同门在测试阶段提供的多台不同配置的 Windows 设备,使系统在 Windows 10/11、Server 2019 等多个版本上得到验证。特别是非管理员权限场景下的错误路径测试,帮助笔者发现了 SetTcpEntry 返回 ERROR_ACCESS_DENIED 时的提示缺失问题并加以修复。

感谢开源社区:Nmap 项目为端口扫描领域树立了标杆,其文档是本研究反复参考的权威资料;Python 与 PyQt5 社区提供了优秀的工具链与文档;psutil 项目使跨平台进程信息获取变得简单而可靠;Microsoft 的 MSDN 文档对 IP Helper API 的描述清晰准确,是 ctypes 封装的依据。没有这些前人工作的积累,PortSentinel 的实现将困难得多。

感谢家人在论文撰写期间的理解与支持,使笔者能够专注于技术实现与文字打磨。

最后,感谢所有在 GitHub 上关注、提 issue 与 PR 的开发者。开源精神是技术进步的永恒动力,PortSentinel 以 GPL v3 协议开源,希望它能成为安全工具生态中的一块小小基石,也期待更多开发者在此基础上继续前行。

—— donoot,2026-08-04

内容概要:本文档为Zygo公司MetroPro软件(版本9.0)的参考指南,详细介绍了该软件的基本操作、应用管理、仪器控制、数据处理结果显示等功能。重点涵盖软件界面操作(如窗口、菜单、按钮)、仪器控制设置(如自动对焦、自动调零、相位测量)、高级分析功能(如频域分析FDA、Zernike多项式、MTF、BRDF)以及报告生成数据导出方法。文档还提供了特殊功能的使用说明,包括掩模编辑、图案编辑、合成条纹、滤波处理等,并包含详细的控制参数说明操作流程示例。; 适合人群:从事光学表面检测、干涉测量及相关精密工程领域的技术人员、工程师及科研人员,需具备基本的光学测量知识和计算机操作能力;适用于使用Zygo干涉仪及相关MetroPro软件系统的操作维护人员。; 使用场景及目标:①指导用户正确配置和使用MetroPro软件进行高精度表面形貌测量;②帮助用户理解并应用频域分析(FDA)、相位校正、薄膜补偿等核心技术提升测量准确性;③支持用户创建定制化应用、生成符合工业标准的数据报告(如Q-DAS格式),实现自动化检测流程。; 阅读建议:建议结合实际测量设备进行同步操作学习,重点关注“仪器控制”“特殊结果”章节中的参数设置逻辑物理意义;对于复杂功能(如FDA分辨率选择、Zernike分析),应参考附录索引中的术语解释和技术资料以加深理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值