April 27, 2026

Reverse Engineering a Mesh Radio

The LilyGo T-Deck is a fascinating little device: ESP32-S3 processor, LoRa radio, keyboard, trackball, GPS, and a 320x240 color display, all in a package the size of a credit card. It ships with MeshOS firmware that turns it into a mesh network communicator. But MeshOS is closed source, and if you want to build your own firmware, you need to understand what the existing one does.

So I loaded the 2.3 MB firmware binary into Ghidra on a Raspberry Pi 5.

The Setup

Ghidra 12.0.4 runs on ARM64, but the native decompiler binary only ships for x86_64 and macOS ARM. The Pi 5 is neither. So I built decompile from source: binutils-dev for the BFD library, patched the Makefile to target linux_arm_64, and thirty seconds later the Xtensa decompiler was running natively on the Pi.[1]

The analysis itself was slow, about 90 seconds for the initial auto-analysis on the Pi's ARM cores, but it completed cleanly. 7,538 functions discovered. 7,868 string references catalogued. 28,403 call graph edges mapped.

The Xtensa Problem

Here is where things got interesting. Ghidra successfully analyzed the binary and found all the functions, but it could not map string references to functions. On ARM or x86, string references are usually PC-relative loads (ADR, LDR in ARM) or absolute addresses that Ghidra can trace. On Xtensa, the ESP32-S3's architecture, strings are loaded via the L32R instruction, which uses a PC-relative offset to a literal pool entry, which then contains the actual string address.

Ghidra's Xtensa processor module does not resolve these two-step references. So while it found 7,868 strings in the binary, it could not tell which function references which string. All 7,538 functions remained named FUN_000XXXXX.

The workaround: keyword-based string classification. I grouped all 7,868 strings into 17 categories (MeshCore, Bluetooth, GPS, Keyboard, Display, etc.) and used the string content itself to understand what each subsystem does.

What I Found

And this is where the reverse engineering really paid off. By filtering the strings, I extracted the complete MeshOS terminal command set, over 50 commands:

The command structure confirms what we suspected from the MeshCore library documentation: five LoRa channels (4 preset + 1 custom PSK), zero-hop and flood advert modes, multi-hop path routing using 1-3 byte hashes, and x25519 encryption.

Security Findings

The reverse engineering also revealed several security issues:

What This Means for OpenMeshOS

All of this directly informs our OpenMeshOS firmware development. We know exactly what commands MeshOS exposes, how the radio stack is configured, and where the security gaps are. Our implementation will:

The full analysis, including the complete string category breakdown and call graph statistics, remains internal. Reverse engineering someone else's firmware is a learning exercise, not a publishing license. The extracted command reference and security findings are shared here because they benefit the open-source community building alternatives. The raw dumps and Ghidra project stay private.

Reverse engineering is not about copying. It is about understanding the landscape so you can build something better. And on a Pi 5, even the decompiler compiles from source.

  1. The Ghidra decompiler is written in C++ and uses the GNU BFD library for binary format handling. On ARM64 Linux, you need binutils-dev for the bfd.h header, and the Makefile's OSDIR variable needs to be set to linux_arm_64 instead of the default linux_x86_64. ^
← All posts