Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
pyrmax — Decoder/encoder for Ubiquiti AirMAX wireless protocol frames; parse pcap/live captures, discover devices, scan for vulnerable firmware, craft malformed packets, and emulate AirMAX AC/M targets. | Kitploit
Tools/GitHubGitHub/infobyte/pyrmax
Vulnerability ScannersReverse EngineeringFuzzingNetwork SecurityWireless Security
GitHubinfobyte/pyrmax

pyrmax

Decoder/encoder for Ubiquiti AirMAX wireless protocol frames; parse pcap/live captures, discover devices, scan for vulnerable firmware, craft malformed packets, and emulate AirMAX AC/M targets.

View Repository
31342 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

pyrmax

Decoder and encoder for Ubiquiti AirMAX wire-protocol frames, covering both the AC (WA firmware) and M (XW/XM firmware) series.

  • Decode of the cleartext outer headers is driven by Kaitai Struct schemas under ksy/. The AC payload (version-gated, switched on msg_type, with the deauth XOR-unmask) is hand-written in ac.py — that logic doesn't fit parse-only Kaitai.
  • Encode is hand-written to mirror the decode path field-for-field.

Status

VariantDecodeEncodepcap iteration
AC✅ all 5 msg types (beacon, assoc req/resp, probe req, deauth)✅ byte-exact inverse + builders + seal✅
Mpartial (9 documented bytes; rest as unknown_rest)✅ round-trips the documented head + unknown_rest✅
Routerboard.com IE (companion to M)✅ device name + sub-IE listn/a✅

AC wire format follows docs/ac_wire_format.md ([P]-confirmed against ubnt_poll_host.ko). All AC multi-byte integers are big-endian.

TODO

  • pyrmax.ac.encode(AcPacket) -> bytes — hand-written byte-exact inverse of decode() (+ seal(), to_ie(), and build_* constructors).
  • pyrmax.m.encode(MPacket) -> bytes — same for M (+ build_m, seal, to_ie).
  • Pin down the [open] AC fields — cap_flags exact bit map, mixed_mode, field_14/field_9c (assoc_req), sta_field_68/ic_6b8 (assoc_resp), and the deauth enc_token (extractable but its key is not yet reverse-engineered, so it can't be verified). See docs/ac_wire_format.md §11-§12.
  • Verify the version < 9 AC decoders against real captures — the TX builder only ever emits version 9, so the lower-version (untagged name, missing tail fields) paths are implemented from the spec but unverified on the wire.
  • Reverse-engineer the rest of the M payload (bytes 9+ in unknown_rest) — currently opaque.
  • Surface radiotap metadata on pcap.FrameMeta: channel, RSSI, rate. Currently only timestamp/MACs/BSSID are populated.
  • Real capture fixtures in tests/samples/ — currently: airmax_ac_beacon.pcap (1 frame, beacon) and airmax_m_probe_response.pcap (1 frame, probe response). More variants (assoc req/resp, multi-frame captures) still welcome.
  • Packet sending — the scan (active force-assoc) and emulate commands inject via scapy (the [scan] / [emulate] extras). See scan.py / emulate.py.

Command-line tool

The package ships a sub-command driven CLI. Run it as python -m pyrmax COMMAND ....

usage: python -m pyrmax [-h] [--version] COMMAND ...

COMMAND
  parse     Print each AirMAX frame in detail.
  discover  Summarize devices observed in the capture.
  scan      Live-scan for AirMAX devices and flag vulnerable firmware.
  emulate   Emulate AirMAX AC/M devices (fake targets for scanners).

parse and discover take either a pcap/pcapng file (positional) or a live wireless interface via -i / --iface IFACE. The two are mutually exclusive. scan accepts the same source options (plus an --active mode that only applies to a live interface); emulate is live-only.

python -m pyrmax parse     capture.pcap          # offline
python -m pyrmax discover  capture.pcap

sudo python -m pyrmax parse     -i wlan0mon      # live (needs root)
sudo python -m pyrmax discover  -i wlan0mon

Live capture assumes the interface is already in monitor mode on the channel of interest — pyrmax does not configure either. It requires the optional [live] extra (pip install pyrmax[live]) which pulls in pcapy-ng. Press Ctrl-C to stop: parse reports how many frames it streamed; discover prints the aggregated device summary at exit.

Exit codes (shared by both commands and both source modes): 0 on success (including "no AirMAX frames" — a valid result), 1 for capture-format errors (wrong link type, malformed file, can't open interface), 2 for missing file or invalid source arguments.

parse — per-frame dump

python -m pyrmax parse capture.pcap

Each 802.11 management frame carrying an AirMAX vendor IE produces one block: AC packet, M packet, and any Routerboard.com companion IE found in the same frame.

Found 1 AirMAX frame(s) in capture.pcap: 0 AC, 1 M (1 with Routerboard companion).

=== Frame #0  [M]  ts=1765494974.955358 ===
  802.11   src=04:18:d6:0e:0c:42  dst=24:a4:3c:88:d8:22  bssid=04:18:d6:0e:0c:42
  AirMAX M
    version      15
    msg_type     BEACON  (raw=1)
    src_mac      04:18:d6:0e:0c:42
    enable       1
    unknown_rest b700000000000000000000040418d60e0c420000000000  (23B)
  Routerboard.com IE
    oui_type     0
    unknown      0000
    device_name  'AP Sur HY1315'
    sub_ie       subtype=1  (30B) 040000001f660902ff0f4150205375722048593133313500000000000000

The same single-pass walker is exposed programmatically as pyrmax.pcap.iter_airmax(path) — it yields one AirmaxRecord(meta, ac, m, routerboard) per AirMAX-bearing frame, so you don't need to correlate M and Routerboard IEs by hand.

discover — device summary

python -m pyrmax discover capture.pcap

Frames are grouped by 802.11 source MAC, peers accumulated, and AC payload / M payload / Routerboard device-name observations roll up into a single block per device.

2 device(s) observed in capture.pcap across 12 AirMAX frame(s).

  24:5a:4c:44:57:fd  (AC)
    radioname    'LB1'
    ssid         'labalUBI2'
    ac_msg_types BEACON
    ac_version   9
    cap_flags    0x0000003e
    mixed_mode   0
    frames       8
    first seen   1767046123.708745
    last seen    1767046129.012448
    peers        (broadcast only)

  04:18:d6:0e:0c:42  (M)
    device_name  'AP Sur HY1315'
    msg_types    BEACON
    m_version    15
    m_enable     1
    frames       4
    first seen   1765494974.955358
    last seen    1765494980.341110
    peers        24:a4:3c:88:d8:22

The same aggregation is also a public function:

from pyrmax.devices import summarize
from pyrmax.pcap import iter_airmax

devices = summarize(iter_airmax("capture.pcap"))
for mac, dev in devices.items():
    print(mac.hex(":"), dev.device_name or dev.radioname, dev.peers)

Usage

Decode a single AirMAX AC message

The decoder expects the bytes of the 802.11 Vendor Specific IE starting at the OUI — the IE wrapper (Element ID 0xDD + Length) must already be stripped. src_mac / dst_mac are lifted from the outer 802.11 frame's SA / DA.

from pyrmax import ac

packet = ac.decode(
    data,                              # bytes starting at b"\x00\x27\x22"
    src_mac="aa:bb:cc:dd:ee:ff",       # accepts str ("aa:bb:..." or "aa-bb-..."
                                       # or "aabbcc...") and raw 6-byte bytes
    dst_mac="ff:ff:ff:ff:ff:ff",       # optional — defaults to broadcast
)

packet.msg_type          # <MsgType.BEACON: 1>
packet.version           # 9 (the wire-format epoch / version gate)
packet.src_mac           # b'\xaa\xbb\xcc\xdd\xee\xff'  (integrity-checked)
packet.radioname         # "lab-rx-1"   (convenience prop, delegates to body)
packet.ssid              # "NetA"
packet.cap_flags         # 0x3e  (None for msg types that have no cap_flags)

# The per-message-type fields live on packet.body, one of:
#   BeaconBody | AssocReqBody | AssocRespBody | ProbeReqBody | DeauthBody
body = packet.body
if isinstance(body, ac.BeaconBody):
    body.mac_0c          # the radio's own MAC / BSSID
    body.cap_flags       # u32 capability bitfield (§11)
    body.mixed_mode      # u32 [open]
Download Tool