Skip to content

Attack Walkthroughs 00 07

Romel edited this page Aug 8, 2026 · 1 revision

06. Attack Walkthroughs (00–07)

This page is the practical, step-by-step companion to 05. Satellite Hacking & Attack Vectors: that page covers the vulnerability theory and root cause, this one covers how to actually run each of the 8 validated attack scripts against a real FlatSat board — prerequisites, the exact command, and how to confirm it worked. Script source lives in Attacks/ in this repo; this page only references filenames and flags, it doesn't reproduce the source.

Warning

Authorized Use Only Every attack below transmits real RF or sends real commands. Only run these against a FlatSat board, lab transmitter, or signal source you own or are explicitly authorized to test.


Before You Start

Two ways to run each attack. Every script below ships in two variants:

  • *_rf.py — the physically-realistic version, transmitted over the air via HackRF (uplink, 918MHz) and received via RTL-SDR or CatSniffer (downlink, 916MHz). This is what's been confirmed on real hardware.
  • *_usb.py — the identical finding, sent over FlatSat's USB serial link instead. Useful to rehearse the effect without an SDR, or when a HackRF isn't available. Less thoroughly validated on real hardware than the RF variants — see each script's own header comment for its current status.

The two-radio setup. FlatSat replies fast enough (~11-39ms) that a single HackRF can't reliably switch from transmitting a command to receiving the reply in time — attacks that need to see a response (00, 05, 06) use the HackRF for TX and a second, dedicated radio (RTL-SDR by default, or a CatSniffer) for continuous RX. This is why --rx-device shows up as a flag on several scripts below.

Dependency: PWNSAT-C3. These scripts import their SPP/CCSDS codec and AES implementation from PWNSAT-C3's pwnsat_tools/ — clone it as a sibling of this repo before running anything here. Full detail: Attacks/README.md.

Hardware assignment. Only one process can hold the HackRF (or the RTL-SDR) at a time. If PWNSAT-C3's own radio bridge (gradio/pwnsat_rx_bridge.py) is running, stop it before an _rf.py script needs the same radio — each attack's Prerequisites note below when this applies.


00 — APID Enumeration

What it does: Black-box enumeration of every APID in a given range, with no prior knowledge of the firmware source — probes each one and classifies the response (implemented, valid-but-unimplemented, or no response).

Finding: unauthenticated recon, no rate-limiting on uplink reception.

Scripts: 00_recon_apid_enum/00_recon_apid_enum_rf.py / 00_recon_apid_enum_usb.py

Prerequisites: HackRF + a second radio (--rx-device rtlsdr or catsniffer) free; if PWNSAT-C3's radio bridge is using the RTL-SDR, stop it first.

Command:

python3 00_recon_apid_enum_rf.py --start 1 --end 0x13 --skip 2 3 6 7 \
    --seq 500 --retries 6 --listen-seconds 1.2 --tx-gain 20 \
    --rx-device rtlsdr --rx-gain 30

--skip excludes APIDs that either change state without replying (SET_THRUSTER, SET_BEACON_RATE) or that you don't want to trigger during a sweep (RESETC, BROADCAST_MSG — the latter crashes the board with a 0-byte payload, see attack 02).

How to confirm it worked: the script prints a table classifying every probed APID as implemented / valid-but-unimplemented (ERROR TM) / no response. A full sweep of the 0x00–0x14 range accounts for all 19 APIDs without ever having seen the firmware source.


01 — Eavesdropping

What it does: Passively decrypts FlatSat's downlink telemetry in real time — no login, no C3 access, no transmission of any kind. The AES-128 key is static and hardcoded (PWNsatLabKey1234), so any receiver in range can decode every telemetry frame.

Finding: static AES-128 key, ECB mode, no IV, embedded in the firmware binary.

Scripts: 01_eavesdropping/pwnsat_rx_bridge.py, decode_capture.py

Prerequisites: RTL-SDR or HackRF, free (stop PWNSAT-C3's radio bridge first if it's running).

Command:

python3 pwnsat_rx_bridge.py --address tcp://127.0.0.1:5006 --output-file eavesdrop_run1.bin

How to confirm it worked: every time the FlatSat transmits (roughly every 14–30s on its own, faster if you trigger a reply from another attack), the terminal prints the decoded packet header, the ciphertext, and the plaintext recovered with the known static key. decode_capture.py eavesdrop_run1.bin replays a saved capture offline.


02 — Fuzzing Crash

What it does: A single malformed BROADCAST_MSG packet triggers an integer underflow in the firmware's length computation, crashing the board.

Finding: critical — integer underflow → stack buffer overflow (see Attack Vectors for the source-level root cause).

Scripts: 02_fuzzing_crash/02_fuzzing_crash_rf.py / 02_fuzzing_crash_usb.py

Prerequisites: HackRF free. No RX radio needed — this is fire-and-forget, the crash is confirmed by the board going unresponsive, not by a reply packet.

Command:

python3 02_fuzzing_crash_rf.py --seq 1 --encrypt on --device-args hackrf --gain 20 --frequency 918000000

How to confirm it worked: the FlatSat stops responding — in the real validation run, the USB serial port also wedged (stayed enumerated but streamed null bytes) until the USB cable was physically unplugged and replugged; the GPS status LED can stay lit since this is a software hang, not a power loss. If PWNSAT-C3 was connected over USB at the time, restart it after reconnecting the board.


03 — Command Injection

What it does: Sends a legitimate SET_THRUSTER command with no authentication at all — the ground-station gate defaults to disabled, so this (and most other commands) is wide open unless an operator has explicitly turned it on.

Finding: unauthenticated thruster control.

Scripts: 03_command_injection/03_command_injection_rf.py / 03_command_injection_usb.py

Prerequisites: HackRF free.

Command:

python3 03_command_injection_rf.py --thruster-id 0 --power 255 --seq 1 --encrypt on --device-args hackrf --gain 20 --frequency 918000000

How to confirm it worked: FlatSat's serial console immediately prints Thruster 0 changed to: 255 with no authentication step. On PWNSAT-C3, the Thruster gauge and sparkline jump to the new value without any dashboard button being touched; at power ≥200 the "THRUSTERS" health ring goes to warn.


04 — GPS Spoofing

What it does: Overpowers FlatSat's real onboard GPS receiver (u-blox NEO-6M) with a fake L1 signal generated by gps-sdr-sim, attacking the GPS chip directly — no CCSDS packet, no APID, no encryption involved at all.

Finding: the onboard GPS has no signal authentication — the general, well-known GPS spoofing problem.

Script: 04_gps_spoofing/04_gps_spoofing_rf.py

Warning

RF Safety Note. This transmits a fake GPS L1 signal over the air, affecting any GPS receiver in range, not just FlatSat. Intentionally transmitting fake GPS is regulated in most jurisdictions. Keep power low, transmissions short, and stay indoors/shielded with the antenna close to FlatSat's own GPS antenna. Do not run this near an airport, a road, or anywhere real navigation depends on real GPS.

Prerequisites: HackRF free, and your own .bin file generated with gps-sdr-sim (not bundled — it's a large binary IQ recording specific to a target position/date). --bin-file is required; there's no default target.

Command:

python3 04_gps_spoofing_rf.py --bin-file <your-target.bin> \
    --frequency 1575420000 --sample-rate 2600000 --tx-gain 0 --amp

Start --tx-gain low (default 0) and raise it gradually while watching the dashboard's GPS/Nav panel, rather than starting high.

How to confirm it worked / honest validation status:

  • Jamming — confirmed and reliable across the full gain range tested: the real GPS fix is reliably knocked out as soon as transmission starts, and reliably recovers once it stops.
  • Corrupted telemetry — confirmed intermittently at higher gain: the firmware's NMEA parser (sensors.cpp:gpsPoll()) accepts and displays physically impossible values (e.g. 239 "satellites") with no plausibility check.
  • Clean hijack (spoofing the reported position, not just jamming) — not achieved despite over 20 attempts (full gain ramp, sustained transmission, an aligned "soft handover" instead of a hard jump, amp on/off, a sweep of PPM values). The confirmed root cause: the HackRF used for validation has no active TCXO, which multiple independent sources report as a real requirement for GPS spoofing to lock cleanly. This is a hardware limitation of the transmitter used during validation, not a firmware fix — a TCXO-equipped SDR is expected to close this gap.

05 — Ground-Station Auth Spoofing

What it does: Forges a valid ground-station authentication session without ever knowing a real credential — the challenge/response handshake XORs against a key that's hardcoded in the firmware binary in plaintext.

Finding: static, hardcoded ground-station shared key, no rotation or derivation.

// worker.cpp
static const uint32_t gs_shared_auth_key = 0xC0DEFACEUL;
static uint32_t groundStationExpectedResponse(uint32_t challenge) {
  return challenge ^ gs_shared_auth_key;
}

Having the firmware is enough — or just overhearing a single real handshake over radio, since the challenge travels legibly inside the reply TM (encrypted with the same AES key attack 01 already breaks).

Scripts: 05_gs_auth_spoofing/05_gs_auth_spoofing_rf.py / 05_gs_auth_spoofing_usb.py, plus 00_gps_override_prereq_rf.py / _usb.py for the range prerequisite below.

Prerequisites: HackRF + a second RX radio, same as attack 00. The firmware also requires FlatSat's GPS to report a position within 35km of the ground station's coordinates before it accepts any phase of the handshake — if your board isn't physically near its configured ground station, this needs a debug firmware build with a GPS_OVERRIDE command to satisfy the range check first (not included in this release; this is a demo-only prerequisite, unrelated to the vulnerability itself).

Command:

python3 05_gs_auth_spoofing_rf.py --seq 2100 --retries 4 --listen-seconds 1.5 --tx-gain 20 --rx-device rtlsdr

How to confirm it worked:

[*] Phase 0x00 -- requesting a challenge...
    -> challenge issued: 0x<8 hex>
[*] Forged response = challenge XOR auth_key = 0x<...> ^ 0xC0DEFACE = 0x<...>
[*] Phase 0x01 -- submitting forged response...
    -> ACCEPTED. Session active for 300s -- forged, no real credentials ever used.

On PWNSAT-C3's Ground Station panel: auth_active: true, within_range: true, gate_open: true, session_remaining_s counting down from ~300.


06 — Replay

What it does: Captures one legitimate command and retransmits the exact same bytes later — the SPP sequence-count field is a plain counter with no anti-replay semantics, so the firmware processes the identical packet a second time as if it were new.

Finding: no anti-replay protection on the uplink.

Script: 06_replay/06_replay_rf.py / 06_replay_usb.py

Prerequisites: HackRF + a second RX radio (or --rx-device none to skip confirmation over RF and rely on PWNSAT-C3 instead).

Command:

python3 06_replay_rf.py --seq 100 --tx-gain 20 --rx-device rtlsdr

How to confirm it worked: the script sends a STATUS command, waits (--replay-delay, default 5s), then retransmits the byte-identical packet. Both replies are real, distinct executions — not a cached response — proven because uptime_s and the outgoing TM sequence_count both advance a second time between the two replies. A captured command that fires a thruster or opens a ground-station session could be replayed the same way, indefinitely, without ever knowing the key.


07 — RESETC (Unauthenticated Reboot)

What it does: A single unauthenticated RESETC packet reboots the board immediately.

Finding: unauthenticated reset — same root cause as 03 (the ground-station gate defaults to disabled).

Script: 07_resetc/07_resetc_rf.py / 07_resetc_usb.py

Prerequisites: HackRF free. No RX radio needed for the RF confirmation — the script instead queries PWNSAT-C3's own view of FlatSat's uptime_s before and after (--confirm-via-c3, on by default), since RESETC doesn't produce any TM of its own to hear over RF.

Command:

python3 07_resetc_rf.py --seq 1 --gain 20 --confirm-via-c3 on

How to confirm it worked:

[*] Checking PWNSAT-C3 for the FlatSat's current uptime (pre-attack baseline)...
    -> uptime_s=<N> before the attack.
[+] Sent.
[+] REBOOT CONFIRMED: uptime_s went <N> -> <M, much smaller> ...

uptime_s suddenly dropping instead of continuing to climb is the proof — a real reboot from one unauthenticated command. On the serial console: a red→yellow LED pattern, then the USB CDC ports disconnect and re-enumerate.


Finding Reference

Attack Root cause category
00 No rate-limiting, unauthenticated recon
01 Static AES-128 key, no key rotation
02 Integer underflow → stack buffer overflow (critical)
03 Unauthenticated command execution
04 No GPS signal authentication (general GPS problem, not FlatSat-specific)
05 Hardcoded ground-station shared key
06 No anti-replay protection
07 Unauthenticated reset

Full vulnerability-class writeups and firmware-level root cause for several of these: 05. Satellite Hacking & Attack Vectors.