Saleae Logic MSO Analysis
This skill enables analysis of captured signals from Saleae Logic MSO devices using the saleae-mso-api Python library. It supports loading binary exports, analyzing signal transitions, and decoding common protocols.
Prerequisites
saleae-mso-apiPython package — Do NOT blindly pip install. First check if it's already installed:
Only if that fails, install it:bashpython3 -c "from saleae.mso_api.binary_files import read_file; print('saleae-mso-api is available')"pip install saleae-mso-api- Binary export files from Saleae Logic software (
.binformat)
Quick Reference
Loading Binary Files
pythonfrom saleae.mso_api.binary_files import read_file from pathlib import Path file_path = Path("capture.bin") saleae_file = read_file(file_path) # Access metadata print(f"Version: {saleae_file.version}") print(f"Type: {saleae_file.type}") # Access data contents = saleae_file.contents
Digital Capture Structure
Digital exports contain DigitalExport_V1 with chunks:
pythonchunk = saleae_file.contents.chunks[0] # Key attributes: chunk.initial_state # Starting logic level (0 or 1) chunk.transition_times # numpy array of transition timestamps (seconds) chunk.sample_rate # Capture rate in Hz chunk.begin_time # Capture start time chunk.end_time # Capture end time
Calculating Pulse Durations
pythonimport numpy as np times = np.array(chunk.transition_times) durations_ms = np.diff(times) * 1000 # Convert to milliseconds # If initial_state is 0 (LOW): # - Even indices (0, 2, 4...) = HIGH pulse durations # - Odd indices (1, 3, 5...) = LOW gap durations # If initial_state is 1 (HIGH): # - Even indices = LOW gap durations # - Odd indices = HIGH pulse durations
Helper Scripts
This skill includes helper scripts for common analysis tasks:
Protocol Analyzer
bash# Analyze signal characteristics python3 skills/logicmso/analyze_protocol.py capture.bin # Show detailed timing histogram python3 skills/logicmso/analyze_protocol.py capture.bin --histogram # Show detected timing clusters python3 skills/logicmso/analyze_protocol.py capture.bin --clusters # Export transitions to CSV python3 skills/logicmso/analyze_protocol.py capture.bin --export transitions.csv # Show raw transition values python3 skills/logicmso/analyze_protocol.py capture.bin --raw -n 50
See examples.md for full worked end-to-end captures: unknown-protocol triage, and UART, SPI, I2C, and 1-Wire decoding with runnable Python.
Common Protocol Patterns
UART (Asynchronous Serial)
- Idle state: HIGH
- Start bit: LOW (1 bit period)
- Data bits: 8 bits, LSB first
- Stop bit: HIGH (1-2 bit periods)
- Common baud rates: 9600, 19200, 38400, 57600, 115200
- Bit period calculation:
1/baud_rateseconds - Identifying features: Consistent bit periods, durations are multiples of base period
SPI (Serial Peripheral Interface)
- 4 signals: SCLK (clock), MOSI (master out), MISO (master in), CS (chip select)
- Clock polarity (CPOL): Idle clock state (0=LOW, 1=HIGH)
- Clock phase (CPHA): Sample edge (0=leading, 1=trailing)
- Data: Sampled on clock edges, typically 8 bits per transaction
- Identifying features: Regular clock signal, CS goes LOW during transaction
I2C (Inter-Integrated Circuit)
- 2 signals: SDA (data), SCL (clock)
- Idle state: Both HIGH (pulled up)
- Start condition: SDA falls while SCL is HIGH
- Stop condition: SDA rises while SCL is HIGH
- Data: 8 bits + ACK/NACK, MSB first
- Address: 7-bit (first byte after START)
- Identifying features: START/STOP conditions, 9 clock pulses per byte (8 data + ACK)
1-Wire
- Single signal: DQ (data/power)
- Idle state: HIGH (pulled up)
- Reset pulse: Master pulls LOW for 480us minimum
- Presence pulse: Slave responds LOW for 60-240us
- Write 0: LOW for 60-120us
- Write 1: LOW for 1-15us, then release
- Read: Master samples 15us after pulling LOW
Analysis Workflow
Step 1: Initial Exploration
pythonfrom saleae.mso_api.binary_files import read_file import numpy as np f = read_file("capture.bin") chunk = f.contents.chunks[0] print(f"Sample rate: {chunk.sample_rate/1e6:.1f} MHz") print(f"Duration: {chunk.end_time - chunk.begin_time:.3f}s") print(f"Initial state: {'HIGH' if chunk.initial_state else 'LOW'}") print(f"Transitions: {len(chunk.transition_times)}")
Step 2: Analyze Timing Patterns
pythontimes = np.array(chunk.transition_times) durations_us = np.diff(times) * 1e6 # microseconds # Separate HIGH and LOW durations high_idx = 0 if chunk.initial_state == 0 else 1 high_durations = durations_us[high_idx::2] low_durations = durations_us[(1-high_idx)::2] print(f"HIGH pulses: min={min(high_durations):.1f}us, max={max(high_durations):.1f}us") print(f"LOW gaps: min={min(low_durations):.1f}us, max={max(low_durations):.1f}us") # Find unique timing values (cluster detection) unique_high = sorted(set(round(d, -1) for d in high_durations)) # Round to 10us unique_low = sorted(set(round(d, -1) for d in low_durations)) print(f"HIGH clusters: {unique_high}") print(f"LOW clusters: {unique_low}")
Step 3: Identify Protocol
Based on timing patterns:
- UART: Consistent bit periods, durations are multiples of base period, idles HIGH
- SPI/I2C: us-scale timing, needs clock signal analysis, look for regular patterns
- 1-Wire: Reset pulses ~480us, data pulses 1-120us
Step 4: Decode
Once protocol is identified, decode based on protocol rules. For unknown/custom protocols, analyze the timing clusters and bit patterns to determine encoding scheme.
UART Decoding Example
pythonfrom saleae.mso_api.binary_files import read_file import numpy as np f = read_file("uart_capture.bin") chunk = f.contents.chunks[0] times = np.array(chunk.transition_times) BAUD = 115200 BIT_PERIOD = 1 / BAUD def decode_uart_byte(start_time, times, bit_period): """Decode a single UART byte starting at start_time.""" byte_val = 0 for bit_num in range(8): # Sample at center of each bit (1.5, 2.5, 3.5... bit periods from start) sample_time = start_time + (1.5 + bit_num) * bit_period # Find state at sample_time idx = np.searchsorted(times, sample_time) state = (chunk.initial_state + idx) % 2 if state: byte_val |= (1 << bit_num) # LSB first return byte_val # Find start bits (falling edges when idle HIGH) decoded_bytes = [] i = 0 while i < len(times) - 1: # Look for falling edge (start bit) if chunk.initial_state == 1 or i > 0: byte_val = decode_uart_byte(times[i], times, BIT_PERIOD) decoded_bytes.append(byte_val) # Skip to next potential start bit (after stop bit) i += 1 while i < len(times) and times[i] < times[i-1] + 10 * BIT_PERIOD: i += 1 else: i += 1 print("Decoded:", bytes(decoded_bytes))
CTF Tips
- Unknown protocol: Start with
analyze_protocol.py --clustersto see timing distribution - Multiple channels: Export each channel separately, identify clock vs data lines
- Inverted signals: Some captures have inverted logic levels
- Timing variations: Real hardware has jitter, use threshold-based detection
- Partial captures: Check if capture starts mid-transmission
- Custom protocols: Look for repeating patterns, identify sync/framing bytes
Troubleshooting
"No module named 'saleae.mso_api'"
First verify it's truly missing:
bashpython3 -c "from saleae.mso_api.binary_files import read_file"
Only if the import fails, install it:
bashpip install saleae-mso-api
Empty or corrupt file
Check file size and try re-exporting from Saleae Logic software.
No transitions detected
- Signal may be constant (stuck high/low)
- Check if correct channel was exported
- Verify trigger settings in original capture
Timing seems wrong
- Check sample rate matches original capture settings
- Verify time units (seconds vs milliseconds vs microseconds)


