ldesfontaine/phantom-ir
GitHub: ldesfontaine/phantom-ir
Phantom-IR 是一款基于 SSH 的 Linux 应急响应分拣工具,通过无 agent 方式实时采集并关联进程、网络和系统信息,快速检测异常并导出取证数据。
Stars: 0 | Forks: 0
# Phantom-IR
应急响应 (IR) 分拣工具:通过 SSH 连接到可疑的 Linux 机器,
实时收集进程、网络连接并进行被动侦察,
自动关联和检测异常,并在终端 TUI 中显示。
**目标**:在 30 秒内从告警转化为全面了解,
同时尽量减少对受分析机器的修改和留下的痕迹。
**对目标系统的痕迹极小** — 不安装任何 agent,不存放任何由 Phantom 创建的文件,也不打开任何额外端口。收集过程通过 SSH 和标准命令完成;但这可能会出现在身份验证或审计日志中。
## 安装说明
```
# 克隆并编译(需要 Go ≥ 1.21)
git clone https://github.com/ldesfontaine/phantom-ir
cd phantom-ir
make build
# → bin/phantom(静态二进制文件,无系统依赖)
# 交叉编译:Linux 和 macOS,amd64 和 arm64
make cross
# → bin/phantom-linux-amd64, bin/phantom-darwin-arm64 等
```
## 快速使用
```
# 通过 ~/.ssh/config 配置文件连接
phantom web-srv-03
# 直接连接
phantom admin@192.168.1.10
# 使用非标准端口或显式 SSH 密钥
phantom admin@192.168.1.10:2222
phantom -i ~/.ssh/id_ed25519_prod admin@192.168.1.10
# 欢迎屏幕:SSH 配置文件列表 + 最近的会话
phantom
```
### Headless 模式 (脚本、SOAR、CI)
```
# 导出 ECS JSON 而不打开 TUI
phantom --headless web-prod --export ecs
# 可用格式:ecs | timesketch | report | all
phantom --headless 192.168.1.10 admin 22 --export all
# → 创建的文件路径输出到 stdout
```
### Replay 与 diff
```
# 重播已保存的快照(只读,相同的 TUI)
phantom --replay ~/.local/share/phantom/exports/web-srv-03_2025-01-15_ecs.json
# 比较两个时间点的快照
phantom --diff avant.json,apres.json
# → 出现/消失的进程,更改的连接,新增/已解决的异常
```
## Snapshot — 核心概念
### 为什么需要 Snapshot?
当 Phantom 连接到一台机器时,它会在几秒钟内收集数以万计的
原始数据:数百行 `/proc/$pid/status`,
数千条 `/proc/net/tcp` 条目,以及数以千计的 FD 符号链接。
这些原始数据**彼此之间缺乏关联**。一个 `/proc/net/tcp`
文件显示“inode 为 55555 的 socket 处于 ESTABLISHED 状态,连接到 1.2.3.4:4444” — 但它
并不知道这个 inode 属于哪个进程。
**Snapshot** 正是为了解决这个问题:它是在 T 时刻对机器所有状态的一个统一、关联
且索引化的视图。
### 工作原理
```
┌────────────────── Pipeline de données ──────────────────────────────┐
│ │
│ SSH → /proc ──► Normalizer ──► Builder (3 passes) ──► Snapshot
│ (données brutes) (structs Go) (corrélation + index) (vue unifiée)
│ │
│ Tout est reconstruit à chaque tick (toutes les N secondes). │
│ La TUI ne fait jamais de collecte directe — elle lit le Snapshot. │
└──────────────────────────────────────────────────────────────────────┘
```
**阶段 1 — 进程索引**:每个 `ProcessInfo` 都通过 PID 进行索引。
提取出的 SocketRefs(已打开 socket 的 inode)将用于下一阶段。
**阶段 2 — 连接扩充**:每个网络连接
通过 inode 绑定到其对应的 PID。`inode → PID` 的映射
只计算一次(每个 tick 仅对 `/proc/$pid/fd/` 下的 FD 扫描一次)。
**阶段 3 — 异常检测**:所有规则(内置 + 自定义 YAML)
都会遍历扩充后的 Snapshot 并生成 `Anomaly`。
**结果**:Snapshot 包含用于 O(1) 访问的**预计算索引**:
```
Snapshot {
Processes map[int]*ProcessInfo // PID → processus
Connections []NetConn // toutes les connexions
Anomalies []Anomaly // détections automatiques
NetRecon *NetRecon // interfaces, ARP, routes, DNS
Logs []LogEntry // auth.log, journalctl (M9)
// Index de corrélation (O(1) sans re-scan)
PIDToConnectionIndexes map[int][]int // PID → ses connexions
ServiceToPIDs map[string][]int // service → ses PIDs
UserToPIDs map[string][]int // user → ses PIDs
}
```
### 为什么需要预计算索引?
在交互期间,TUI 每秒会重绘 30 次以上。如果每次
显示都要重新遍历所有连接才能找到属于某个 PID 的连接,
在拥有 500 个进程和 2000 个连接的服务器上,界面会变得非常缓慢。
借助 `PIDToConnectionIndexes`,在 Focus 视图中显示某个进程的连接
只需简单地读取 slice — 完全不受总连接数的影响。
### 过滤后的 Snapshot — 定向导出
Snapshot 可以针对特定的 PID 子集进行**过滤**:
```
FilterSnapshot(snap, pids []int) → *Snapshot
```
用于从 TUI 进行定向导出:
- 在 Focus Service 模式下按 `[e]` → 仅导出 `nginx.service` 及其连接
- 在导出面板中按 `[a]` → 仅导出存在异常的 PID
过滤后的 Snapshot 在结构上与完整的 Snapshot 完全相同 — 同样
支持所有导出格式(ECS、Timesketch、Markdown),无需进行任何修改。
## 键盘快捷键
### 全局导航
| 按键 | 动作 |
|-------------------|---------------------------------------------|
| `j` / `↓` | 在列表中向下移动 |
| `↑` | 在列表中向上移动 |
| `g` / `G` | 跳转至列表顶部 / 底部 |
| `Ctrl+D` / `Ctrl+U` | 向下 / 向上翻页 |
| `Tab` / `Shift+Tab` | 下一个 / 上一个标签页 |
| `1`–`4` | 直接访问标签页 (QWERTY 键盘) |
| `&` `é` `"` `'` | 直接访问标签页 (AZERTY 键盘 — 配置) |
| `/` | 内联搜索过滤器 |
| `r` | 强制立即刷新 |
| `?` | 上下文帮助 (覆盖层) |
| `q` / `Ctrl+C` | 退出 |
### Focus 视图 (选定的进程)
| 按键 | 动作 |
|----------|--------------------------------------------------|
| `Enter` | 打开 Focus PID (完整详情) |
| `s` | Focus Service — 同一服务的所有 PID |
| `u` | Focus User — 同一用户的所有 PID |
| `Esc` | 从焦点视图返回 |
| `k` | 向进程发送 SIGTERM (优雅停止) |
| `K` | 向进程发送 SIGKILL (强制终止) |
| `e` | 打开导出面板 |
### 导出面板
| 按键 | 动作 |
|--------|-------------------------------------------------------------|
| `1`–`4` | ECS / Timesketch / 报告 / 全部格式 (当前范围) |
| `5` | 完整 Snapshot (如果启用了某个焦点) |
| `a` | 仅导出异常 (触发检测的 PID) |
| `Esc` | 取消 |
## TUI 标签页
| 标签页 | 内容 |
|------------|---------------------------------------------------------------|
| Processus | PID、PPID、user、command、state、service — 根据严重程度着色 |
| Réseau | TCP/UDP 连接:本地、远程、state、PID、service |
| Anomalies | 包含严重程度、规则、描述的检测结果 |
| Recon | 接口 (IP, MAC, MTU)、ARP、routes、DNS、静态主机 |
## Phantom 收集的信息
收集过程不会安装任何 agent,也不会在目标系统上存放任何文件。
它通过 SSH 执行必要的标准读取和命令;这些
会话可能会出现在身份验证或审计日志中。
### 进程
- PID、PPID、user、完整 command、state (running/sleeping/zombie)
- 启动时间 (`stat -c '%Y' /proc/$pid`) — 有助于检测新启动的进程
- 关联的 service (通过 systemd 或 docker 的 cgroup)
- 打开的 Socket 和打开的文件 (通过 `/proc/$pid/fd/`)
### 网络
- 来自 `/proc/net/` 的 TCP/TCP6/UDP/UDP6 连接
- 通过符号链接 `/proc/$pid/fd/` 关联 socket → PID (每个 tick 仅扫描一次)
- 连接状态 (ESTABLISHED、LISTEN、TIME_WAIT 等)
### 被动侦察
- 网络接口:IP、掩码、MAC、MTU、state (`ip addr` + `ip link`)
- ARP 表 — 网络邻居 (`ip neigh`)
- 路由表 (`ip route`)
- DNS 解析器 (`/etc/resolv.conf`)
- 静态主机 `/etc/hosts` — **在 IR 中高度相关**:对 `/etc/hosts` 的修改
(例如:将 `update.microsoft.com` 重定向到攻击者的 IP) 是系统被入侵的指标
### 无 root 访问权限时
收集过程会平稳降级:无法读取其他
用户的 FD,但 TUI 会显示当前可用的所有信息。
覆盖率百分比会显示在状态栏中。
## 异常检测
Phantom 内置了相关规则,并支持以 YAML 格式自定义规则。
这些规则会在每个 tick 对完整的 Snapshot 执行检测。
### 内置规则
| 规则 | 严重程度 | 描述 |
|------------------------|----------|--------------------------------------------------|
| `exec_from_tmp` | critical | 从 /tmp、/dev/shm、/var/tmp 执行二进制文件 |
| `reverse_shell` | critical | 指向低端口的出站连接 (< 1024) |
| `process_no_service` | warn | 没有 systemd/docker 服务的网络进程 |
| `suspicious_uid0_cmd` | warn | 带有非标准路径的 root 命令 |
| `orphan_process` | warn | 没有已知父进程的进程 (找不到 PPID) |
| `non_standard_port` | warn | 监听不常见的端口 |
| `access_sensitive_file`| warn | 打开敏感文件 (/etc/shadow, SSH 密钥…) |
### 自定义规则 (YAML)
创建 `~/.config/phantom/rules.yaml`:
```
rules:
- id: crypto_miner
severity: critical
category: process
description: "Miner de crypto-monnaie détecté"
match:
command_contains: "xmrig"
- id: external_ssh_from_web
severity: warn
category: process
description: "SSH sortant depuis un processus web"
match:
command_contains: "ssh"
user_equals: "www-data"
disabled_rules:
- process_no_service # trop bruyant sur ce serveur
```
一个 `match` 块中所有非空的条件都必须为真 (逻辑与)。
## 导出
在 TUI 中,按 `e` 可打开导出面板。在 Focus 模式下,导出会
自动定向到当前上下文。
| 格式 | 文件 | 用途 |
|--------------|----------------------------|------------------------------------------|
| `ecs` | `hostname_date_ecs.ndjson` | Elastic Common Schema → Kibana, Sentinel |
| `timesketch` | `hostname_date_ts.jsonl` | Timesketch → 取证时间线 |
| `report` | `hostname_date_report.md` | Markdown 报告 → Jira, TheHive |
| `all` | 全部 3 种格式 | 一次性完整导出 |
文件将创建在 `~/.local/share/phantom/exports/`
(或 `$XDG_DATA_HOME/phantom/exports/`) 目录下。
### 定向导出 (M8)
```
# 在 TUI 中,使用 Focus nginx.service 模式:
[e] → panneau d'export
[1]–[4] → exporte seulement les 3 processus nginx + leurs connexions
[5] → exporte le snapshot complet
[a] → exporte uniquement les PIDs avec anomalies (toute la session)
```
## 会话持久化
每个会话都会自动保存在
`~/.local/share/phantom/sessions/` 目录下:
```
web-srv-03_2025-01-15_0234/
metadata.json # cible, durée, statut, nombre d'anomalies détectées
latest.json # dernier snapshot complet (écrasé à chaque tick)
session.jsonl # journal d'événements : connexion, kill, export, perte de connexion
```
最多保留 10 个会话 (自动轮转,会删除最旧的会话)。
如果 SSH 连接丢失,`latest.json` 将包含最后已知的状态。
TUI 提供以下选项:`[r]` 重新连接,`[e]` 导出此最后的 snapshot,`[q]` 退出。
## 配置
将 `configs/phantom.yaml` 复制到 `~/.config/phantom/phantom.yaml`:
```
refresh_interval: 5s # Intervalle de rafraîchissement (défaut : 5s)
log_level: warn # Niveau de log : debug, info, warn, error
ssh:
connect_timeout: 10 # Timeout connexion SSH (secondes)
command_timeout: 25 # Timeout commandes (secondes)
kill:
enabled: false # Désactivé par défaut — activer explicitement en mission
default_signal: TERM # TERM (graceful) ou KILL (force)
ui:
azerty: false # true → &/é/"/` pour basculer entre les onglets
```
## 架构
```
cmd/phantom/ → point d'entrée CLI (cobra) : TUI / headless / replay / diff
internal/
config/ → chargement phantom.yaml + parsing ~/.ssh/config
connector/ → interface Collector, SSHPipeCollector, probe, résilience SSH
normalize/linux/ → parseurs /proc (processus, réseau, recon)
correlate/ → Builder 3 passes → Snapshot indexé + moteur d'anomalies
diff/ → comparaison entre deux snapshots
models/ → types partagés (ProcessInfo, NetConn, Anomaly, NetRecon…)
store/ → persistance sessions (metadata.json, latest.json, session.jsonl)
export/ → formats ECS, Timesketch, Markdown
ui/ → TUI Bubble Tea : onglets, focus PID/Service/User, styles
log/ → zerolog structuré (stderr + fichier JSON rotatif)
configs/phantom.yaml → configuration par défaut
tests/ → tests d'intégration Docker (6 scénarios)
```
### 基本不变量
这允许您添加新的收集器 (Windows、临时 agent),而
无需修改任何一行 TUI 代码。
## 安全性
- 不安装任何 agent,不遗留任何文件,且不在目标上打开任何额外端口
- SSH 连接和命令可能会出现在身份验证或审计日志中
- 会话以 `0700`/`0600` 权限存储 (其他用户无法读取)
- kill 信号在执行前会根据 POSIX 白名单进行验证
- 与 SSH agent 的连接在身份验证后会干净地关闭
## 测试
```
# 单元测试(快速,无外部依赖)
make test
# Docker 集成测试(需要 Docker)
make test-integration
```
集成测试会启动 6 个独立的 Docker 容器:
| 场景 | 描述 | 标准 |
|----------|--------------------------------------|------------------------------------|
| 01 | 基准 — 健康的系统 | 创建 ECS 导出,包含进程 |
| 02 | 可疑进程 | 导出中包含 ≥ 5 个进程 |
| 03 | 无 root — 平稳降级 | 无崩溃 (exit 0) |
| 04 | 受限 shell — 缺少工具 | 无崩溃 (exit ≠ 2) |
| 05 | 高负载 (100+ 个进程) | 收集在 < 30 秒内完成 |
| 06 | systemd 服务关联 | 创建导出 |
## 依赖项
| 库 | 用途 |
|-----|-------|
| [Bubble Tea](https://github.com/charmbracelet/bubbletea) | TUI 框架 MVU (Model-Update-View) |
| [Lip Gloss](https://github.com/charmbracelet/lipgloss) | 终端样式 |
| [Cobra](https://github.com/spf13/cobra) | CLI |
| [golang.org/x/crypto/ssh](https://pkg.go.dev/golang.org/x/crypto/ssh) | 纯 Go 实现的 SSH 客户端 |
| [zerolog](https://github.com/rs/zerolog) | 结构化 JSON 日志 |
| [gopkg.in/yaml.v3](https://pkg.go.dev/gopkg.in/yaml.v3) | 解析 YAML (配置 + 规则) |
标签:EVTX分析, 内存分配, 日志审计