Detecting GPS spoofing attacks on UAVs from flight telemetry — no SDR, no raw IQ, no extra hardware. The detector watches each flight for the GPS measurement diverging from the autopilot's inertial estimate and flags an attack the moment it starts, in real time.
Built and evaluated on the UAV Attack Dataset (real Pixhawk flights with a live HackRF spoofer, plus PX4 simulations across several airframes).
Benign flight (green) stays flat; the live HackRF-spoofed flight (red) breaks above the detection threshold the instant the attack begins at t≈118s, with a matching jump in GPS-vs-EKF position divergence.
- Detects the real attack the supervised model missed. A within-flight, baseline-relative detector flags all 3 spoofing flights — including the subtle live HackRF attack — with 0 false alarms across 6 flights.
- An honest evaluation that caught itself lying. A supervised Random Forest scored 68% on a single split but 17% under leave-one-flight-out cross-validation, exposing a sim-to-real generalisation gap. That finding drove the redesign.
- Real-time monitor with an online adaptive baseline that needs no per-flight tuning and absorbs benign manoeuvres (e.g. VTOL transitions) on its own.
- Firmware-agnostic ingestion of PX4 ULOGs across two firmware generations and four airframes.
GPS spoofing feeds a drone false satellite signals to push its perceived position off-true. The defensive signal is cross-sensor inconsistency: spoofed GPS drifts away from the inertial/EKF estimate, which stays honest short-term. That lives in the autopilot's own logs — no extra hardware required.
The instructive part was the evaluation. A supervised classifier looked fine on a single train/test split, but proper leave-one-flight-out cross-validation revealed it does not generalise across flights:
| Approach | Flight-level accuracy | What it means |
|---|---|---|
| Supervised RF — single group split | 68% | Optimistic; one lucky split |
| Supervised RF — leave-one-flight-out CV | 17% | Honest; fails across airframes/firmware |
| Within-flight baseline-relative detector | 100% (6/6) | 3/3 spoof detected, 0/3 false alarm |
Why the supervised model fails: the six flights span 4 airframes and 2 firmware versions, and the attacks range from sub-metre (live) to ~4 km (simulated) GPS drift. A model that learns absolute thresholds on one regime can't transfer to another. Real spoofing detection doesn't compare your aircraft to a library of others — it watches your flight deviate from its own baseline. Reframing the problem that way is what made it work.
- Ingest (
ulog_features.py) — parse PX4 ULOGs and merge the relevant topics onto one timeline. Firmware-agnostic: the live (v1.11.3) and simulated (~2020) logs name the innovations topic differently, so features are built only from topics common to both —estimator_status(test ratios),vehicle_gps_position(raw GPS + quality), andvehicle_local_position(EKF state). - Features — GPS-vs-EKF position and velocity divergence (computed by hand from lat/lon → local metres vs the EKF state), the EKF's own normalised innovation test ratios, and GPS quality (satellites, eph, jamming, noise).
- Detect (
detect_anomalies.py) — per flight, establish a baseline from the clean early portion and flag a sustained excursion above it, with an onset time. - Stream (
stream_detector.py) — the online version: an adaptive baseline that updates only while the signal looks normal, so it works on a live feed and shrugs off benign manoeuvres without ever being poisoned by a slow attack.
Within-flight detector, all six GPS-relevant flights:
| Flight | Source | Airframe | Truth | Verdict | Onset |
|---|---|---|---|---|---|
| ace-benign-log_0 | live | multirotor | benign | clean | — |
| ace-spoofing-hackrf-log_5 | live | multirotor | spoof | ATTACK | ~118 s |
| log_7 | sim | fixed-wing | spoof | ATTACK | ~434 s |
| log_8 | sim | tailsitter | benign | clean | — |
| log_13 | sim | VTOL | spoof | ATTACK | ~293 s |
| log_15 | sim | VTOL | benign | clean | — |
Spoof detection: 3/3. False alarms: 0/3.
The live HackRF attack: test ratio breaks the adaptive threshold at t≈118s.
Same airframe, no attack — the signal stays flat throughout.
stream_detector.py replays a flight as if it were arriving live (or can be fed
from a real MAVLink stream) and alarms the moment an attack begins:
--- streaming ace-spoofing-hackrf-log_5 ---
[t= 0s] WARMUP ratio=0.007 baseline_thr=--
[t= 30s] NORMAL ratio=0.041 baseline_thr=0.30
[t= 60s] NORMAL ratio=0.011 baseline_thr=0.30
[t= 90s] NORMAL ratio=0.003 baseline_thr=0.30
>>> SPOOFING DETECTED at t=118s (ratio=0.49 exceeded adaptive threshold 0.30) <<<
=> verdict: SPOOFING, onset t=118s
The StreamingSpoofDetector class is transport-agnostic — the same
.update(t, pos_test_ratio) that replays a log can be driven live by pymavlink
off PX4/ArduPilot SITL.
pip install -r requirements.txt
# 1. Build a labelled feature table from the dataset (point at your unzipped copy)
python src/build_dataset.py --root "/path/to/UAVAttackData" --out real_features.csv
# 2. The honest evaluation — supervised model and its failure to generalise
python src/train_real.py --data real_features.csv
python src/evaluate_cv.py --data real_features.csv
# 3. The working detector
python src/detect_anomalies.py --root "/path/to/UAVAttackData"
# 4. Real-time monitor (replay one flight at true speed)
python src/stream_detector.py "/path/to/a_spoofed_flight.ulg" --speed 1
# 5. Plot a flight's signal
python src/plot_flight.py "/path/to/a_flight.ulg" out.pngNo dataset yet? python src/generate_synthetic.py makes ArduPilot-style telemetry
so the synthetic pipeline (train_rf.py) runs end-to-end as a smoke test.
| File | Role |
|---|---|
src/ulog_features.py |
Firmware-agnostic ULOG parsing, topic merge, windowed features |
src/build_dataset.py |
Dataset folder → one labelled features CSV |
src/train_real.py |
Supervised RF, group-aware split, flight-level verdict |
src/evaluate_cv.py |
Leave-one-flight-out CV + sim/live breakdown |
src/detect_anomalies.py |
Within-flight baseline-relative detector (recommended) |
src/stream_detector.py |
Real-time monitor with online adaptive baseline |
src/plot_flight.py |
Plot test-ratio + divergence over time for one flight |
src/generate_synthetic.py, src/features.py, src/train_rf.py |
Synthetic smoke-test pipeline |
- Dataset: UAV Attack Dataset, J. Whelan et al., Ontario Tech University — IEEE DataPort (open access, free account). See the dataset page for the exact citation to include.
- This project is detection only — it analyses recorded telemetry and never transmits anything. Generating GPS spoofing or jamming signals over the air is illegal in most jurisdictions (in Canada, the federal Radiocommunication Act); this work deliberately stays on the defensive, receive/analyse side.
- Tiny, heterogeneous dataset (6 GPS-relevant flights). The within-flight approach sidesteps the cross-flight generalisation problem, but more flights — ideally of one airframe — would strengthen any supervised extension.
- Onset times are approximate for long simulated flights (a higher threshold can report a later onset); the binary detection is robust.
- The detector keys on EKF innovation, which also rises during aggressive manoeuvres; the adaptive baseline handles the cases seen here, but flight-mode awareness (below) is the principled fix.
- Firmware-agnostic PX4 ULOG ingestion
- Supervised baseline + leave-one-flight-out CV (sim-to-real gap)
- Within-flight baseline-relative detector
- Real-time streaming monitor with online adaptive baseline
- Live: feed
StreamingSpoofDetector.update()from a pymavlink SITL stream - Flight-mode-aware baselining (
vehicle_status.nav_state) - Add GPS-jamming detection as a second class (sat-count collapse + noise spike)
- Fuse features into a single anomaly score (Mahalanobis) with an ROC curve


