CIS490/exploits
max 2707709299 Fix workload-silent false-positive on Alpine busybox guests (closes #15)
On-device agent (k-gamingcom) ran the diagnostic probe sequence and
proved the workload IS running on Alpine — yes saturating the vCPU,
loadavg=1.05, three yes PIDs visible — but two busybox incompatibilities
made every episode look silent:

1. _probe() used `pgrep -c yes`. The -c flag is procps-ng/util-linux,
   not busybox. busybox pgrep exits 1 with a usage banner; the
   `|| echo 0` fallback then reported yes=0 every time. Switched to
   `pgrep yes | wc -l` which both pgrep variants support.

2. _wrap_loop appended `disown` after the nohup-backgrounded script.
   busybox sh / ash have no disown builtin, so each infected_running
   phase printed `sh: disown: not found` into run()'s captured output.
   The script kept running (nohup gives SIGHUP immunity, which is
   what disown was for), but the spurious error is now gone.

Cross-validation in the classifier:
- prune_episodes.py: workload-silent now requires the probe AND
  host-side /proc CPU envelope (flat-cpu) to AGREE. A probe-only zero
  is treated as the busybox false-positive and dropped. This means
  the 244 already-on-disk episodes from elliott-thinkpad and
  k-gamingcom are correctly classified without re-collecting.

Test coverage:
- test_workload_silent_flag updated to require both signals
- test_workload_silent_suppressed_when_host_cpu_real new regression
  for the busybox false-positive

AGENTS.md gains a "Don't trust the in-guest probe alone" section with
the busybox-vs-procps gotcha + a list of busybox-incompatible patterns
to avoid in any new in-guest diagnostic.
2026-04-30 17:28:48 -05:00
..
modules Solvable Tier-3 holes: callback payloads, busybox workloads, bridge by default 2026-04-30 02:32:52 -05:00
__init__.py Tier 3: msfrpc-driven exploit driver + first module config 2026-04-29 23:11:52 -05:00
driver.py Close out the deployment-readiness gaps 2026-04-30 00:31:55 -05:00
modules.py Solvable Tier-3 holes: callback payloads, busybox workloads, bridge by default 2026-04-30 02:32:52 -05:00
msfrpc.py Tier 3: msfrpc-driven exploit driver + first module config 2026-04-29 23:11:52 -05:00
README.md Tier 3: msfrpc-driven exploit driver + first module config 2026-04-29 23:11:52 -05:00
workloads.py Fix workload-silent false-positive on Alpine busybox guests (closes #15) 2026-04-30 17:28:48 -05:00

exploits/

The Tier-3 exploit driver — fires a Metasploit module against a vulnerable target VM, watches for the resulting session, and stamps the session-open transition into the episode's events.jsonl so the labeler can mark armed → infecting honestly.

Layout

exploits/
  msfrpc.py           tiny msgpack-over-HTTPS client for msfrpcd
  driver.py           MSFExploitDriver — plugged in as EpisodeRunner.on_phase
  modules.py          ModuleConfig + TOML loader
  modules/
    vsftpd_234_backdoor.toml   first canned module (Metasploitable2)
    ...

Module configs

Each modules/*.toml describes one Metasploit module — its path, the options to set, and the payload to use. The driver reads these files to drive module.execute over msfrpc.

description = "..."
[module]
type = "exploit"                      # exploit | auxiliary | post
path = "unix/ftp/vsftpd_234_backdoor"

[module.options]
RHOSTS = "{{ target_ip }}"            # placeholder substituted at runtime
RPORT = 21

[payload]
path = "cmd/unix/interact"
[payload.options]                     # optional
# LHOST = "{{ target_ip }}"

[session]
type = "shell"

The only placeholder supported today is {{ target_ip }}. Add more in exploits/modules.py::ModuleConfig.render_options when needed.

Running

# 1. Start msfrpcd locally:
msfrpcd -P <password> -U msf -a 127.0.0.1 -p 55553

# 2. Drop a vulnerable target image at vm/images/<name>.qcow2 (e.g.
#    Metasploitable2 — see docs/sources.md for sha256).

# 3. Drive an episode:
MSFRPC_PASSWORD=<password> uv run python tools/run_tier3_demo.py \
    --module vsftpd_234_backdoor \
    --target-port 21 \
    --data-root data

The episode's events.jsonl will contain:

driver_setup        — module + target snapshotted before fire
exploit_fire        — module.execute issued
session_open        — new session id observed in session.list
session_landing_probe — first command response (id) recorded
sample_executed     — workload kicked off inside the session
session_dormant     — workload killed
session_killed      — session.stop at episode end

These pair with the standard phase labels in labels.jsonl so a downstream loader can reconcile "what the orchestrator scheduled" against "what actually happened on the wire".

Adding a module

  1. Drop a TOML at exploits/modules/<name>.toml per the schema above.
  2. Pick a payload that works without a callback channel until the br-malware bridge is in (see vm/launch_target.sh — SLIRP + restrict=on blocks reverse-tcp by design). cmd/unix/interact and other "session on the same socket" payloads are safe.
  3. Drive a quick check: uv run python tools/run_tier3_demo.py --module <name>.
  4. The new module is automatically picked up by tools/run_tier3_demo.py via --module <name>; no driver code changes needed.

We do not author exploits or modify upstream Metasploit code. The driver is a pure adapter from the project's phase machine to msfrpc.