DOCUMENTATION / PART 4 — THE CATALOGUE4.2

4.2 SYSTEM — five modules about the box

These are the modules that answer questions about the machine rather than about the network: what is installed, what is plugged in, whether the bench is honest, and how to run everything else in a stated order. They are the category to read first, and the category to re-read when a result looks wrong, because four times in five the wrong result is a wrong bench.

4.2.1 00_deps — Dependency Manager

IMSISting is a shell toolkit over external programs, and this module is the inventory of those programs. Its table is the single source of truth for "what can this box do": each entry names a tool, how to probe it, and what its absence means for the modules that need it.

What makes the inventory worth reading is that detection is by use, not by presence. A gnuradio that cannot load the gr-gsm out-of-tree blocks, an srsRAN with no application binary, a Python module that will not import — each is reported broken, never installed. A file on the PATH proves only that a file is on the PATH.

modules/00_deps.sh --preflight        # radio, capture paths and gates in one page
modules/00_deps.sh --status-tsv       # one row per registered tool
modules/00_deps.sh --missing-only     # only what is not usable
modules/00_deps.sh --check-versions   # present / absent / broken, with versions
modules/00_deps.sh --report           # write loot/deps/deps_report.tsv
modules/00_deps.sh --install-missing  # distro packages for what is missing
modules/00_deps.sh --install-pip      # the Python side

What it keeps: loot/deps/deps_report.tsv. Probe results are also memoised to tmp/deps_cache.<pid>/ — deliberately to files rather than shell variables, because the callers read them through command substitution, which runs in a subshell where an in-memory cache is discarded on exit. The cache key is derived from PATH at call time, so a run that changes PATH re-probes instead of returning a stale "missing".

When it refuses: never. This is the one module whose job is to report absence rather than to refuse because of it — and it is what every other module's WHY line is quoting.

Read next: §2.1, which walks the same module as a pre-flight; 01_radio for the half of the bench that 00_deps cannot see.

4.2.2 01_radio — SDR / Radio Manager

The radio manager is an interface manager for software defined radios: enumerate what is attached, ask each device what it can actually do, keep one device selected, and arbitrate who is allowed to touch it.

It probes through each vendor's own tool rather than a generic USB scan, so what comes back is capability, not a bus address: frequency range, sample rate, gain range, and direction. A receive-only device — an RTL-SDR is the common case — is flagged on every row and every row it produces, and a verb that would need to transmit refuses by name instead of failing somewhere inside a vendor CLI with a message about an argument.

modules/01_radio.sh --list-tsv                  # every attached SDR
modules/01_radio.sh --caps-tsv                  # what each one can do
modules/01_radio.sh --select 0                  # pick one; remembers the choice
modules/01_radio.sh --band-check 1842.5         # which band is this, can we reach it
modules/01_radio.sh --set-freq 935 --set-gain 32 --set-rate 2000000
modules/01_radio.sh --lock-claim survey 4711    # own the device for one operation
modules/01_radio.sh --lock-status-tsv           # who owns it, live or stale
modules/01_radio.sh --record 30                 # raw IQ into captures/

What it keeps: loot/radio/, the selection in tmp/sdr_device, and an exclusive lock in tmp/radio.lock. The lock is the part worth understanding: two modules recording from one radio produce two broken captures, so a module claims the device before opening it, the claim is released only by the owner, and --lock-recover removes a stale claim while still refusing to steal a live one. A killed run leaves a stale claim rather than a silent second owner, which is the failure you would rather have.

When it refuses: with no radio, --record and every transmit path stop and name the missing device. --plan is the exception worth knowing: it prints the vendor commands without running them, so the bench can be reviewed on a machine that has no radio at all.

Read next: §2.2 on the radio, and §5.2 for what the receiver is for.

4.2.3 05_field_kit — Field Kit

Two jobs, both for the case where time is short and the radio may not be there at all.

Pre-flight. --capability says what hardware is present and, more usefully, what that means: not "hackrf found" but "transmit available on 2G and 4G, receive only on 3G".

Honesty verification. --guard-check runs every radio-gated verb on the box and asserts that each one fails honestly — non-zero exit, naming the missing hardware, no success line, no fabricated row. This is the toolkit checking its own doctrine rather than asking you to trust it, and its output is a store you can keep as part of a bench record.

modules/05_field_kit.sh --capability
modules/05_field_kit.sh --guard-check          # writes loot/field_kit/guard-check.tsv
modules/05_field_kit.sh --scenarios            # what synthetic streams exist
modules/05_field_kit.sh --emit imsi-catcher 12 # deterministic GSMTAP, no radio
modules/05_field_kit.sh --emit gsm-attach 12 10 /tmp/attach.pcap

What it keeps: loot/field_kit/guard-check.tsv. --emit writes a pcap when given a path and UDP datagrams when given a host:port; the stream is deterministic, so the same scenario twice is the same bytes, which is what makes it usable as a fixture in another module's selftest.

When it refuses: nothing to refuse — it is the module that verifies refusals.

Read next: §2.2. --emit is the best way to try a decoder with no hardware: pipe it at 03_gsmtap and read the result.

4.2.4 25_killchain — Killchain

A chain is a list of steps in a file. Each step names a module, a verb, that verb's arguments, whether it needs hardware, and a sentence saying what the step is for. The module runs the steps in order and records what happened to each one.

The point is not automation for its own sake. It is that a multi-step operation — survey, select, decode, harvest — has an order that matters and a checkpoint that matters more: a run that dies at step nine should resume at step nine, not at step one, and the record of that run should say which step it died at.

modules/25_killchain.sh --list-chains          # every chain, steps, hardware count
modules/25_killchain.sh --show-chain recon     # resolve the steps, run nothing
modules/25_killchain.sh --run recon --dry-run  # print the resolved argv
modules/25_killchain.sh --run recon            # run it, write the report
modules/25_killchain.sh --pause                # cooperative: between steps
modules/25_killchain.sh --resume                # from the checkpoint
modules/25_killchain.sh --run-multi recon targets.tsv   # once per target
modules/25_killchain.sh --runs-tsv             # every recorded run, with outcome

A chain file is TSV and readable:

step    module  verb    args    hardware    description
1   02_cell_recon   --scan  lte yes survey the cell layer
2   03_gsmtap   --read  loot/gsmtap/live.pcap   no  decode what was captured

What it keeps: loot/killchain/chains/ for the definitions, loot/killchain/runs/ for what happened, and state/killchain/ for the checkpoint a resume would use.

When it refuses: --run refuses a step that needs hardware when none is present, and stops there unless --keep-going was given — in which case it continues but the run's overall exit is still non-zero. A run that skipped something is not a successful run.

Read next: §5.x for the chains as techniques, and 59_remote_node --dispatch for running one of these steps on another machine.

4.2.5 59_remote_node — Remote Nodes

The fleet. A "node" is another machine, a phone, a modem or a serial device that can be told to run a module and report what it measured. This module is the front door to the registry: it names the verbs, keeps the exit codes honest, and adds the one summary a fleet needs — how far behind each node's spool you are.

The registry itself lives in tools/casebook.py, and the far side of a node is tools/node_agent.sh. That split is deliberate: the verbs here are the same ones the Casebook's Nodes tab calls, so a fleet you built in the window is a fleet you can drive from a shell.

modules/59_remote_node.sh --caps                         # what THIS host could be
modules/59_remote_node.sh --presets                      # device classes
modules/59_remote_node.sh --add van-1 pi@10.0.0.5 --transport ssh --role survey
modules/59_remote_node.sh --probe van-1                  # ONLINE / OFFLINE / UNKNOWN
modules/59_remote_node.sh --list-tsv                     # class, caps, role, last seen
modules/59_remote_node.sh --stream van-1 --follow 5      # drain its spool, live
modules/59_remote_node.sh --dispatch van-1 02_cell_recon --scan lte
modules/59_remote_node.sh --collect van-1                # into the active case
modules/59_remote_node.sh --health                       # load, memory, disk, work

Transports are ssh, local, adb, serial, qmi, mbim, diag and sms. A node's roles are inferred from what it actually reported in its probe; --set-roles overrides that, and --allow limits which modules may be dispatched to it, so a node cannot be talked into running something its owner did not sanction.

What it keeps: the registry and the per-node spool cursors in the casebook's node store; --collect copies the far side's state into the open case.

When it refuses: UNKNOWN is a distinct answer from OFFLINE and is never rounded to either. --probe on a node whose transport is missing fails naming the transport, and --bulk-probe continues past an unreachable node without pretending it answered.

Read next: §4.13.5 for the Fleet page, and §5.x for a survey that spans more than one box.