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, 开源工具, 异步扫描, 插件系统, 无依赖, 无后门, 服务探测, 端口扫描器, 网络安全, 计算机取证, 轻量级工具, 逆向工具, 隐私保护