reachtim

Technical Security

LoRecon: LoRa Reconnaissance on a $20 Board

A teaching walkthrough of a passive LoRa sniffer for cheap ESP32 hardware — the scanning problem it solves, exactly what it can and can’t decrypt, and how the pieces fit together.

My hacking partner and I spent an afternoon in a hotel listening to the LoRa traffic drifting through the building. Mesh nodes relaying each other’s messages. Someone’s sensor phoning home. Someone’s “private” channel still running on whatever key it shipped with. By the time we packed up, we had 3,630 packets from 266 devices, and 57 messages we could read in plaintext. Every readable one was sitting on a public channel or an unchanged default key.

None of it expected a listener. That is the interesting part, and it is what this article is about.

LoRecon is the listener. It’s firmware for a twenty-dollar ESP32 board that turns it into a self-contained LoRa reconnaissance tool, driven from your phone over its own web interface. We built it — I wrote the firmware, my buddy The-Foe built out the hardware and did the field testing — and I’ll teach it the way I’d want it explained to me, starting from the one fact that makes sniffing LoRa harder than sniffing WiFi.

Why you can’t just listen

On WiFi, one radio hears every other radio on the same channel. LoRa doesn’t work that way. “LoRa” is just the modulation; the protocol on top is left to each network, and every one picks its own frequency, spreading factor, bandwidth, and coding rate, and a receiver has to match all four before it can demodulate anything at all. Get one of the four wrong and you hear nothing at all — just silence, and you never even see it.

So a passive sniffer can’t just “listen.” It has to guess, methodically, across every combination anyone actually uses. LoRecon carries a table of 29 such combinations, covering five network families, and cycles through them one at a time, dwelling about twelve seconds on each before moving on. A full lap is roughly six minutes. Whatever’s in range and transmitting during its slice gets captured, classified, and — where possible — decrypted, before the radio retunes and moves to the next slot.

The 29-configuration scan cycle A row of 29 small blocks grouped into five clusters — Meshtastic (11), LoRaWAN/TTN (7), Helium (6), MeshCore (3), and ISM (2) — with one block highlighted to show the radio's current position in the sweep. SCAN CYCLE 0:00 ~6:00 Meshtastic 11 configs LoRaWAN / TTN 7 configs Helium 6 configs MeshCore 3 configs ISM 2 configs
Twenty-nine frequency/spreading-factor/bandwidth/coding-rate combinations, grouped by the network family that uses them. The highlighted block is wherever the sweep currently sits — roughly twelve seconds per config, about six minutes for a full loop.

That’s also why the scan takes minutes instead of being instant: there’s no shortcut around trying each combination in turn. It’s the same reason a Meshtastic node in your backpack and a LoRaWAN sensor on someone’s roof can share the same air without ever hearing each other — they were never listening on the same “channel” in the first place. Back in that hotel, the six-minute lap is exactly what turned a quiet band into 266 devices: give the sweep enough laps and everything transmitting eventually lands in a slice you’re tuned to.

What it does once it hears something

Once a packet is captured, LoRecon classifies it by protocol and tries to open it. Three things can happen, depending on what it heard.

  • Default-key decryption. It carries 23 known and historically common Meshtastic channel PSKs and tries each against what it captured. For MeshCore, the public channel key is fixed and shipped on every device, so that one’s automatic; a handful of common hashtag-room keys (#general, #emergency, and similar) are derived from the room name and tried too. For LoRaWAN, it checks 16 default AppKeys against any Join Request it catches.
  • Position extraction. A successful decrypt of a Meshtastic POSITION_APP packet yields GPS coordinates, exportable straight to KML or GeoJSON.
  • Replay. Ten storage slots, with a configurable repeat count and delay, for testing how your own gear reacts to a packet it’s seen before.

Everything captured is reachable from a six-tab web UI the board serves itself — over its own access point if there’s no WiFi around — and exportable as CSV or Wireshark-ready PCAP. From there, a Python toolkit that ships in the repo picks up the file: a report command that turns a capture into a prioritized findings page, a map command that plots extracted GPS onto an interactive map, and a topology command that draws the mesh as a graph, labeling nodes with whatever operator name it could decrypt.

The boundary: what a default key doesn’t get you

This is the part I’d want to know first if I were reading instead of writing: a cracked default key does not hand you someone’s private messages.

Meshtastic firmware from 2.5.0 onward split its encryption into two systems, and it’s worth understanding both, because the difference is the entire argument for why this tool is interesting rather than alarming.

Everything a node broadcasts automatically — its position, its telemetry, its node info, its traceroutes, routing packets, and anything typed into a channel (as opposed to a direct message) — is wrapped in one shared AES key per channel. If that key is still the factory default, which most deployments never change, LoRecon’s 23-key database opens it in one pass. That’s the finding, and it’s the whole of the finding: almost nobody rotates the key their gear shipped with. The 57 readable messages in that hotel were 57 people who never changed a setting.

Direct messages and admin commands are a different story. Since firmware 2.5.0 they get a second, independent layer — Curve25519 public-key encryption, keyed to the recipient’s device, whose private key never leaves that device. No database of default values gets you through that layer, because there is no shared secret to guess. This is the correct primitive, used correctly.

What a default channel key does and doesn't open A LoRa packet passes through a shared channel-PSK layer. Automatic broadcasts such as position, telemetry, and node info become plaintext once that layer opens. Direct messages and admin commands carry a second Curve25519 layer that a channel key never opens. ENCRYPTION BOUNDARY LoRa packet, captured over the air CHANNEL PSK · AES-256-CTR shared key — often still the factory default AUTOMATIC BROADCASTS position · telemetry · nodeinfo routing · channel messages plaintext once the key hits DIRECT MESSAGES & ADMIN firmware 2.5.0 and newer Curve25519 key pair stays encrypted — no key for this
The channel PSK is one shared secret protecting everything a node sends automatically. A hit opens it all at once. Direct messages and admin commands sit behind a second, independent lock keyed to one device’s private key — a channel-PSK hit never reaches it.

If an operator has set a custom channel PSK, the 23-key database simply doesn’t match anything, and the broadcasts stay encrypted blobs too. That’s the correct outcome, and honestly it’s the whole point of running the tool against your own mesh: one six-minute sweep tells you which side of that line you’re on. Setting a custom PSK is a thirty-second job, and it is the difference between being a readable blip in someone’s hotel capture and being noise.

One historical wrinkle worth knowing: before firmware 2.5.0, direct messages were just channel messages with a destination field, protected by the same shared key as everything else. Pre-2.5.0 devices don’t get the Curve25519 protection at all. So if you’re auditing an older deployment, keep in mind that “we’re on the default key” and “our DMs are private” cannot both be true at once.

From antenna to report

None of this needs a laptop in the field, and none of it needs a cloud service. The whole pipeline fits on the board and the phone in your pocket.

LoRecon's end-to-end data path LoRa RF flows one-way into the ESP32 board, which serves a web UI to a phone or laptop over WiFi. Captures export as CSV, PCAP, KML, or GeoJSON to Python tools that run entirely offline. END-TO-END DATA PATH no cloud in this picture — a phone is enough, and every analysis step after export runs offline LoRa RF passive reception receive only (no transmit path) ESP32-S3 + SX1262 scans & decrypts WiFi Phone or laptop web UI, no install export Python toolkit report · map · topology
CSV, PCAP, KML, and GeoJSON move the data from device to disk; `lorecon report`, `lorecon map`, and `lorecon topology` turn a saved capture into a findings page, a GPS map, and a mesh graph — all of it running on a machine that never has to touch the internet.

The RF leg is drawn one-way on purpose. LoRecon never transmits — replay writes to storage slots you trigger manually, but nothing goes out over the air unless you ask it to, and normal operation is receive-only from power-on. That’s a deliberate design line.

Choosing hardware

Four boards run the same firmware and the same web UI; the differences are about what you need in the field, not what the tool can do.

Board Why you’d pick it
Heltec WiFi LoRa 32 V3 Cheapest, most proven. Start here.
Heltec WiFi LoRa 32 V4 Optional L76K GPS, native USB
LilyGO T3-S3 SD card slot — log PCAP/CSV to disk in the field
LilyGO T-Beam Supreme SD, GPS, battery management, and 8 MB of PSRAM

That last column, PSRAM, earns its keep. A long unattended capture dies of heap fragmentation on a bare ESP32 long before anything conceptually interesting happens to it. The T-Beam Supreme with PSRAM enabled holds roughly 130 KB of minimum free heap after twenty hours with fifty devices in range, against about 53 KB without. Most of the recent release cycle was stability engineering of exactly that flavor — chunked API responses, heap guards, crash context that survives a reboot. It’s unglamorous work, and it’s the difference between a tool that runs on the bench and one that’s still running when you come back to it.

Try it yourself

There’s a browser-based installer, so getting a board running doesn’t require touching a toolchain:

https://haksht.github.io/lorecon/install/ — plug the board in, open the page in Chrome or Edge, click flash.

If you want the Python analysis toolkit too — report, map, topology, and the rest — that lives in the source repo, not the binary release, so you’ll want to clone it:

git clone https://github.com/haksht/lorecon.git
cd lorecon
python -m venv venv
venv\Scripts\activate.ps1        # or ./venv/bin/activate on macOS/Linux
pip install -e .

lorecon --help confirms the install worked. From there, lorecon report capture.csv is the first thing worth running against anything you capture.

Building the firmware yourself is PlatformIO, with OTA updates available after the first USB flash. MIT licensed. The current release is 2.6.0.

Note: This is for networks you own or have written permission to test. Passive reception is generally legal in the US; interception of content can implicate 18 U.S.C. § 2511, and the rules vary considerably by jurisdiction. The default-key testing exists to demonstrate a risk, not to hand anyone a way into someone else’s traffic.

Learn more

The repo’s docs/ folder is the fuller version of everything above:

Credit where it’s due

I wrote the firmware and the tooling. The hardware side — board selection, the antenna and power work, and the field testing that turned up most of the bugs worth fixing — is The-Foe, who I share the haksht org with. A sniffer that works on the bench and falls over after six hours in a bag is not a sniffer, and finding that out is its own skill.

The repo is at github.com/haksht/lorecon. If it’s useful to you, a star helps other people find it.