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分析, 内存分配, 日志审计