A generic engine for graded student lab exercises on the WiFi Pineapple Pager. It doesn't do any detection/recon itself — it turns any existing payload or built-in tool in the library into a checkable, scored exercise by wrapping it with instructions, an optional hint, and a shell command that decides pass/fail.
Every payload in the official library is a tool built for someone who already knows what they're doing. None of them are a lesson: something with a stated objective, step-by-step guidance, a way to verify the student actually did it, and a score. Lab_Runner is that missing piece — a thin layer over the existing tools, not a replacement for any of them.
payload.shis the engine. It scanslabs/*.lab, shows a menu of available labs, captures a student name/ID, walks the student through each step of the chosen lab, and writes a report.- Each
.labfile is a small, trusted (professor-authored) bash script. It defines three metadata variables and arun_lab()function made oflab_stepcalls. - A step's "check" is just a shell command. If it exits
0, the step passes. This can be anything already on the device:ls /root/loot/handshakes/*.pcap,grep -q SomeVendor /root/loot/..., checkingrecon.db, whatever fits the exercise.
Because the check is a live shell command against the device's real state, a lab step naturally fails until the student actually does the thing — there's no separate "answer key" to keep in sync with reality.
Create labs/NN_your_lab_id.lab, where NN is a two-digit number controlling menu order. The engine lists labs/*.lab in plain filename order (alphabetical, which for a shell glob means numeric prefixes sort correctly) -- without one, a new lab's position in the picker menu depends on how its name happens to alphabetize against the others, not on where it actually belongs in the sequence. Existing labs are numbered 01-05; leave gaps (06, 07, ...) or renumber as needed if you want to insert one in the middle.
LAB_ID="your_lab_id"
LAB_TITLE="Shown in the lab-picker menu"
LAB_OBJECTIVE="One or two sentences shown before the lab starts."
run_lab() {
lab_step "Instructions shown to the student for this step." \
"Hint text -- pass \"\" for no hint." \
'shell command; exit 0 = pass, nonzero = fail'
lab_step "Next step's instructions." \
"Another hint." \
'another check command' \
'optional: a command that runs automatically when the step starts'
lab_complete "Closing message shown when every step is done (or skipped)."
}Rules that matter:
LAB_ID,LAB_TITLE,LAB_OBJECTIVEmust stay simple one-lineKEY="value"assignments (no command substitution, no multi-line). The enginegrepsLAB_TITLEout of every.labfile to build the menu without running any of them — only the one the student actually picks gets sourced and executed. Keeping the metadata lines simple is what makes that safe.- The check command (and the optional 4th action command, see below) runs via
eval, in its own subshell. Either can use anything available on the device, and can see$STUDENT_ID/$LAB_IDif useful (e.g. naming a file after the student). There's no sandboxing beyond the subshell isolation — this is the same trust level as writing any otherpayload.sh, since you're the one authoring both. - The subshell means
exitis safe to use inside a check as an early-bailout idiom ([ -z "$x" ] && exit 1) — it only ends that one check attempt, not the student's whole session. This was tested deliberately (a bareevalwithout the subshell does not have this property —exitinside it kills the entire engine). - A check can call the same DuckyScript UI commands the engine itself uses — e.g.
ans=$(TEXT_PICKER "What's the gateway IP?" "")— to ask the student a question as part of the check, then validate the answer against real device state. Seelabs/subnet_gateway_map.labfor a full example of this pattern. - 4th argument, "action": a command that runs automatically the moment a step starts, before the check menu appears. Use this to launch an existing payload directly (
bash /root/payloads/user/reconnaissance/some_payload/payload.sh) as part of the step, instead of telling the student to go find and launch it themselves. This matters on this device specifically: backing out of a running payload to launch a different one kills the running one rather than pausing it (confirmed live) — there's no way to "go do something else and come back," so a step that needs another payload's work has to launch it itself. When an action is set, the step's menu gains a "Re-run" option, in case the launched payload's own flow gets cancelled or needs a second attempt. - 5th argument,
"manual": makes the action available on demand instead of auto-firing it. The menu item is labeled "Get help" instead of "Re-run" in this mode. Use this when the action would hand the student the answer to what the step is actually asking them to figure out — e.g.subnet_gateway_map.lab's gateway-identification step offers theroutepayload as an optional "Get help," but doesn't run it automatically, since doing that would spoil the exercise before the student's even tried. - A step's instructions should describe what the student needs to go do, not just something to read. The engine's UI is deliberately minimal: instructions → hint/action/skip/check-my-work menu.
- Every step's menu also has an "Exit payload" option (confirmed before it actually exits), regardless of what the lab file defines. This exists because backing out via the device's own navigation doesn't pause Lab_Runner or return you to it -- it just returns an unrecognized response that the menu silently loops on, which looks like it's hung. "Exit payload" is the only reliable way out of a step. The in-progress attempt isn't saved when used this way.
- Keep checks idempotent and re-runnable — a student will hit "Check my work" multiple times while they work, so the check shouldn't have side effects beyond what it's actually verifying.
- Launch Lab_Runner, pick a lab from the menu.
- Enter the student's name/ID (shown in the report and gradebook).
- Confirm the objective and start.
- For each step: read the instructions (shown as a pop-up), go do the thing, then pick "Check my work." "Show hint" is available anytime and is tracked (doesn't block progress, just recorded). "Skip step" moves on and marks it skipped.
- At the end, a report is written and the engine loops back to the lab menu — ready for the next student without relaunching the payload.
- Per-attempt report:
/root/loot/lab_runner/<lab_id>_<student_id>_<timestamp>.txt— objective, a table of every step's result/time/hints, and a final score line. - Running gradebook:
/root/loot/lab_runner/gradebook.csv— one row appended per completed attempt (timestamp,lab_id,student_id,steps_passed,steps_total,hints_used,seconds_total), so a whole class's results across a session end up in one importable CSV. Pair this with the existingUSB_Mount_Loot_Transferorhandshake_2_usb-style payload to pull it off the device, orUSB_Mount_Loot_Transfer's general pattern for any loot export.
Meant to be assigned roughly in this order — each stage assumes the vocabulary/comfort the previous one built:
01_orientation.lab— zero networking content. Just the UI itself: whatLOG/ALERTmean, where loot lives, how hints work, how a typed-answer check feels. For a student who's never touched the device.02_know_your_network.lab— IP address, MAC address, subnet prefix — by inspecting the Pager's own state only, no scanning of anything else. Builds the vocabulary every later lab assumes.03_passive_recon.lab— first real recon concept: a short, purely passive BLE scan (only listens for advertisements already being broadcast — no active probing, nothing authorization-sensitive). Written directly withhciconfig/hcitoolrather than launching the existingbluetoothorlive_probepayloads — both had real risk for a beginner's first exercise (bluetoothauto-chains into a second payload viasourceat the end;live_probedepends on a monitor-mode interface that isn't guaranteed to exist and exits via a non-standard raw-input loop instead of the normal button API). Written from the same primitives Flock_Sky_Spy already proved reliable instead.04_subnet_gateway_map.lab— pairs with the officialnmap_host_discoverypayload, which it launches directly via the action-argument pattern above rather than asking the student to find it themselves. Confirms the student is on a real subnet, runs the host-discovery sweep, then asks the student to identify the gateway's IP address (viaTEXT_PICKERinside the check itself) and cross-validates their answer against both the system's actual default route and the scan's own "Host is up" result for that address — so a lucky guess doesn't pass, and neither does a right answer with no scan to back it up. Also demonstrates the manual/"Get help" pattern: offers theroutepayload as an on-demand helper for the gateway question, without auto-running it and spoiling the exercise.05_first_handshake.lab— the advanced tier: find a (professor-controlled test) AP via Recon, capture its handshake, and confirm the capture. Requires an isolated test AP you control — standard setup for a wireless security course, and a natural moment to reinforce the authorization/scope point (the hint on the deauth step says so explicitly, deliberately).
- Pair with
wpa_handshake_quality_check— a follow-on lab checking the captured handshake actually has a full 4-way EAPOL exchange, not just any pcap. - Pair with
Fuzz_Finder/Flock_Detect— "identify N distinct device types in your environment" using existing BLE detectors' loot output as the check.