Skip to content
All posts
miscrevised August 1, 20267 min read

Hardware and signal challenges: logic captures, RF, and barcodes

What to do with a logic-analyzer capture, how to recognise UART, SPI and I2C from their waveforms, decoding a Flipper sub-GHz or IR file, reading damaged QR codes, and where the flag hides in a JTAG or SWD dump.

Hardware challenges arrive as data, not as devices. You get a .sr capture, a .sub file, a WAV of a radio recording, a photograph of a circuit board, or a spreadsheet of voltage samples - and the work is decoding a protocol from a waveform. No soldering iron required.

The unifying skill is recognising a protocol from the *shape* of the signal, because once you know what it is, a decoder does the rest. That recognition is what this post is about.

Logic captures: how many wires?

A logic analyzer records the digital state of several channels over time. The channel count and their relationship identify the protocol almost immediately.

ChannelsProtocolHow to recognise it
1 (or 2, one each direction)UART / serialNo clock line. Idles high, one start bit low, 8 data bits, a stop bit. Bit width is constant across the whole capture.
4SPIA clock, two data lines (MOSI and MISO), and a chip-select that goes low for the duration of a transfer.
2I2CA clock and a bidirectional data line. Distinctive start condition: data falls while the clock is high.
21-WireOne data line plus ground, with timing-encoded bits. Long low pulse to reset.
4-5JTAGTCK, TMS, TDI, TDO (and optionally TRST). A state machine driven by TMS.
2SWDClock plus bidirectional data, ARM debug. Shorter than JTAG for the same job.
Many, parallelA memory busAddress and data lines changing together on a clock edge.

For UART, the one parameter you must recover is the baud rate, and it comes straight from the capture: measure the shortest pulse in the signal, and the baud rate is one divided by that duration. Round to the nearest standard rate - 9600, 19200, 38400, 57600, 115200 - and the decode falls out.

# From a two-column CSV of (timestamp, level), recover the bit period.
import csv

edges = []
prev = None
for t, v in csv.reader(open("capture.csv")):
    v = int(v)
    if prev is not None and v != prev:
        edges.append(float(t))
    prev = v

gaps = [b - a for a, b in zip(edges, edges[1:])]
unit = min(gaps)
print(f"shortest pulse {unit*1e6:.1f} us -> {round(1/unit)} baud")
The shortest pulse is one bit. Everything longer is a run of identical bits, and its length divided by the unit is how many.
  • PulseView (sigrok's GUI) opens .sr files, stacks protocol decoders, and shows the decoded bytes annotated on the waveform. It is the standard tool and it is free.
  • Saleae Logic captures come as .sal or as exported CSV. The CSV path is the portable one.
  • Endianness and bit order vary: SPI has four clock-polarity and phase modes, and getting the mode wrong shifts every byte. If the decode is *almost* readable, try the other three modes.
  • I2C addresses are 7 bits with a read/write bit appended, so the address you read off a decode is often double what the datasheet says. Shift right by one before searching for the chip.
  • A UART capture that decodes to a boot log is a gift: it names the SoC, the bootloader, and the kernel, which turns the rest into a firmware challenge.

Radio and infrared

Sub-GHz remotes, garage doors, key fobs, and IR remotes all encode a short bit string with simple modulation, and CTFs deliver them as Flipper Zero capture files or as raw timing lists.

  • `.sub` files are Flipper sub-GHz captures. They are text: a header with the frequency and modulation, then either a protocol name and key, or RAW_Data as a list of signed microsecond durations - positive for a high pulse, negative for a low one.
  • `.ir` files are the infrared equivalent, either as a named protocol (NEC, RC5, Samsung32) with an address and command, or as raw timings.
  • Raw timings are a manchester or PWM encoding. Histogram the durations: you will find two clusters, a short and a long. Map them to 0 and 1 - and if that gives noise, map the *pairs* instead, because manchester encodes each bit as a transition.
  • Fixed-code versus rolling-code is the interesting distinction. A fixed code is the same every press and is trivially replayable; a rolling code (KeeLoq and friends) changes every time and is the actual challenge.
  • Frequencies to know: 315 MHz and 433.92 MHz for remotes, 868 MHz in Europe, 125 kHz and 13.56 MHz for RFID and NFC.

If the challenge gives you an IQ recording rather than a decoded capture, that is a software-defined-radio problem: inspectrum to look at it, GNU Radio or rtl_433 to demodulate. rtl_433 -A analyses an unknown signal and often names the protocol outright, which is worth trying before building a flowgraph. When the recording is delivered as audio, the audio steganography workflow applies - the spectrogram is the same tool.

Optical: barcodes and QR

A QR code arrives damaged, inverted, rotated, split in half, or drawn in a spectrogram. Two facts make these solvable more often than they look.

  • QR has Reed-Solomon error correction, at four levels recovering from 7% to 30% of damage. A code missing a chunk is frequently still readable, and a decoder that refuses may just need the image cleaned up - threshold it, straighten it, scale it up.
  • The three big squares are the finder patterns, and a fourth smaller one is the alignment pattern. If they are missing you can *draw them back in*, because their position and size are determined by the code's version.
  • Inverted colours defeat most decoders and are one command to fix.
  • The format information near the finder patterns encodes the error-correction level and the mask pattern. If a decoder fails on an otherwise clean code, the format bits are the usual culprit and there are only 32 possibilities to try.
  • Other symbologies turn up too: Data Matrix, Aztec, PDF417 on identity documents, and plain Code 128. zbarimg reads most of them.

Chip photographs and datasheets

A photograph of a board is an OSINT problem with a hardware vocabulary. Read the part numbers off the chips and search them: the datasheet gives the pinout, the interface, and often the memory layout. A SOIC-8 package near a microcontroller is almost always an SPI flash chip holding the firmware, and a four-pin header is almost always UART.

The OSINT method applies directly here: a part number is a pivot, a board silkscreen revision is a pivot, and an FCC ID on a device label leads to the regulatory filing, which includes internal photographs and sometimes the test report.

The approach

  1. Identify what you were given: a digital capture, an analog recording, a file from a specific tool, or an image.
  2. Count the channels. That names the protocol family before you look at anything else.
  3. Run every plausible decoder blind. Readable output identifies the protocol faster than analysis does.
  4. For serial, recover the bit period from the shortest pulse and round to a standard rate.
  5. For raw timings, histogram the durations. Two clusters means a two-symbol encoding; more means look for a preamble and a frame structure.
  6. For anything modulated, try rtl_433 -A or multimon-ng before building a flowgraph.
  7. For images, clean before decoding: threshold, deskew, invert, upscale.
  8. When the decode yields bytes rather than text, you are back at protocol reversing - look for lengths, types and checksums.

That last handoff is the most common ending. Hardware challenges usually stop being hardware challenges once you have the bytes, and what remains is an ordinary parsing problem with an unfamiliar origin.