Mechanism
LoRa modulates data with Chirp Spread Spectrum (CSS): a sinusoid sweeps linearly across the channel bandwidth, and the symbol value is the chirp’s cyclic start frequency offset — for spreading factor SF, a symbol carries SF bits, so there are 2^SF distinguishable start offsets [marquet2020] [tapparel2020]. The regional plans use 125/250/500 kHz bandwidths and SF7–SF12, trading data rate for sensitivity (higher SF spreads the same energy over a longer symbol, reaching lower SNR) [rp002].
Demodulation is de-chirping: the receiver multiplies the incoming chirp by a locally generated reference down-chirp, which collapses the swept tone into a single constant-frequency tone, then takes an FFT — the symbol is read off as the index of the FFT bin holding the energy peak [marquet2020] [xu2022]. This is the standard, well-understood LoRa receiver and is what every open-source SDR demodulator implements.
Doing it reliably on real captures is the hard part. The energy peak is degraded by sampling time offset (STO) and carrier frequency offset (CFO) between the unsynchronised transmitter and the SDR, and at low SNR the peak can be distorted or buried in noise so the FFT bin is read wrong [xu2022]. A complete receiver therefore needs frame detection (correlating against the up-chirp preamble), then STO and CFO estimation and correction before demodulation; the gr-lora_sdr design implements exactly this chain — preamble sync, CFO/STO compensation, demodulation, Gray-demap, deinterleave, Hamming decode, dewhiten, CRC — and is built to keep decoding at very low SNR [tapparel2020] [grlorasdr] [tapparel2024].
Beyond the per-symbol decision, the choice between hard-decision (emit the single nearest bin) and soft-decision demodulation (carry the per-bin likelihoods forward into the Hamming/FEC stage) changes how many frames survive at the edge of the link budget: soft information lets the decoder recover frames a nearest-bin decoder loses, so hard-decision yields are a lower bound on what is actually on the air [marquet2020] [xu2022]. This is a capture-completeness concern for the auditor, not a vulnerability in the target — hence info.
This is a passive PHY check: it recovers symbols and frame bits, but the LoRaWAN application payload stays AES-128 encrypted until the keys are assessed at the crypto layer. There is no off-the-shelf hardware demodulator for LoRa CSS in the standard kit; the de-chirp happens in software on the SDR, which is why capturing and framing live together at the next (link) layer (RFSAM-RES-07).
Procedure
Work only on signals you are authorised to receive and analyse. Receiving and demodulating LoRa is passive, but capturing third-party traffic may still be regulated in your jurisdiction — have explicit permission for the deployment under test.
-
Confirm the band and channel plan (from RFSAM-LORA-SP / RFSAM-LORA-IG): the regional band sets the centre frequency, and the device profile sets bandwidth and spreading factor. If you only know the band, plan to scan SF7–SF12 [rp002].
-
Capture I/Q over the channel. Tune the SDR to the channel centre with sample rate ≥ the LoRa bandwidth. Example with an RTL-SDR for EU868 channel 0:
rtl_sdr -f 868100000 -s 1000000 -g 40 -n 6000000 lora_eu868_ch0.iqProduces a raw
uint8I/Q file.-s 1000000(1 Msps) comfortably covers 125 kHz; increase the sample rate to cover 250/500 kHz channels. Expect a file growing at sample_rate × 2 bytes/s. -
De-chirp and demodulate in software. Run the captured I/Q through a gr-lora_sdr receiver flowgraph (or the bundled examples), set to the channel’s SF and BW:
git clone https://github.com/tapparelj/gr-lora_sdr # build/install per its README, then run an RX flowgraph (GRC or Python) # configured: center_freq=868.1e6, bw=125000, sf=7, samp_rate to match the captureExpected output: for each detected frame the receiver prints the demodulated/decoded payload bytes (after Gray-demap, deinterleave, Hamming decode, dewhiten, CRC). A frame that passes CRC is a clean PHY demodulation; CRC failures or “no frame detected” mean the SF/BW guess, sync, or SNR is wrong [grlorasdr] [tapparel2020].
-
If you do not yet know SF/BW, or want the whole sub-band at once, stream wideband I/Q and let the decoder sweep SF7–SF12 across every bandwidth:
git clone https://github.com/persistentcache/Lora-Wideband-Decoder # run per its README against a bladeRF/USRP feed; decoded packets, node IDs # and a live waterfall appear in the local Flask web UIExpected output: decoded LoRa/LoRaWAN/Meshtastic packets surfacing in the web UI as they are demodulated across the band — useful when the exact channel or SF is unknown.
-
Read the result as a yield. Note how many frames demodulate cleanly (CRC-OK) versus how many the waterfall shows but the decoder drops. A non-trivial drop rate at the edge of range is the soft-vs-hard-decision gap, and the cue to confirm a soft-decision-capable demodulator before treating the capture as complete [marquet2020] [xu2022]. Export CRC-OK frames as LoRaTap PCAP for the link layer (RFSAM-LORA-LL, RFSAM-RES-07).
-
Single-board alternative without an SDR de-chirp flow: the CatSniffer’s SX1262 can capture and decode LoRa into Wireshark via
catniponce region/SF/BW are set — a hardware-radio capture path to cross-check the SDR demodulation against.
Field case
A representative, reproducible setup (the values below marked [FILL] are not measured RFSAM results — substitute your own bench numbers):
Target: an EU868 LoRaWAN sensor, channel 0 (868.1 MHz), SF7, BW 125 kHz. Capture with rtl_sdr -f 868100000 -s 1000000 -g 40 -n 6000000 lora_eu868_ch0.iq, then demodulate with gr-lora_sdr (sf=7, bw=125000) and observe the per-frame decode output. Repeat the same capture with the demodulator in hard-decision mode and compare CRC-OK frame yield.
On a clean, close-range capture both modes recover essentially every frame, and the PHY check simply confirms the de-chirp path works on this SF/BW. The interesting case is a weak-signal capture at the edge of range: there, soft-decision is expected to recover frames that hard-decision drops.
[!NOTE] [FILL: measured CRC-OK frame yield, hard vs soft-decision, at a recorded SNR for one capture]. Earlier stub copy asserted “~25% more bits recovered”; that specific figure is not an RFSAM measurement and is SF/BW/SNR/decoder-dependent, so it is withheld rather than fabricated. The direction (soft ≥ hard at low SNR) is supported by [marquet2020] and [xu2022]; the magnitude must be measured on the actual target capture.
For a passive survey aiming to enumerate a network, every frame the demodulator drops is potentially a device or a join you never see — which is why this PHY-completeness check precedes the link-layer enumeration rather than being assumed.
Remediation
This control verifies an auditor capability, so most of the action is auditor-side; the developer/integrator/operator guidance is about what the demodulability of the PHY implies for the deployment.
-
Auditor. Use a soft-decision-capable LoRa demodulator (gr-lora_sdr class) for assessment, and treat hard-decision-only yields as a lower bound on what is on the air, not a complete enumeration [marquet2020] [xu2022]. Confirm the SF/BW set actually in use (devices may use multiple data rates) before declaring a capture complete; sweep SF7–SF12 if the profile is unknown.
-
Developer / integrator. The CSS PHY is, by design, open and demodulable — its protection is not obscurity but the AES-128 crypto layer (FRMPayload confidentiality, MIC integrity). Do not treat “LoRa is hard to demodulate” as a security property: an SDR plus open-source software recovers the frames. Ensure all sensitive data and commands rely on the LoRaWAN crypto, not on the difficulty of demodulation, and assess that crypto under the CR-layer controls.
-
Operator. Assume any LoRa transmission in range is demodulable to frames by a passive listener; plan the threat model around the link-layer metadata that travels in clear (DevAddr, and the join-request DevEUI/JoinEUI/DevNonce) being observable, and around the encrypted payload’s confidentiality resting entirely on key management. PHY demodulation itself exposes no payload, but it is the prerequisite for every analysis above it.