harshilmodi2910/reconpy

GitHub: harshilmodi2910/reconpy

一个用 Python 编写的轻量级 TCP 端口扫描器,支持 banner 抓取识别服务和基于 TTL 的操作系统指纹猜测,适合安全学习和轻量级侦察任务。

Stars: 1 | Forks: 0

# reconpy 这是我在攻读网络安全硕士学位期间,为了学习网络侦察而使用 Python 构建的一个小型端口扫描器。它可以执行 TCP 端口扫描,尝试识别每个开放端口上运行的服务(通过 banner 抓取),并尽力猜测目标的操作系统。 [![CI](https://static.pigsec.cn/wp-content/uploads/repos/2026/05/3d818082eb014734.svg)](https://github.com/harshilmodi2910/reconpy/actions) ![Python](https://img.shields.io/badge/python-3.10%2B-blue) ## 为什么我要构建这个工具 我正在攻读网络安全管理硕士学位,希望能真正了解网络安全工作中“侦察”阶段到底发生了什么,而不仅仅是纸上谈兵。大多数初学者教程只是教你如何使用 `socket.connect()` 编写一个 30 行的端口扫描器然后就完事了。这并没有教会我多少东西。我想知道: - 为什么像 nmap 这样的专业扫描器支持“SYN 扫描”,而我写的简单版本却不行? - banner 抓取到底是如何工作的,为什么它比仅仅查看端口号更可靠? - 如何仅通过网络行为来猜测目标运行的是什么操作系统? 因此,我构建了 reconpy,试图通过编写代码来弄清楚这些问题。这段代码比 nmap 小得多——我并不打算与它竞争——但每个部分都能正常工作,而且我明白它为什么存在。 ## 功能特性 - TCP 端口扫描,支持并发,因此不会耗费太多时间 - 从开放端口抓取 banner 并利用它们来识别服务(因此即使在端口 8080 上运行的 SSH 也会被检测为 SSH,而不是“http-alt”) - 尝试利用 ICMP ping 回复中的 TTL 值来猜测操作系统,并以 banner 提示作为备用方案 - 输出结果可以是易读的终端格式或 JSON 格式(便于通过管道传递给其他工具) - 包含单元测试和集成测试(集成测试实际上会绑定一个真实的本地 socket 并验证扫描器是否能找到它) ## 如何安装和运行 ``` git clone https://github.com/harshilmodi2910/reconpy.git cd reconpy pip install -e . ``` 需要 Python 3.10 或更高版本。工具本身无需任何外部依赖(除非你想运行测试,那才需要 pytest)。 一些示例命令: ``` # 扫描目标上最常见的 100 个端口 reconpy 192.168.1.10 # 仅扫描特定端口 reconpy 192.168.1.10 --ports 22,80,443,8080 # 扫描范围 reconpy 192.168.1.10 --ports 1-1024 # 跳过 OS fingerprinting(它需要 admin/root 权限来执行 ICMP ping) reconpy 192.168.1.10 --no-fingerprint # 获取 JSON output reconpy 192.168.1.10 --json ``` 示例输出如下所示: ``` [+] target: 192.168.1.10 [+] os guess: Linux/Unix (TTL≈64) [+] open ports: 3 of 100 scanned PORT SERVICE BANNER 22/tcp ssh SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.1 80/tcp http HTTP/1.1 200 OK 443/tcp https (no banner) ``` ## TCP connect 扫描的工作原理(以及为什么我没有进行 SYN 扫描) 当你正常连接到一个 TCP 端口时,你的计算机会执行一次“三次握手”——发送一个 SYN 数据包,收到一个 SYN-ACK 回复,然后再回复一个 ACK。之后连接就建立了,任何一方都可以开始发送数据。 SYN 扫描仅发送第一个 SYN 数据包并查看响应(SYN-ACK = 端口开放,RST = 端口关闭)。它永远不会完成握手。这种扫描速度更快,且目标的日志不太可能将此次尝试记录为一次真实的连接。 但问题在于:SYN 扫描需要原始套接字(raw socket)权限,这意味着在 Linux/Mac 上需要以 root 身份运行,或在 Windows 上需要管理员权限。此外,由于你必须自己构造 TCP 数据包,这段代码也更难正确编写。 作为一个学习项目,我选择了 TCP connect 扫描(完整的握手过程)。它速度较慢且更容易暴露,但它适用于任何操作系统上的任何用户,代码也更容易测试,并且标准的 `socket` 模块会处理一切。我在 [docs/DESIGN.md](docs/DESIGN.md) 中写了一篇关于这种权衡的更详细的解释。 ## 基于 banner 的服务识别 当你连接到一个 TCP 端口时,许多服务会立即发送一小段问候信息。SSH 会发送类似于 `SSH-2.0-OpenSSH_8.9p1` 的信息。FTP 会发送 `220 vsFTPd 2.3.4`。这就是所谓的 banner。 我的扫描器会抓取服务发送的任何信息(或者对于 Web 端口发送一个微型 HTTP 探测,因为它们在没有收到请求时什么都不会发送),然后尝试将其与已知服务的模式进行匹配。如果无法匹配到 banner,它会退而求其次,使用常规的“端口 22 = SSH”查找表。 我让 banner 证据优先于端口查找,因为端口号仅仅是一种约定。有人可能会为了躲避常规扫描而将 SSH 运行在端口 8080 上,但他仍然会被发现,因为 banner 会暴露他们的行踪。这在我们的课程中经常出现——“证据优于假设”的重要性——能够将其真正编写成代码感觉非常棒。 ## 操作系统指纹识别 这是构建过程中最有趣的部分。基本思路是:当操作系统发送数据包时,它会在其上标记一个 TTL(Time-To-Live,生存时间)值。Linux/Mac/Unix 系统默认使用 64,Windows 使用 128,网络设备通常使用 255。沿途的每个路由器都会将 TTL 减 1。 因此,如果我 ping 一个目标并收到 TTL 为 55 的回复,那这可能是一台距离约 9 跳的 Linux 机器(64 - 55 = 9 跳)。 我在代码中记录了这些诚实的局限性: - 这需要原始套接字权限。如果你不是 root/管理员,我的代码会跳过 ping,转而通过查看服务 banner 中的操作系统线索(例如 SSH banner 中的“Ubuntu”)来进行判断。 - 位于你和目标之间的 NAT 或防火墙会重写 TTL,因此你看到的值可能无法反映目标真实的操作系统。 - 像 `nmap -O` 这样的专业工具会针对微妙的 TCP/IP 协议栈差异使用数十个探测包。而我的工具只使用了一个 ping。所以这只是一个猜测,而非定论。 我尽量在代码中诚实地反映这一点——它返回像 `"Linux/Unix (TTL≈64)"` 这样的标签,而不是简单地返回 `"Linux"`,这样你就能看出这个猜测的依据。 ## 项目结构 ``` reconpy/ ├── reconpy/ source code │ ├── scanner.py the actual port scanner with thread pool │ ├── services.py banner regex + port-to-service lookup │ ├── fingerprint.py TTL ping + banner-based OS guessing │ ├── output.py JSON + terminal output formatters │ ├── cli.py argparse setup, port spec parsing │ ├── __init__.py │ └── __main__.py entry point for `python -m reconpy` ├── tests/ pytest tests (39 of them) ├── docs/ │ ├── AUTHORIZED_USE.md legal/ethical use guide │ └── DESIGN.md why I made specific design choices ├── examples/ sample output writeups ├── pyproject.toml └── README.md ``` ## 运行测试 ``` pip install pytest pytest tests -v ``` 你应该会看到 `39 passed`。如果提示 `pytest is not recognized`,请尝试使用 `python -m pytest tests -v`。 集成测试实际上会在 `127.0.0.1` 上启动一个真实的 TCP 监听器,并检查我的扫描器是否能找到它——我不想模拟 socket 层,因为那样你只是在测试模拟代码,而不是真正的代码。 ## 沿途学到的经验 - `socket.connect_ex()` 返回错误码而不是抛出异常,这使得扫描循环比使用 `connect()` 并捕获异常要干净得多。 - 我最初尝试使用 `asyncio`,后来转而使用了 `ThreadPoolExecutor`。socket API 默认是阻塞的,因此要让其与 asyncio 一起工作,意味着需要重写所有内容以使用非阻塞 socket,对于线程就能轻松处理的工作负载来说,这增加了太多的复杂性。 - banner 抓取比我预期的要脆弱。某些服务(SSH、FTP、SMTP)在你连接后会立即发送 banner。而 HTTP 则不会——你必须先发送一个请求。仅支持 TLS 的服务(没有 banner 的 HTTPS)在没有进行真正握手之前什么都不会发送。 - 尽早编写测试改变了代码结构。我写的第一个版本把所有东西都塞进了一个巨大的函数中。一旦我开始编写测试,就不得不把代码拆分成更小的模块,这使得整个代码更易于阅读。 ## 未来想要添加的功能 - IPv6 支持(目前仅支持 IPv4) - CIDR 范围扫描(例如,`192.168.1.0/24` 扫描整个子网) - UDP 扫描——这更难,因为当你连接大多数 UDP 服务时,它们不会回复 - 也许会增加一些速率限制,这样你就可以放慢扫描速度,从而不会触发 IDS - 像 nmap 那样,使用多个 TCP 探测实现更好的操作系统指纹识别 ## 关于此项目 这是我在诺瓦东南大学攻读网络安全管理硕士学位期间构建的项目。最初的代码结构是在 AI 工具的帮助下完成的(我认为现在大多数开发者都会在一定程度上使用它),随后我对其进行了自定义、添加了功能、编写了测试,最重要的是,我仔细研究了每一部分代码,直到我完全理解它的作用。比起阅读教科书上的章节,做这个项目让我对 TCP 和网络侦察的实际工作原理有了更深刻的理解。 如果代码或文档中有任何不清楚的地方,欢迎提出 issue 或联系我。 ## 许可证 MIT — 详见 [LICENSE](LICENSE)。 ## 联系方式 **Harshil Modi** - GitHub: [@harshilmodi2910](https://github.com/harshilmodi2910) - LinkedIn: [Harshil Modi](https://linkedin.com/in/harshilmodi2910)
标签:API安全, DNS枚举, DNS查询工具, ICMP, JSON输出, nmap替代, OS指纹探测, Python, Qt框架, reconpy, SOCKETS编程, TCP扫描, TTL分析, 动态分析, 单元测试, 安全学习项目, 安全规则引擎, 并发扫描, 插件系统, 操作系统指纹识别, 无后门, 服务识别, 横幅抓取, 端口扫描器, 网络安全, 网络安全工具, 网络安全管理, 逆向工具, 隐私保护, 集成测试