Part 3 — Interfaces
Part 2 used the toolkit through its most honest surface: a shell prompt and one module at a time. That surface is the one that always works, and it is the one this part describes first and in the most detail — because the other two are views over it, and a reader who knows the command line can always reconstruct what a button did.
There are four things in this part. §3.1 is the module command line and the launcher that indexes it. §3.2 is the terminal workspace. §3.3 is the window. §3.4 is the gate suite, which is not an interface at all but is the reason the other three can be trusted to say what they say.
A note on the relationship between them. The window does not contain a second
implementation of anything: every button is an argv array handed to the same
shell script you would have typed. When this part says "the GUI runs
--turbo-decode", it means the exact command in §3.1's sense, run as a child
process, with its stdout read back into a result pane. That is a deliberate
limit and it has a cost — the window cannot do anything the modules cannot do —
but it is what makes the whole thing auditable: any result you see on screen can
be reproduced in a terminal, and any result in a terminal can be pasted back.
3.1 The command line, and the launcher that indexes it
One script, two lives
Every module is one file under modules/, and every one of them is written to
be both a program and a library:
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
set -o pipefail
IMSISTING_CLI=1
: "${TMP_DIR:=/tmp/imsisting}"
: "${LOOT_DIR:=$IMSISTING_ROOT/loot}"
...
declare -F banner >/dev/null 2>&1 || banner() { :; }
declare -F info >/dev/null 2>&1 || info() { printf '[info] %s\n' "$*" >&2; }
declare -F warn >/dev/null 2>&1 || warn() { printf '[warn] %s\n' "$*" >&2; }
declare -F err >/dev/null 2>&1 || err() { printf '[err] %s\n' "$*" >&2; }
declare -F ok >/dev/null 2>&1 || ok() { printf '[ok] %s\n' "$*" >&2; }
gatk_cli_main "$@"
fi
That tail is why a gate can do this:
source modules/07_gsm_attack.sh
gatk_selftest # no dispatch, no launcher, no window
and why the window can build a page out of a module without executing it. The
fallback info/warn/err/ok stubs matter more than they look: sourced, the
module has no launcher to define them, so each module defines a do-nothing
version and the launcher's version, when there is one, wins.
Every function a module defines is prefixed with its own tag — gatk_ in
07_gsm_attack, uair_ in 08_umts_air, et_ in 57_evil_twin, sc_ in
58_suci_catcher. The prefixes exist so that several modules can be loaded into
one shell without a collision, which is exactly what the codec gates do.
The preamble
The first twenty-five lines of a module are the same in all sixty-one of them, and each line is load-bearing.
The root comes from the script's own path, following symlinks:
if [[ -z "${IMSISTING_ROOT:-}" ]]; then
_sl_src="${BASH_SOURCE[0]}"
while [ -L "$_sl_src" ]; do ... done
IMSISTING_ROOT="$(cd -P "$(dirname "$_sl_src")/.." && pwd)"
export IMSISTING_ROOT
fi
A module launched standalone — from a pkexec prompt, from a desktop file, from
a cron job — has no launcher to tell it where the tree is, and its store must
still land inside that tree rather than in whatever directory happened to be
current.
HOME is reset when it does not match the tree's parent, because pkexec
runs with HOME=/root while writing to the invoking user's project. Anything
$HOME-relative would otherwise resolve into /root and the write would either
fail or succeed somewhere nobody looks.
Five directories are named and created:
| Variable | Default | What belongs in it |
|---|---|---|
IMSISTING_ROOT |
the checkout | the tree; every other path is derived from it |
TMP_DIR |
/tmp/imsisting |
scratch: caches, logs of the window, intermediate files |
LOOT_DIR |
$IMSISTING_ROOT/loot |
observations that are the answer — stores you keep |
CAPTURES_DIR |
$IMSISTING_ROOT/captures |
raw captures, before anything decoded them |
LOGS_DIR |
$IMSISTING_ROOT/logs |
what a long run writes while it runs |
STATE_DIR |
$IMSISTING_ROOT/state |
what the next launch must remember |
The distinction between loot/ and captures/ is the one people get wrong
first: a capture is evidence of what crossed the air, a loot store is the
toolkit's reading of it. You may delete a loot store and rebuild it from the
capture; you cannot rebuild the capture. That is why captures/ is the
directory to back up.
All five are environment variables first and defaults second, so a survey can be relocated without touching the code:
LOOT_DIR=/mnt/field/loot CAPTURES_DIR=/mnt/field/captures \
modules/02_cell_recon.sh --scan lte 20
Verbs, and the usage block
Dispatch is one case on "${1:-}", and the unknown-verb arm is the manual
for that module:
*)
cat >&2 <<EOF
GSM Catcher — IMSI catcher, A5/1 downgrade, TMSI de-anonymisation, Kc recovery
Usage: $0 COMMAND
--status-tsv radio and plan state as TSV
--plan <name> write a handoff plan file and report the hardware
...
--selftest verify sequence, classmark, messages and ciphers
--menu interactive menu
EOF
return 2 ;;
Two consequences follow. Asking a module for help is an error, so
modules/07_gsm_attack.sh with no arguments prints usage to stderr and exits
2 — it is not a failure, it is a refusal to guess. And the usage block is the
only place a verb is described, which is what lets tools/check-verbs.sh
(§3.4) compare the set of documented verbs against the set of things the window
offers.
The usage text follows a shape across all sixty-one modules: a title line
naming the module and what it is for, Usage:, then groups separated by a blank
line and introduced by a short heading, because a flat list of ninety verbs
cannot be read. An argument in <angle> is required, one in [brackets] is
optional, and an [a|b|c] is a closed set with the default first.
What a verb prints
Machine-readable output goes to stdout, as TSV, always with a header row:
ts ts_epoch run step channel direction protocol message_type message source
That convention is not cosmetic. It is what lets awk -F'\t' '$2 ~ /^GSM Um/'
work, what lets the window fill a list without an API, what makes two runs
diffable, and what keeps a column from being renamed silently — because a
retired column name breaks a gate before it breaks a caller.
Human-facing lines go to stderr, and their first field is the kind:
| First field | Meaning | Exit code that usually follows |
|---|---|---|
ERROR |
the request cannot be honoured; the rest of the line says why | 2 |
WHY |
a capability is absent and here is the reason, with the alternative | 2 |
[info] |
progress on a run that is still going | carries on |
[warn] |
something was skipped or assumed | carries on |
[ok] |
the step the operator was waiting for finished | carries on |
The WHY line deserves its own sentence. When a module cannot do something —
no radio, no tshark, a driver that will not open — it does not print a generic
failure. It prints a WHY line naming the missing piece and the verb that would
work instead. A refusal you cannot act on is worse than no tool at all, and the
WHY line is the toolkit's answer to that.
Exit codes are the same three throughout, and §1.2 gave them in principle: 0
measured and printed, 1 ran and the answer is negative or nothing was found,
2 refused because the request or the bench cannot be honoured. A pipeline of
modules is therefore readable with &&:
modules/02_cell_recon.sh --scan lte 20 \
&& modules/04_tower_map.sh --survey lte 20
The four flags to try first on any module
modules/26_ss7_map.sh # no verb: usage on stderr, exit 2
modules/26_ss7_map.sh --selftest # prove this module's own arithmetic
modules/26_ss7_map.sh --status-tsv # what it can see right now, as TSV
modules/26_ss7_map.sh --menu # the same verbs as a numbered menu
--selftest is the one to reach for when a result looks wrong. It asserts the
module against published values — 357 rows in 07_gsm_attack, a few dozen in
most others — and it runs without a radio, without a capture and without root.
If --selftest passes and your result still looks wrong, the module is doing
what it claims and the claim is what you disagree with, which is a different
conversation; if it fails, stop and read the failing row.
--status-tsv is the honest one. It reports what the module can see now — a
radio that answers, a tool that is present, a store with rows in it — and it
reports absence as absence rather than as zero. That distinction is why the
window's header strip can say SDR none instead of SDR ready.
The launcher
imsisting.sh is an index, not an engine. Its job is to hold one table and to
turn a number into a function call:
CATEGORY|KEY|LABEL|MENU_FUNCTION|MODULE_ID
KEY is a sequential position — 01 through 61 — and the table's own header
comment says so, which is why the keys are unique and gapless. MENU_FUNCTION
names a shell function in the launcher; MODULE_ID names the script it ends up
running. Two rows may share a category and no two rows may share a key, because
fn_for_key() returns the first match and a duplicate key silently makes the
second row unreachable — a bug this tree has had once, and now has a gate
against (§3.4).
bash imsisting.sh --list # every module, its key and its availability
bash imsisting.sh --index # the module index as TSV
bash imsisting.sh --tui # the terminal workspace, §3.2
bash imsisting.sh --gui # the window, §3.3
bash imsisting.sh --version # imsisting v1.0.86 — 61 modules, 2G/3G/4G/5G
bash imsisting.sh --help # the above
The interactive browser needs root because the modules it launches may; the read-only verbs do not. That asymmetry is deliberate: looking at a catalogue should never require privilege, and transmitting always should.
Declared settings
The launcher also owns the small set of settings that outlive a session:
bash imsisting.sh --settings list # declared settings as TSV
bash imsisting.sh --settings set K V # declare one
bash imsisting.sh --settings unset K # back to the default
bash imsisting.sh --settings reset # back to every default
A setting declared here is written to a file the modules read, and a module reports the value it used, so "why did it use that path" has an answer. The window's Settings page (§3.3) edits the same values; the two cannot drift apart because there is only one store.