grisuno/lazyown/apt-emulation
Claude Code and agent skill by grisuno/lazyown
- community
Checking that the SKILL.md file is still in the repository.
Emulate APT adversaries end-to-end in LazyOwn: research MITRE ATT&CK techniques, translate them to Atomic Red Team tests, build ordered emulation plans, and stage executable artefacts. Knows the resolver, the TTPScope bridge with its offline fallbacks, the MCP tools, and the CLI flow.
Install
npx vskill@latest install grisuno/lazyown/apt-emulationSKILL.md
/apt-emulation
Emulate a real adversary by chaining MITRE ATT&CK techniques into concrete, executable Atomic Red Team tests. Research is read-only; staging writes files under sessions/; nothing runs on a target without an explicit operator step.
Usage
A bare technique list means "build the emulation plan". The name of a playbook in playbooks/apt_*.yaml means "emulate that APT". For the whole thing as one command (scan unless cached, sweep, validate, plan, stage), run the chain instead of the steps below:
Chains are fully YAML-administered: chains/apt_chain.yaml holds the steps; per-APT files (chains/apt_rocke.yaml, ...) inherit it through extends: and only pin the playbook default. Manage them from the lazy_orchestrator Chains tab (preview steps, enable/disable, generate from playbook, delete) or by editing the files — the apt_chain command only interprets. The autonomous loop reaches the same chains through its apt_chain verb (auto apt_chain rocke <target> proposes from emulation goals, binds, and routes through the shell executor).
The pipeline (in order, skip nothing)
Research and planning never touch a target. Steps 1-4 are always safe to run. Step 5 executes on the target through the multiplexed session (lab and in-scope targets only). Step 6 opens an interactive REPL exclusively in a foreground terminal — loop workers always get verify-only mode instead. Step 7 soft-cleans by default; --hard destroys the gym container.
MCP equivalents: lazyown_ttp_chain (plan), lazyown_apt_chain (full chain), lazyown_atomic_shell (verify-only check), lazyown_atomic_clean (soft; hard needs confirm=true).
Step 1 — Research the technique
Prefer the MCP tool when available, CLI otherwise. Both answer even when the TTPScope C binary is missing (result carries backend: fallback-*).
First run on a fresh checkout needs ttpscope action=sync once to cache the STIX bundle at ~/.cache/ttpscope/enterprise-attack.json. Without it, show covers techniques (T*) from parquets/ but not groups (G*), software (S*), or mitigations (M*); those return an attack.mitre.org link.
Never run ttpscope action=tui from an agent. It needs an interactive TTY.
Step 2 — Resolve MITRE IDs to Atomic tests
One MITRE technique maps to N Atomic tests. The GUIDs (UUIDs) identify single tests; the T#### codes identify techniques. Translation lives in modules/atomic_resolver.py — the single source of truth. Nothing else in the repo should parse Atomic YAML directly.
Rules: validate every ID before use (is_mitre_id / is_guid); an unknown technique resolves to an empty list, never an exception inside chain; a bare parent such as T1059 expands to its sub-techniques.
Step 3 — Plan the chain
lazyown_ttp_chain takes an ordered technique list and returns one platform-compatible test per technique plus a staged JSON plan at sessions/ttp_chain_<platform>_<ts>.json. For named APTs, use the playbook flow instead:
attack_plan <file> --auto executes immediately. Treat --auto like a loaded weapon: only with operator approval.
Step 4 — Stage artefacts
Output lands in sessions/temp_uploads/ as atomic_test_<id>.sh|.ps1 plus a cleanup twin, mode 0600, variables (#{ip_address}, #{username}, ...) pre-filled from live shell params with declared test defaults as fallback. Staging copies commands to the clipboard when available; clipboard failure never aborts.
Step 5/6 — Emulate and clean
Hand the staged scripts to the operator with, per step: technique ID, MITRE URL, test name, prereq commands, main command, cleanup command. After execution, run the cleanup twin and confirm the artefact state.
Live C2 sessions (execution that the loop can drive)
Staged scripts need a live target to run on. For lab targets with a root shell primitive, the loop deploys a malleable agent and operates it entirely through shell commands — no interactive prompts anywhere:
Every step feeds the dashboard through modules/c2_surfaces.py (banners, CVEs, notes, loot event rules) — the same surfaces the operator sees at /banners, /cves, /notes, /tasks:
- exploit sweep matches publish service banners; shells and CVEs land
on every attempt via record_outcome;
- loot appends a timestamped note and registers an event rule;
- the chain ends with
create_session_jsonso/reportrenders fresh; /tasksgroups identical titles (×N badge) and tallies filtered
noise; the daemon no longer creates timestamp/URL bogus credential tasks nor duplicate open titles at the source.
Mechanics to remember next session:
- Client ids are
msf-<target-with-dashes>(client_id_for). The C2
queue file sessions/cmd_<id>.json persists across server restarts — deploy truncates it so stale commands never replay.
- Answers land in
sessions/<client_id>.records.jsonl(one JSON per
line: command, output, user, hostname). wait_record polls it.
- The AES key is owned by the server (
sessions/key.aeswhile running)
and snapshotted to sessions/c2_lab.json on boot. Tooling NEVER generates keys: a rotated key bricks deployed agents (garbage commands in, HTTP 500 out). After any C2 restart, redeploy agents.
- The gym runs Python 2.5 with no
sslmodule: the agent speaks plain
HTTP (lab listener, e.g. :8080) and does AES via the openssl CLI. Never use timeout= kwargs or except...as in agent code. os.closerange does not exist on 2.5 either — close fds one by one.
- Deployed agents MUST drop inherited fds at startup (close 3..255,
stdin from /dev/null). An agent born in a backdoor shell otherwise inherits its LISTEN socket and wedges the target port permanently — every later trigger fails and only killing the agent (or restarting the target) recovers it.
- Single-shot backdoors (vsftpd :21/:6200) serve effectively one shell
per trigger: one trigger, one persistent multiplexed session, clean exit on close. Never stack connections; never abandon sessions.
- Beacon poll/result routes are limiter-exempt (AES already
authenticates); the global 200/hour limit would otherwise 429 agents polling every 10s — and the generic error handler masks 429s as 500s, which once cost a full debugging session. Poll at 25s+ regardless.
issue_command_to_c2/download_file_from_c2fail loudly instead
of prompting when stdin is not a TTY — always pass explicit client ids.
- Lab C2 creds live in
sessions/c2_lab.json, never inpayload.json.
The human-visible .c2_credentials.txt always holds the RUNNING server's creds: c2_server start rewrites it on every boot, and the shell adopts it at startup (_sync_c2_credentials, which must run AFTER self.params exists — a past ordering bug made it dead code that swallowed its own AttributeError). If login fails, compare the file against c2_server status (live_user): different values mean the server was started outside the manager with other creds.
payload.jsonrequired keys must never go null:assignrefuses
null/blank on validation errors, MCP set_config refuses blanks, and shell-less auto_populate refuses to save (it once truncated the file to 2 keys). If [payload] ERROR ... required but missing appears at boot, restore via assign and check sessions/config_changes.jsonl to identify the writer. assign redacts schema-sensitive values instead of echoing secrets.
- Tests must NEVER write the real
payload.json: patch
core.config.save_payload (where function-level imports look it up), never the caller's namespace, and hash-check the file before/after when in doubt. A wrongly-placed monkeypatch once truncated the operator's payload to 4 keys mid-session.
- The full chain already includes these steps (
chains/apt_chain.yaml):
apt_chain rocke 172.17.0.2 runs assign → scan → sweep → c2_server → c2_deploy → c2_loot shadow → validate → plan → stage → emulate.
Enrichment sources (read-only, always available)
| Source | MCP tool | Contents | |
|---|---|---|---|
| Atomic catalogue (1690 tests) | lazyown_atomic_search | keyword + mitre_id + platform + scope + complexity + prereq filters | |
| MITRE techniques | lazyown_parquet_query | mode="keyword" / "context" over parquets/techniques*.parquet | |
| Session history | lazyown_parquet_query | mode="session" — what already worked against this target | |
| Phase briefing | lazyown_parquet_query | mode="context" — history + GTFOBins + MITRE for one phase |
Check sessions/scan_<rhost>.nmap* and the world model before planning. Never re-scan what is already known. Never invent technique IDs: if resolve returns empty, say so and suggest the closest prefix via lazyown_atomic_search.
Module map
| File | Role | |
|---|---|---|
modules/atomic_resolver.py | MITRE <-> GUID translator + chain(). No shell, no network. | |
modules/atomic_cli.py | Secure atomic_tests / atomic_gen implementation. Non-interactive safe. | |
modules/ttpscope_bridge.py | TTPScope wrapper with STIX/parquet/Markdown fallbacks. No hard binary dependency. | |
modules/playbook_engine.py | Phase-aware playbook derive/run loop (STIX tactics -> Atomic tests). | |
lazyaddons/ttpscope.yaml | Research client as a first-class LazyOwn command. | |
cli/commands/command_and_control_migrated.py | Thin do_atomic_tests / do_atomic_gen wrappers. No logic here. |
Known upstream gaps (not stale clones)
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 90 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingskills/apt-emulation/SKILL.md
Popularity
- Repository stars
- 301
- Repository forks
- 63
- 7-day trend score
- 0
Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/grisuno/lazyown/apt-emulation)