Unified rfcat helper for sub-GHz RF work
Project description
rfox
Unified rfcat helper for sub-GHz RF work. One CLI, one interactive menu, one capture format. Built on top of rfcat / rflib so it works with any YARDStickOne, RfCat-compatible CC1111 dongle, or DonsDongle.
./rfox # interactive menu
./rfox decode --hex aabbccdd -m manchester # direct CLI
⚠️ Legal & ethics — read this first
rfox is a dual-use security research tool. Several of its commands —
notably jam, scanjam, rolljam, brute, and fuzz — actively transmit
RF energy and can interfere with, disable, or attempt to gain unauthorised
access to wireless systems. In most jurisdictions this is illegal unless
you own the target equipment or have explicit, written authorisation to
test it.
You are responsible for understanding and complying with the law in your country. As a non-exhaustive starting point:
- United States — 47 U.S.C. § 333 prohibits "wilful or malicious interference" with any radio communication. The FCC enforces this and has fined private individuals six-figure amounts for jamming. The Computer Fraud and Abuse Act (CFAA) covers unauthorised access to electronic systems.
- United Kingdom — the Wireless Telegraphy Act 2006 makes unlicensed intentional interference an offence; the Computer Misuse Act 1990 covers unauthorised access.
- European Union — RED (2014/53/EU) and national transposition acts regulate spectrum use; most member states have similar criminal prohibitions on jamming.
- Australia, Canada, Japan, … — similar regimes exist almost everywhere.
Use rfox only for:
- security research on equipment you own;
- formally authorised penetration testing engagements (with a written scope-of-work / authorisation letter from the asset owner);
- CTF challenges and educational exercises in shielded enclosures (RF isolation chamber, Faraday cage / bag) where transmissions cannot leak into licensed bands;
- compliance and interoperability testing in a controlled lab environment.
Do not use rfox to:
- attack, disrupt, or interfere with infrastructure, vehicles, premises, devices, or services that are not yours, or for which you do not hold written authorisation;
- jam, intercept, or replay communications belonging to other people;
- bypass access controls (gates, garage doors, vehicles, alarms, locks) on property you do not own;
- target safety-critical, life-safety, public-safety, or aviation systems under any circumstances.
By using this software, you acknowledge that you alone are responsible
for ensuring your use is lawful and authorised. The authors and
contributors provide rfox without warranty of any kind (see
LICENSE) and are not liable for any direct
or indirect harm, legal action, regulatory penalty, equipment damage, or
data loss arising from its use or misuse.
If any of this is unclear, stop and consult a qualified lawyer in your jurisdiction before using the offensive commands.
Why rfox
rfcat ships a powerful Python library (rflib) and an interactive shell,
but day-to-day RF work tends to be the same handful of recipes — capture a
key fob, replay it, sweep a band, decode some Manchester, find the rolling
counter — repeated with slightly different parameters. The community
RfCatHelpers project showed
how useful those recipes are as standalone scripts, but each one
re-implements the same dongle init, defines its own incompatible CLI, and
uses its own ad-hoc capture format.
rfox consolidates that workflow into:
- one tool with consistent flags everywhere (
-fis always Hz,-ris always bps,-mis always one ofOOK / 2FSK / GFSK / MSK / 4FSK); - an interactive menu for when you don't remember the flags — it walks you through every option with the default shown;
- one capture file format (libpcap, DLT_USER0, RFCT pseudo-header) so anything you capture in one mode can be replayed, decoded, diffed, or CRC-checked by any other command;
- named profiles (
~/.rfcat/profiles.json) and built-in presets for common protocols, so you stop re-typing six flags for every command.
The interactive menu and the CLI share a single dispatcher, so they can never drift apart — adding a new command exposes it in both modes automatically.
Installation
rfox is a standalone tool that depends on rfcat for the underlying rflib library.
# 1. install rfcat (provides rflib)
pip install rfcat
# 2. clone and install rfox
git clone https://github.com/qu-crypt/rfox.git
cd rfox
python3 -m venv .venv && source .venv/bin/activate
pip install rfcat # runtime dependency
pip install -e .
Optional dependencies:
pip install -e '.[specan]' # enables `rfox specan` (PySide6)
System libraries:
- libusb-1.0 (Linux:
apt install libusb-1.0-0, macOS:brew install libusb, Windows: see the rfcat README) - non-root USB access on Linux requires the udev rules from the rfcat repo's
etc/udev/
Verify:
./rfox --help
./rfox devices # lists attached dongles
Quick start
Five things you'll almost certainly want to do:
# 1. Sweep a band looking for activity
./rfox sweep --start 433e6 --stop 434e6 --step 50e3 --ascii-bar
# 2. Capture five presses of a 433 MHz remote into a pcap
./rfox replay capture --preset ev1527 -n 5 -o /tmp/garage.pcap
# 3. Replay them
./rfox replay replay --input /tmp/garage.pcap --use-capture-cfg
# 4. Find the rolling-counter byte (or rule it out)
./rfox diff --input /tmp/garage.pcap
# 5. Identify the protocol's checksum
./rfox crc --input /tmp/garage.pcap
No dongle? The analysis subcommands work entirely on captures or hex strings:
./rfox decode --hex aabbccdd -m auto
./rfox find-sync --hex aaaaaad391deadbeef
./rfox crc --hex 12345612fd
./rfox diff --hex aabbcc00 --hex aabbcc01 --hex aabbcc02
Command reference
| group | command | what it does | needs hw |
|---|---|---|---|
| dongle | devices |
list connected RfCat dongles | ✓ |
specan |
open the PySide6 spectrum analyser | ✓ | |
| RX | scan |
RX loop, hex-print frames, optional pcap log | ✓ |
sweep |
RSSI sweep across --start..--stop, optional CSV |
✓ | |
logger |
headless RX → append every frame to a pcap | ✓ | |
| TX | transmit |
binary string → OOK (raw or PWM-encoded) | ✓ |
tx-hex |
raw bytes given as hex on the CLI | ✓ | |
jam |
one frequency for N seconds ⚠️ | ✓ | |
scanjam |
two dongles: scan + reactive-jam ⚠️ | ✓✓ | |
brute |
iterate a key space and transmit each value ⚠️ | ✓ | |
fuzz |
bit/byte-mutate a seed frame, retransmit ⚠️ | ✓ | |
| capture/replay | replay capture |
record N frames to a pcap | ✓ |
replay replay |
replay a pcap | ✓ | |
rolljam |
jam → capture 1 → unjam → capture 2 → replay 1 ⚠️ | ✓✓ | |
| analysis | decode |
manchester/diff-manchester/PWM/raw decoders | |
find-sync |
candidate sync words in a capture | ||
find-repeats |
repeating bit patterns | ||
crc |
try CRC-8/CRC-16 polynomials over capture tails | ||
diff |
bit-by-bit diff (find rolling-counter fields) | ||
decode-wav |
OOK decoder for WAV recordings | ||
| workflow | profile {save,show,list,delete} |
named radio configs | |
preset {list,show} |
built-in protocol presets |
Commands marked ⚠️ are the ones you should only use on equipment you own or are formally authorised to test. See the disclaimer above.
✓✓ means the command requires two dongles.
Each command also has full --help:
./rfox replay capture --help
./rfox find-sync --help
Capture file format
Every capture/replay command shares one libpcap file
(magic 0xa1b2c3d4, link type DLT_USER0 = 147). Each packet payload
starts with a 24-byte RFCT pseudo-header recording the radio
configuration at capture time, followed by the raw on-air bytes:
struct rfct_pseudo_hdr { // little-endian
uint8_t magic[4]; // "RFCT"
uint8_t version; // 1
uint8_t modulation; // 0=OOK 1=2FSK 2=GFSK 3=MSK 4=4FSK
uint32_t freq_hz;
uint32_t drate_bps;
uint32_t chanbw_hz;
int16_t rssi_dbm10; // dBm * 10, signed
uint16_t sync_word; // 0 if none
uint8_t payload[];
};
Read with the dataclass-based reader:
from rflib.rfox import pcap
for frame in pcap.read("garage.pcap"):
print(frame.ts, frame.cfg.summary(), frame.payload.hex())
Or open it in Wireshark / tshark — frames will appear as data under
USER0. A future Lua dissector could decode the pseudo-header field by
field, but the raw bytes are usable today with any pcap parser.
Profiles & presets
Save a configuration once, reuse it forever:
./rfox profile save mygate -f 433.92e6 -r 2400 -m OOK --chanbw 325000
./rfox replay replay --input cap.pcap --profile mygate
Or jump straight to a built-in:
./rfox preset list
./rfox scan --preset ev1527
./rfox tx-hex --preset keyfob315 --hex aabbcc
CLI flags override the profile/preset, so --preset ev1527 --freq 433.95e6
works.
Built-in presets:
| name | freq | drate | mod | typical use |
|---|---|---|---|---|
ev1527 |
433.92 MHz | 2400 | OOK | generic 433 MHz remotes |
pt2262 |
433.92 MHz | 1200 | OOK | older garage / gate openers |
keeloq |
433.92 MHz | 2000 | OOK | rolling-code keyfobs |
keyfob315 |
315 MHz | 2400 | OOK | US automotive key fobs |
srd868 |
868.35 MHz | 4800 | 2FSK | EU short-range devices |
ism915 |
915 MHz | 38 400 | 2FSK | US ISM band |
tpms433 |
433.92 MHz | 19 200 | 2FSK | tyre-pressure sensors |
Adding a new command
Drop a module under rflib/rfox/commands/, expose three functions:
HELP = "one-line description shown in --help and the menu"
def add_args(parser):
"""Attach argparse args. Use _common.add_radio_args / add_dongle_args."""
def prompt(args): # optional
"""Walk the user through args interactively."""
def run(args):
"""Do the work. Return an exit code."""
Register it in rflib/rfox/commands/__init__.py. The CLI dispatcher and
the interactive menu both pick it up automatically.
Use _common.cfg_from_args(args) to honour --preset / --profile
before merging in CLI overrides — that's how every existing command keeps
its behaviour consistent.
Tests
python -m unittest tests.test_rfox
The pcap roundtrip, presets, profiles, and analysis subcommands
(decode, find-sync, find-repeats, crc, diff, decode-wav) are
covered without hardware. The hardware-using TX commands are smoke-tested
by patching open_dongle to return FakeRfCat.
Versioning & stability
rfox is versioned independently of rfcat. Anything in
rflib.rfox.commands is part of the public CLI; flag names and the
pcap pseudo-header layout are stable within a major version. Anything
under rflib.rfox (other than the public command modules) is internal
and may change without notice.
Contributing
Pull requests welcome. Please:
- Open an issue first for non-trivial changes.
- Add a test under
tests/test_rfox.pythat exercises your code without hardware (useFakeRfCatfor TX commands). - Match the existing argparse flag style (
-f/--freqis Hz,-r/--drateis bps, etc.). - Don't add commands that are exclusively useful for unauthorised attacks.
By contributing, you agree your contribution is licensed under the MIT
License (see LICENSE).
License
rfox's own source — the entry script, the rflib/rfox/ package,
and tests/test_rfox.py — is released under the MIT License. See
LICENSE.
It builds on top of rfcat, which is distributed under its own BSD-style license.
Acknowledgements
- @atlas0fd00m and the rfcat / rflib contributors for the underlying library and dongle firmware.
- @AndrewMohawk — the original RfCatHelpers project that mapped out the most useful day-to-day recipes and motivated this tool.
- The Ubertooth, GNU Radio, and HackRF communities for decades of prior art in sub-GHz tooling.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rfox-1.0.0.tar.gz.
File metadata
- Download URL: rfox-1.0.0.tar.gz
- Upload date:
- Size: 38.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
afba9f1123047e862c7684f4eec711eb48e3a5a3d4031849ae28d18778f9e637
|
|
| MD5 |
bd24258626546764acf55898fdad23b9
|
|
| BLAKE2b-256 |
b4a55bfb0d1a8695a89c32a6c20a82dff76edc248ceecc8294ac90820370fe40
|
Provenance
The following attestation bundles were made for rfox-1.0.0.tar.gz:
Publisher:
ci.yml on qu-crypt/rfox
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rfox-1.0.0.tar.gz -
Subject digest:
afba9f1123047e862c7684f4eec711eb48e3a5a3d4031849ae28d18778f9e637 - Sigstore transparency entry: 1393737518
- Sigstore integration time:
-
Permalink:
qu-crypt/rfox@44b6aee981d47e50b28635406871db80498d6db5 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/qu-crypt
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@44b6aee981d47e50b28635406871db80498d6db5 -
Trigger Event:
push
-
Statement type:
File details
Details for the file rfox-1.0.0-py3-none-any.whl.
File metadata
- Download URL: rfox-1.0.0-py3-none-any.whl
- Upload date:
- Size: 42.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
107a7c31e34f1380e5fd0dfbe69b934a2a94bd547b2838964515746f9da653d8
|
|
| MD5 |
929546aa64699867304fb3e29778d8cc
|
|
| BLAKE2b-256 |
828d59f52b5837090614e529884378c2ea3cdc39b69cad48e6d5a2ece990cbfb
|
Provenance
The following attestation bundles were made for rfox-1.0.0-py3-none-any.whl:
Publisher:
ci.yml on qu-crypt/rfox
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rfox-1.0.0-py3-none-any.whl -
Subject digest:
107a7c31e34f1380e5fd0dfbe69b934a2a94bd547b2838964515746f9da653d8 - Sigstore transparency entry: 1393737522
- Sigstore integration time:
-
Permalink:
qu-crypt/rfox@44b6aee981d47e50b28635406871db80498d6db5 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/qu-crypt
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@44b6aee981d47e50b28635406871db80498d6db5 -
Trigger Event:
push
-
Statement type: