← Back to Blog

Capturing a Car Key Signal with Flipper Zero — and Visualizing It with Python

Modern car keys communicate over Sub-GHz radio frequencies using rolling codes. Here's how I used a Flipper Zero to capture the raw signal, read it programmatically via USB serial, and visualize the pulse data with Python — without ever decoding the key.

Background: How Car Keys Work

Most modern car keys use Sub-GHz radio frequencies — typically 433 MHz or 868 MHz depending on the region — to communicate with the vehicle. When you press the unlock button, the key transmits a short burst of radio signal that the car's receiver decodes.

The critical security mechanism is rolling code (also called hopping code). Instead of sending the same signal every time, the key and car share a synchronized counter. Each button press generates a new, unique code derived from that counter. Even if an attacker captures the signal, replaying it won't work — the car has already moved to the next code in the sequence.

Rolling code: Each key press generates a cryptographically unique signal. Replay attacks don't work — the car rejects any previously used code.

Equipment and Setup

The setup is straightforward:

Important: Only capture signals from devices you own. Intercepting radio communications without authorization is illegal in most jurisdictions.

Step 1: Finding the Frequency

The first step is identifying which frequency the key uses. Flipper Zero has a built-in Frequency Analyzer under Sub-GHz — it scans the spectrum in real time and shows signal strength at each frequency.

With the Frequency Analyzer running, I pressed the car key button. The analyzer immediately showed a strong signal at 440.17 MHz. This is slightly above the standard 433.92 MHz — the exact frequency varies by manufacturer.

Detected Frequency
440.175 MHz
Modulation
OOK650 (AM)
Protocol
RAW (rolling code)
Samples captured
928 samples

Step 2: Capturing the Raw Signal

With the frequency known, I used Flipper's Sub-GHz → Read RAW mode to capture the signal. Unlike the standard Read mode — which tries to decode known protocols — Raw mode records the exact sequence of pulse durations without interpretation.

This is important for rolling code signals: Flipper can't decode them (by design), but it can record the raw waveform. The captured file is saved in Flipper's .sub format on the SD card.

Step 3: Reading the File via Python

Flipper Zero exposes a serial CLI over USB. Using pyserial, I connected directly to the device and read the saved .sub file:

import serial import time ser = serial.Serial('/dev/cu.usbmodemflip_Ridiroc1', 115200, timeout=2) time.sleep(0.5) ser.write(b'storage read /ext/subghz/Xxyy.sub\r\n') time.sleep(2) response = ser.read(4096).decode('utf-8', errors='ignore') print(response) ser.close()

The output contains the raw signal data in Flipper's format:

Filetype: Flipper SubGhz RAW File Version: 1 Frequency: 440175000 Preset: FuriHalSubGhzPresetOok650Async Protocol: RAW RAW_Data: 1224 -210706 99 -220660 41773 -71436 13221 -1802 ...

Each number in RAW_Data represents a pulse duration in microseconds. Positive values are periods when the signal is ON (carrier present), negative values are periods when the signal is OFF (silence). This is the fundamental structure of OOK (On-Off Keying) modulation.

Step 4: Visualizing the Signal

With the raw pulse data, I built a time-domain visualization using matplotlib. Each value is converted into a step in the waveform — positive values raise the signal to 1, negative values drop it to 0:

values = list(map(int, raw_data.strip().split())) x, y = [], [] current_time = 0 for v in values: duration = abs(v) level = 1 if v > 0 else 0 x.append(current_time) y.append(level) current_time += duration x.append(current_time) y.append(level) plt.step(x, y, where='post', color='#58a6ff') plt.fill_between(x, y, step='post', alpha=0.3)
Car key Sub-GHz signal visualization
Captured car key signal at 440.175 MHz — first 500ms of the recording

Reading the Signal

The visualization reveals the structure of the transmission:

The long silence between pulses (~200ms gaps) is typical of rolling code systems. The preamble and sync pulses help the car's receiver synchronize before the actual data arrives. The data block itself encodes the rolling code — a value that changes with every button press.

Why Rolling Code Can't Be Replayed

Even though we captured the exact waveform, sending it back to the car would fail. The car maintains an internal counter in sync with the key. When it receives a signal, it checks: "Is this code within the expected range of my current counter?" If the code has already been used — or is too far ahead — it's rejected.

More sophisticated attacks like RollJam (by Samy Kamkar) exist — they simultaneously jam and capture two consecutive codes, then replay the first one while the car never received it. But that's a different story.

Takeaways

Written by Emrah Ustundag · Explore the Tools →