If you have ever opened Saleae Logic or PulseView and stared at 40 seconds of SPI at 8MHz, you know the feeling.
It is all noise until you know what to trigger on.
Your logic analyzer is not a microscope. It is a filter. Most people use it like they are trying to drink from a firehose. Open capture, zoom in, scroll for an hour, miss the one byte that mattered.
I did that for a year. Then I started scripting triggers. Now my analyzer finds hardcoded keys, debug shells, and backdoor boot modes while I make coffee.
This is how you stop drowning and start triggering on what matters. On your own hardware, in your own lab. SPI, I2C, UART — the three buses that run every smart device you own.
The Problem Is Not Capture, It Is Intent
A logic analyzer at 24MHz capturing 4 channels for 10 seconds gives you 240 million samples. You cannot manually read that. You need intent.
A trigger is intent expressed as code: "wake me up when this pattern happens, and ignore everything else."
Without triggers, you are doing archaeology. With triggers, you are doing detection engineering.
Here are the recipes I actually use when I reverse my own devices. All of these run on a $20 analyzer and some Python. No fancy gear needed.
UART: Where Devices Confess When They Think No One Is Listening
UART is where firmware gets chatty. Boot logs, debug shells, panic messages. Every device has a story it tells at 115200 baud when it thinks no one is listening.
The noise problem with UART is that it never shuts up. Boot is 2 seconds of spam, then silence, then random chatter. You scroll and miss the 200ms window where it says Press any key for shell.
Trigger Recipe 1: Trigger on Boot String, Not on Time
Don't trigger on start. Trigger on string.
You know your device prints U-Boot or BootROM or ESP-ROM on boot. Set your trigger to watch the RX line for that ASCII sequence.
In Saleae automation or PulseView Python, it looks like this in pseudo:
wait_for_string("U-Boot")
capture_next(2 seconds)
Now you never miss boot. Power cycle the device, your analyzer automatically captures the next 2 seconds after it sees the boot string. I caught a smart plug that only exposed a root shell for 400ms after U-Boot, then locked it. Without a string trigger, I would have scrolled past it 100 times.
This is also how you catch secrets. Set triggers for:
password:login:shelldebugAT+###
If your own device prints password: on UART during boot, that is a hardcoded creds waiting to happen. I found three last month in thrift store gear.
If you are trying to free your ESP32 from Arduino and actually see what the ROM bootloader says before your code runs, bare metal helps here. When you own startup, you own what UART prints. The path I took to own that startup without the IDE is in ESP32 Bare Metal Firmware. It makes UART debugging honest again.
Trigger Recipe 2: Trigger on Baud Change
Cheap devices switch baud mid-boot. 74880 to 115200 on ESP8266, 115200 to 921600 on some routers. If you only watch one baud, you miss half the conversation.
Script a trigger that detects framing errors. When you see 5 framing errors in a row, auto-switch baud and re-decode. That is how you catch the bootloader that hides at a weird baud on purpose.
I2C: The Quiet Gossip Bus
I2C is slow and polite. Two wires, many devices, all gossiping about addresses.
The noise problem with I2C is address spam. Your temperature sensor talks every second, your fuel gauge talks every second, your PMIC talks every second. You get thousands of identical transactions.
What matters is not the spam. It is the anomalies.
Trigger Recipe 3: Trigger on Address You Did Not Expect
Scan your bus once, write down all addresses you expect: 0x48, 0x6B, 0x50. Now script:
trigger_if_address != [0x48, 0x6B, 0x50]
I did this on a smart lightbulb I was auditing. It had an extra I2C EEPROM at 0x57 that was not on the schematic. That EEPROM held WiFi creds in plaintext and a cloud token. The manufacturer added it for factory testing and forgot to remove it. The only way to find it was to trigger on "address I did not expect."
Trigger Recipe 4: Trigger on NACK or Clock Stretch
NACK means someone tried to talk to a device that is not there, or a device that is locked. Clock stretching means a device is stalling because it is doing crypto or is bricked.
Set triggers for:
- NACK after address byte -> someone is probing for hidden devices
- NACK after data byte -> write protected EEPROM
- Clock stretch longer than 5ms -> device doing crypto, possible secure element
This is how I found a secure element on a smart lock that was holding the master key. It stretched the clock for 22ms during authentication. That stretch was the tell that it was doing real work. Without a trigger on stretch, I would have seen it as just slow I2C.
SPI: Where Firmware Lives And Secrets Get Dumped
SPI is where firmware lives. If UART is confession, SPI is the diary.
The noise problem with SPI is speed and volume. 8MHz, 4 lines, 10 seconds of capture is gigabytes. You cannot manually find where the flash read ends and the real secrets start.
Trigger Recipe 5: Trigger on Flash Read Command
Almost all SPI flash uses command 0x03 for read. Every boot, the chip reads from address 0x0.
Trigger on:
MOSI first byte == 0x03
Capture the next 256 bytes. Now you have the bootloader without capturing 10 seconds of garbage. You just triggered on the exact moment the device reads its own firmware.
This is the first step to non-destructive dumping. You watch how the device reads its own flash, then you replicate it with a clip. You learn the flash size, the read command it uses, whether it uses quad SPI. All without touching the chip.
I learned this by bricking two devices trying to dump them blind. Now I watch first, then dump. The full safe method — watching first with a logic analyzer, then clipping — is in Shadow Catalog. It is about reading firmware without killing the board.
Trigger Recipe 6: Trigger on High Entropy Data
This is the money recipe.
Most SPI traffic is low entropy — code, configs, repeated values. Secrets are high entropy — keys, tokens, random.
Script a trigger that calculates entropy of MISO payloads in real time. If entropy > 7.5 for 32 bytes, capture it.
I used this on a Zigbee gateway. Normal traffic: low entropy. Then every boot, 32 bytes of high entropy at address 0x7F000. That was the Zigbee network key. Stored in plaintext in external flash. Found automatically because I triggered on entropy, not on address.
Trigger Recipe 7: Trigger on Write Enable + Write
Flash write is 0x06 (WREN) followed by 0x02 (PP) or 0x20 (SE). If you see 0x06 -> 0x02 in your capture, the device is writing to flash. That is where it stores your WiFi password, your token, your calibration.
Trigger on that sequence and capture the payload. That payload is often your secret, stored in plaintext.
How To Chain Them Into An Automated Pipeline
One trigger is a filter. Three triggers chained is a vulnerability discovery pipeline.
My pipeline for any new thrift store device I bring into my lab:
- UART string trigger on boot -> capture boot log -> parse for
password,shell,debug - I2C anomaly trigger -> find hidden EEPROMs -> dump their content
- SPI entropy trigger -> find keys -> check if they are hardcoded
This runs automatically. I plug in the device, power it, my analyzer scripts capture, parse, and spit out a report: "Found UART shell prompt at 0.8s, found I2C device at 0x57 not in baseline, found high entropy blob at SPI address 0x7F000."
That report used to take me 3 hours of manual scrolling. Now it takes 4 minutes. The analyzer does the boring part. I do the interesting part — reading the secret.
I turned that pipeline into a set of reusable trigger scripts. If you want the actual Python for Saleae and PulseView — the entropy trigger, the baud auto-switch, the NACK hunter, the flash read sniffer — I put them in Logic Analyzer Trigger Scripting. It is not about the analyzer, it is about making the analyzer find vulns while you sleep.
And if you want to build devices that deserve this kind of analysis — bare metal firmware that does not leak secrets over UART and does not store keys in plaintext in external flash — that is the bare metal path. Arduino hides these problems. Bare metal forces you to confront them. Notes on that path are in ESP32 Bare Metal.
Stop Collecting Captures, Start Collecting Triggers
A 10GB capture file is not impressive. A 5-line trigger that finds a hardcoded key automatically is impressive.
Think like a detection engineer, not a hoarder.
- What string would a debug shell print?
- What address would a hidden EEPROM use?
- What entropy would a key have?
- What command would a flash write use?
Write a trigger for that question. Let the analyzer answer it.
Your lab does not need a more expensive analyzer. It needs better questions.
Build a library of triggers. Reuse them. My current library has 23. I bring them to every new device like a lockpick set. Most devices open in the first 3.
The rest is just dumping and reading. And for that, you need a clip, not a prayer. Safe dumping method is in Shadow Catalog if you want to read the firmware you just triggered on without bricking the board.
Stop staring at squiggles. Make the squiggles tell you when they are lying.
I teach hardware to be honest.
If your analyzer is just scrolling, you are doing it wrong:
If you drown in noise → Logic Analyzer Trigger Scripting: Automated Vuln Discovery — 23 trigger recipes, Python scripts for Saleae / PulseView, entropy, NACK, baud switching.
If you want firmware that does not need triggers to hide its mistakes → ESP32 Bare Metal Firmware — escape Arduino, own startup, own sleep, own peripherals.
If you want to read the firmware you just found → Shadow Catalog: Firmware Dumping Without Bricking — non-destructive dumping order, voltage checks, clip method.
All on my Gumroad. Capture less, trigger more.
Top comments (0)