uvsser/Artemis

GitHub: uvsser/Artemis

基于 asyncio 的轻量级 TCP 端口扫描器,通过交互式 Shell 快速识别开放端口、抓取 Banner 并映射服务名称。

Stars: 0 | Forks: 0

# 🔍 Artemis — 异步 TCP 端口扫描器 Artemis 是一个使用 Python 构建的轻量、快速、异步 TCP 端口扫描器。它专为网络侦察设计——识别开放端口、检测运行中的服务,并从存活主机抓取 Banner。它通过交互式 Shell 界面运行,既适合快速的一次性扫描,也适合探索性会话。 ## 目录 - [功能特性](#features) - [系统要求](#requirements) - [安装](#installation) - [用法](#usage) - [启动 Shell](#starting-the-shell) - [命令](#commands) - [扫描示例](#scan-examples) - [端口语法参考](#port-syntax-reference) - [架构与设计决策](#architecture--design-decisions) - [模块与文件结构](#module--file-structure) - [函数参考](#function-reference) - [输出格式](#output-format) - [局限性](#limitations) - [免责声明](#disclaimer) ## 功能特性 - ⚡ **异步扫描** — 使用 Python 的 `asyncio` 并发扫描数百个端口 - 🔎 **Banner 抓取** — 捕获开放端口发送的任何数据的第一行 - 🧠 **服务检测** — 通过 OS 服务数据库将开放端口映射到知名服务名称 - 🛡️ **输入验证** — 在扫描前验证 IP 地址和主机名 - 🖥️ **交互式 Shell** — 持久化的 REPL 风格界面,支持 `scan`、`help`、`clear` 和 `exit` 命令 - ⚙️ **可配置** — 每次扫描可调整并发数、超时和端口范围 - 🪟 **跨平台** — 适用于 Linux、macOS 和 Windows ## 系统要求 - Python **3.8** 或更高版本(用于 `asyncio.run()` 和 `asyncio.as_completed()`) - 无第三方依赖 — 整个项目仅使用 Python 标准库 ## 安装 ``` # 克隆 repository git clone https://github.com/your-username/artemis.git cd artemis # 无需 pip install —— 零外部依赖 python main.py ``` ## 用法 ### 启动 Shell ``` python main.py ``` 这将启动交互式 Artemis shell: ``` artemis> ``` 如果项目根目录下存在 `banner.py` 文件并导出了 `BANNER` 字符串,它将在启动时打印。 ### 命令 | 命令 | 描述 | |----------|-------------------------------------------| | `scan` | 对目标主机运行端口扫描 | | `help` | 显示使用信息(来自 `help.py`) | | `clear` | 清除终端屏幕 | | `exit` | 退出 Artemis shell(也可使用:`quit`, `q`)| ### 扫描示例 **扫描主机上的默认端口范围 (1–1024):** ``` artemis> scan 192.168.1.1 ``` **扫描特定端口:** ``` artemis> scan example.com -p 22,80,443 ``` **以更高的并发数扫描自定义范围:** ``` artemis> scan 10.0.0.5 -p 1-65535 -c 1000 ``` **使用更长的超时时间扫描(适用于慢速网络):** ``` artemis> scan scanme.nmap.org -p 1-1024 -t 2.0 ``` **混合单个端口和范围:** ``` artemis> scan 172.16.0.1 -p 22,80,1000-2000,8080,9000-9100 ``` ### 扫描标志 | 标志 | 默认值 | 描述 | |-------------------------|-------------|---------------------------------------------------| | `target` | *(必填)* | 要扫描的 IP 地址或主机名 | | `-p`, `--ports` | `1-1024` | 要扫描的端口。支持范围和逗号分隔列表 | | `-c`, `--concurrency` | `500` | 最大同时 TCP 连接数 | | `-t`, `--timeout` | `1.0` | 将端口标记为 filtered 前等待的秒数 | ## 端口语法参考 Artemis 接受灵活的端口规范: | 语法 | 含义 | |---------------------|-----------------------------------| | `80` | 单个端口 | | `80,443,8080` | 多个单独端口 | | `1-1024` | 从 1 到 1024 的所有端口(包含首尾)| | `22,80,1000-2000` | 单个端口和范围的混合 | - 重复的端口会自动去重 - 超出有效范围 (1–65535) 的端口会被静默忽略 - 以逆序指定的范围(例如 `1024-1`)会自动校正 ## 架构与设计决策 ### 为什么使用 `asyncio`? 端口扫描本质上是一个 **I/O 密集型任务** —— 扫描的大部分时间都花在等待网络响应(或超时)上。传统的线程方法在同时激活数百个线程时,会因上下文切换引入显著的开销。 Python 的 `asyncio` 通过使用单线程 **event loop** 解决了这个问题,该循环可以协作式地管理数千个并发连接。Artemis 不会阻塞每个连接,而是在协程开始等待的那一刻将其挂起,并立即拾取另一个协程。与 `threading` 或 `multiprocessing` 相比,这带来了显著更高的吞吐量和更低的内存使用。 ### 为什么使用 `Semaphore`? 即使使用异步 I/O,打开过多的同时连接也会耗尽 OS 文件描述符限制或使目标网络不堪重负。`asyncio.Semaphore` 充当并发节流阀 —— 它允许最多 `N` 个端口同时进行连接,而其余的则排队等待。这使得在高并发水平下扫描既礼貌又稳定。 ### 为什么使用 `shlex.split()` 进行输入解析? 交互式 Shell 使用 `shlex.split()` 对用户输入进行分词,而不是简单的 `.split()`。这很重要,因为它能正确处理带引号的字符串(例如包含空格的主机名或参数),其行为与真正的 Unix Shell 分词命令的方式一致。如果 `shlex` 失败(例如引号格式错误),代码会优雅地回退到普通的 `.split()`,因此 Shell 永远不会因错误的输入而崩溃。 ### 为什么使用 `argparse` 处理扫描命令? 在交互式 Shell 中复用 `argparse` 使 `scan` 命令免费获得了专业级的参数解析能力——包括 `--help`、类型验证、默认值和错误消息——而无需编写自定义解析器。`_build_scan_parser()` 函数在启动时构建一次解析器,并在每次扫描调用中重复使用。 ### 为什么在 `asyncio.TimeoutError` 时重试? 单次丢包可能导致真正开放的端口显示为 filtered。Artemis 在将端口标记为 filtered 之前会进行 **两次连接尝试**。这是一个简单的启发式方法,可显著减少不可靠网络上的假阴性,而不会增加明显的开销,因为第二次尝试仅在第一次超时时运行。 ### 为什么仅使用标准库? 保持 Artemis 无依赖意味着它可以被放置在任何安装了 Python 3.8+ 的系统上并立即运行——无需 `pip install`,无需虚拟环境,无版本冲突。对于经常在可能限制安装软件包的环境中使用的工具来说,这是一个深思熟虑的设计选择。 ## 模块与文件结构 ``` artemis/ │ ├── main.py # Core application: shell loop, scan logic, argument parsing ├── banner.py # (Optional) Exports a BANNER string printed at startup └── help.py # (Optional) Exports a HELP string shown by the 'help' command ``` **`main.py`** 包含整个扫描引擎和交互式 Shell。可选的 `banner.py` 和 `help.py` 模块通过 `try/except ImportError` 在运行时加载,因此无论它们是否存在,Artemis 都能正确运行。 ## 函数参考 ### `validate_target(user_input: str) -> str` 在扫描开始前验证并解析用户提供的目标。 - 首先尝试使用 `ipaddress.ip_address()` 将输入解析为原始 IP 地址。 - 如果失败,则将输入视为主机名,并通过 `socket.gethostbyname()` 将其解析为 IP。 - 如果输入既不是有效的 IP 也不是可解析的主机名,则抛出带有描述性消息的 `ValueError`。 - 返回解析后的 IP 地址字符串,用于所有后续连接尝试。 ### `scan_port(ip, port, timeout, semaphore) -> Tuple[int, str, str]` 一个 `async` 协程,针对一个端口执行单次 TCP 连接尝试。 - 在连接前获取共享的 `semaphore`,确保并发数保持在配置的限制内。 - 最多尝试 **两次** 连接,以减少因瞬时丢包导致的“filtered”假阳性结果。 - 连接成功后,等待最多 **0.5 秒** 获取 banner —— 服务器发送的任何数据的第一行 —— 并将其与端口号和 `"open"` 状态一起返回。 - 如果两次尝试都超时则返回 `"filtered"`,如果连接被主动拒绝则返回 `"closed"`。 - Banner 文本解码为 UTF-8(带错误替换),去除空白字符,并截断至 80 个字符以保持输出可读。 ### `scan_target(target, ports, concurrency, timeout) -> None` 编排针对主机完整扫描的顶级 `async` 协程。 - 调用 `validate_target()` 解析目标,如果无效则提前退出并报错。 - 为每个端口创建一个 `scan_port` 协程任务,并使用 `asyncio.as_completed()` 提交所有任务,该函数会在结果完成时立即产出,而不是等待最慢的任务。 - 将结果跟踪到三个桶中:`open_ports`、`filtered_count` 和 `closed_count`。 - 对于每个开放端口,调用 `socket.getservbyport()` 查找传统服务名称(例如 `22 → ssh`,`80 → http`)。 - 所有任务完成后打印格式化的结果表,随后是显示计数和耗时的摘要行。 ### `parse_ports(port_str: str) -> List[int]` 将灵活的、逗号分隔的端口规范字符串解析为排序后的有效端口整数列表。 - 按逗号分割输入并单独处理每个段。 - 通过将连字符连接的范围(例如 `1000-2000`)扩展为完整整数集来处理;自动校正逆序范围。 - 使用 `set` 对所有端口进行去重。 - 过滤掉任何超出有效 TCP 范围 1–65535 的端口。 - 对于任何无法识别的段以及如果最终列表为空,抛出带有描述性消息的 `ValueError`。 ### `_build_scan_parser() -> argparse.ArgumentParser` 一个工厂函数,构建并返回 `scan` 命令的 `argparse` 解析器。 - 配置四个参数:一个必需的位置参数 `target`,以及可选的 `-p`/`--ports`、`-c`/`--concurrency` 和 `-t`/`--timeout` 标志。 - 在应用程序启动时调用一次,以便解析器对象可以在每次扫描调用中重复使用,而无需重新构建。 ### `main() -> None` 应用程序入口点和交互式 REPL 循环。 - 在 Windows 上,设置 `WindowsProactorEventLoopPolicy` 以在 asyncio event loop 中启用 TCP 连接,这是该平台所要求的。 - 尝试从 `banner.py` 导入并打印启动 banner。 - 运行无限 `while True` 循环,读取用户输入并根据命令的第一个标记分派到相应的处理程序。 - 优雅地处理 `KeyboardInterrupt` 和 `EOFError`(例如 `Ctrl+C` / `Ctrl+D`),并显示干净的退出消息。 - 未知命令打印有用的错误提示,引导用户使用 `help`。 ## 输出格式 成功的扫描产生以下格式的输出: ``` Artemis scan report for scanme.nmap.org (45.33.32.156) Scanning 1024 port(s) • concurrency 500 • timeout 1.0s PORT STATE SERVICE BANNER ────────────────────────────────────────────────────────── 22/tcp open ssh SSH-2.0-OpenSSH_6.6.1p1 80/tcp open http Not shown: 989 closed, 33 filtered port(s) Scan completed in 3.41s • 2 open port(s) found ``` - 表格中仅显示 **open** 端口;closed 和 filtered 端口在计数行中汇总。 - **BANNER** 列显示服务在连接时返回的第一行,这通常会揭示软件名称和版本。 - 结果按端口号数字排序以提高可读性。 ## 局限性 - **仅支持 TCP** — Artemis 仅执行 TCP 连接扫描。不支持 UDP 扫描。 - **无 OS 指纹识别** — 服务名称来自本地 OS 数据库,而非主动探测。 - **每次扫描单个目标** — 每个 `scan` 命令针对一台主机。目前不支持子网或 CIDR 范围扫描。 - **无输出文件** — 扫描结果仅打印到 stdout。如果需要,可以在 Shell 中使用 `>` 重定向来捕获它们。 ## 免责声明 Artemis 旨在用于**您拥有或已获得明确书面许可测试**的网络和系统。未经授权的端口扫描在您所在的司法管辖区可能是非法的,并且违反大多数网络的服务条款。作者不对滥用本工具承担任何责任。
标签:Banner Grabbing, DNS查询工具, Port Scanner, Python, TCP扫描, URL短链接分析, 二进制发布, 交互式Shell, 开源工具, 异步扫描, 插件系统, 无依赖, 无后门, 服务探测, 端口扫描器, 网络安全, 计算机取证, 轻量级工具, 逆向工具, 隐私保护