A pwn/rev-focused, environment-agnostic, self-contained CTF solving kit. Tools, doctrine, knowledge, and reference data all live in one repo — set it up once on any Linux environment (native / VM / WSL2 / container) and Claude · Codex can start solving challenges right here.
📑 Table of Contents
# 0. One-time environment setup (venv+angr+pwntools, Ghidra, glibc-fetch) → SETUP.md
# 1. Lock the target engagement (allowlist / active lock)
ctfguard begin <challenge> [target]
# 2. Route — project evidence dimensions and bounded candidate probes
rat route <bin>
# 3. Run one bounded discriminator; use full recon/revq only when evidence says it is needed
rat query pwn <bin> # example pwn capability query
rat query func <bin> <function> # example rev function cardflowchart LR
A["📥 newchal / ctfpull<br/>collect artifact"] --> B["🧭 rat route<br/>signals · dimensions · candidate probes"]
B --> D["🎯 run one bounded discriminator<br/>recommend only on clear dominance"]
B --> E["🔍 preserve coexisting leads and unresolved premises"]
D --> F["🧪 rat-verify / concrete-verify"]
E --> F
F --> G{"PASS?"}
G -->|no, DEEP condition| H["🔬 lazy-load doctrine<br/>inherit STATE · cache · current evidence"]
H -->|next discriminator| D
G -->|yes| I["📦 pkshare → HANDOFF.md<br/>knowledge/learned/"]
style A fill:#264653,color:#fff
style B fill:#2a9d8f,color:#fff
style D fill:#e76f51,color:#fff
style E fill:#e76f51,color:#fff
style F fill:#e9c46a,color:#111
style H fill:#6d597a,color:#fff
style I fill:#43aa8b,color:#fff
Note
DEEP-only doctrine — SOLVING (FAST evidence handoff + bounded convergence loop) · SOLVABILITY · PRIMITIVE_GATE · FINALS
Benchmark corpus, ablations, and design-review docs live on the dev branch. main ships only the operational tools needed to actually solve and verify challenges.
CLAUDE.md / AGENTS.md agent entry point (symlink)
SETUP.md environment-agnostic initial setup
doctrine/ SOLVING · SOLVABILITY · PRIMITIVE_GATE · REFUSAL · FINALS
docs/ calibration (로컬 누적 로그, 릴리스는 빈 템플릿 — 오염 상수는 doctrine 밖)
knowledge/ vendored pwn/rev knowledge + repo-owned learned/ + writeup pipeline
reference/ libc-offsets/ · glibc/(list · SOURCES · glibc-fetch)
bin/ all tools (+ghidra_scripts/)
solve/_template/rev/ symsolve · vmlift
kernel/ kernel pwn extensions
tests/ e2e_mock.py(ctfpull) · e2e_rev.sh(rev loop)
rat — the single front-door dispatcher
rat route <bin> projects observations into independent dimensions and returns evidence-linked bounded candidate probes (a thin composition of rat-doctor + rat-profile + revq,
no new analysis). Static routing leaves skill=null; after an active derived/direct STATE observation confirms the method, rat route <bin> --skill <name> --skill-evidence <obs_id> --skill-reason <reason> records the committed Skill in the existing route-assessment note. It emits a recommendation only when one probe clearly dominates the alternatives on deterministic evidence/cost criteria, and rat query {graph,func,oracle,pwn,pattern,slice} · rat dyn|verify · rat state compact ·
rat cache stats expose everything through one entry point. Existing CLIs (revq / recon / etc.)
still work standalone.
Each routed probe has a stable ID bound to the binary digest and expected evidence.
rat query func, rat query oracle, and rat query pwn save hash-checked result
artifacts with active STATE observations. A later rat route consumes matching
results: a complete checker card with comparison sites and both oracle branches
retires that probe, while partial or invalidated evidence keeps it pending with
an explicit reason and suspends automatic re-query. After rat route BIN →
rat query pwn BIN → rat route BIN, a stack candidate advances to a local
pwncrash control measurement; format and heap candidates advance to one
bounded decomp BIN CALLER inspection. A caller named by revq is selected
when available; otherwise the placeholder in next.target must be replaced
with a recovered caller. Independent leads can leave decision=null; choose
one of the listed experiments. Import-only PWN cards never prove a safe or
vulnerable callsite.
An active route.probe.refutation observation can refute an exact probe; for
bounded read/fgets input callsites the current validator has no independent
exhaustive inventory, so it does not accept a safe-callsite refutation.
rat query pattern <bin> returns at most three source-bound hypothesis aids for
coexisting Router v2 leads. rat dyn session optionally keeps one local GDB
process for sequential observations; see GDB session usage.
ctfpull — pull CTFd challenges into a newchal scaffold
Never auto-submits flags (ToS + honest-mode).
ctfpull ctfd --list [--category pwn]
ctfpull ctfd --id 42 [--dest DIR] # download → extract → detect ELF → run.json → newchal기본 수집 경로는 solve/<name>/artifact/이며, 생성된 풀이 파일과 제공 원본이
solve/<name>/ 한 곳에 모인다. --dest DIR을 명시하면 기존처럼 DIR/<name>/에
스테이징한다.
Config precedence: CLI > env vars (CTFD_URL / CTFD_TOKEN) > dotenv (--env, default ./.ctfd.env).
🔍 rev loop
revq <bin> # summary + INTERESTING (check-routine candidates) + EVASION (anti-debug/packing)
revq <bin> --func <name> # single-function neighborhood card (saves context before decompiling)
decomp <bin> <func> # Bash executable: Ghidra headless decompile cache
mod2solve system.json # solve matrix @ x = target modulo 2**bits; verify against original inputs
symsolve.py <bin> --find-str Correct --stdin 16 --printable # recover input, re-run the real binary to verify
vmlift.py --disasm|--run|--solve [blob] # custom VM lifter[!TIP]
revqaddresses = angr's load base (PIE0x400000) → feed them straight intosymsolve --find <addr>.
decomp is a Bash script: invoke bin/decomp <bin> <func> directly or with
bash bin/decomp <bin> <func>. Python tools such as revq accept
python3 bin/revq ...; python3 bin/decomp ... does not. Bad-instruction output
from a cached function is marked partial with a bounded objdump diagnostic;
compare revq's mapped address with the ELF VMA before disassembly.
For WebAssembly core modules, rat route and revq expose a bounded section
inventory. Use rat-wasm inspect BIN --func INDEX for one function body digest
and rat-wasm verify BIN --stdin-file INPUT --expect-stdout LITERAL for an
independent Wasmtime execution (optional setup in SETUP §9). This does not
promote a solve or primitive. Use rat-capture --limit N -- TOOL ... instead
of piping tool output through head when the original exit code matters.
For Linux boot images, newchal skips generic ELF recon and rat route reports
the kernel identity and the prerequisite to extract ELF vmlinux using Linux
scripts/extract-vmlinux. Symbol-bearing kernels use bounded ELF metadata in
revq (including executable NOTYPE symbols), without a whole-kernel CFG.
This establishes artifact identity only; boot environment and primitives still
require evidence. Ghidra imports of kernels or files at least 16 MiB require
decomp --allow-full-analysis --timeout SEC BIN [FUNC]; cached function reads
remain available. Initial import analyzes and exports the whole program.
rat-adapt --root names the artifact store, normally <challenge>/.rat.
Omitting it uses ./.rat. A challenge root containing .rat is rejected before
execution with the corrected store path, so evidence cannot silently land in a
different store from STATE.
mod2solve is a Python executable with no solver dependency. Its JSON input is
{"bits":8,"matrix":[[3,2],[0,1]],"target":[19,5]}. Run
python3 bin/mod2solve system.json; it returns a solution vector, solution_hex
for byte-sized moduli, the original inputs and file digest, and an independent
matrix @ solution check. Status distinguishes unique, multiple, and
inconsistent; exit codes are 0 for a solution, 1 for no solution, and 2 for
invalid input. Even pivots and rectangular systems are supported (up to 256
rows/columns and 64 modulus bits). For a non-unique system, one representative
is returned; byte/printable constraints are not imposed. This verifies the
equation only: validate the recovered input with the challenge's concrete
verifier before recording solve evidence.
🎯 pwn
pwnkit / pwnstage / primitives · pwncalc / pwnleak / pwnpayload / pwnropcheck / pwncrash / pwnscope / pwnclean / pkflag
pkflag checks format only; REAL is not proof of executable-oracle success.
By default pkflag uses solve/<active-challenge>/FLAG_FORMAT (from ctfguard's ACTIVE.json, or the current solve/<challenge> directory). A stale repository-global FLAG_FORMAT is not used implicitly. With no challenge format, a generic NAME{...} heuristic applies.
ctfguard begin example
pkflag --set 'CTF\{[A-Za-z0-9_]+\}' # set this challenge's format
pkflag --show # show this challenge's format
pkflag 'CTF{example_candidate}' # format-only check
pkflag --global --show # explicitly inspect legacy global formatUse --chal example to select a challenge explicitly. --global --set is available only for deliberate legacy-global configuration; pkselftest no longer writes the repository setting.
➕ and more
- Environment / execution plan —
rat-doctor <bin> --format jsonshows which paths (native/GDB/angr/Ghidra/QEMU/Qiling/Wine) actually work for this artifact and why others are blocked. Regression testing is handled separately bypkselftest. - Reproducible scenarios —
rat-scenario init|validate|shownormalizes the input/argv/env/oracle shared byrat-dyn/rat-runtime/rat-verify. Binary stdin is preserved via--stdin-file. - State bus —
state(record confirmed / ruled-out / next) · kernel —k_*(kernel/). - Internal schema test helper —
ratlib.schema_docsextracts validator field contracts fortests/test_schema_docs.py; it is not part of runtime STATE validation. - Handoff & knowledge —
pkshare→HANDOFF.md;writeupcheckquality gate → reviewed lessons land inknowledge/learned/. Typed STATE v2 takes precedence; legacy PASS entries are shown only as candidates. Completion docs require an operator attestation linked to an evidence digest.
python3 bin/revq selftest
python3 bin/rat selftest
python3 solve/_template/rev/symsolve.py selftest
python3 solve/_template/rev/vmlift.py selftest
python3 bin/ctfpull selftest && python3 tests/e2e_mock.py
python3 -m unittest tests.test_writeup_pipeline
bash tests/e2e_rev.sh # real crackme e2e if angr is installed, selftest only otherwise
bash tests/e2e_rev.sh --require-engine # release/CI gate: missing native angr is failureRelease verification requires --require-engine and ALL GREEN ✅; the optional
selftest-only run is reported as partial coverage.
Important
One active challenge at a time · fan-out only inside a challenge · delegate large reads to a subagent and pull back conclusions only · typed .rat/events/STATE.v2.jsonl is the canonical state stream (STATE.jsonl is legacy inspection/import only) · no reinventing what an existing tool already does.
Made for the CTF grind · route → verify → reuse

