
Codificador/decodificador para tramas del protocolo inalámbrico AirMAX de Ubiquiti; analiza capturas pcap/en vivo, descubre dispositivos, busca firmware vulnerable, crea paquetes malformados y emula objetivos AirMAX AC/M.
Decodificador y codificador de tramas del protocolo wire de Ubiquiti AirMAX, que cubre tanto la serie AC (firmware WA) como la serie M (firmware XW/XM).
ksy/. La carga útil de AC
(restringida por versión, conmutada por msg_type, con el desenmascarado XOR de deauth) está
escrita a mano en ac.py — esa lógica no encaja en Kaitai solo-parse.| Variante | Decodificación | Codificación | Iteración de pcap |
|---|---|---|---|
| AC | ✅ los 5 tipos de msg (beacon, assoc req/resp, probe req, deauth) | ✅ inverso byte-exacto + constructores + seal | ✅ |
| M | parcial (9 bytes documentados; el resto como unknown_rest) | ✅ hace round-trip de la cabecera documentada + unknown_rest | ✅ |
| IE de Routerboard.com (compañero de M) | ✅ nombre de dispositivo + lista de sub-IE | n/a | ✅ |
El formato wire de AC sigue
docs/ac_wire_format.md
([P]-confirmado contra ubnt_poll_host.ko). Todos los enteros multi-byte de AC son
big-endian.
pyrmax.ac.encode(AcPacket) -> bytes — inverso byte-exacto escrito a mano
de decode() (+ seal(), to_ie() y constructores build_*).pyrmax.m.encode(MPacket) -> bytes — lo mismo para M (+ build_m,
seal, to_ie).[open] de AC — mapa de bits exacto de cap_flags,
mixed_mode, field_14/field_9c (assoc_req), sta_field_68/ic_6b8
(assoc_resp), y el enc_token de deauth (extraíble pero su clave aún no tiene
ingeniería inversa, por lo que no puede verificarse). Ver
docs/ac_wire_format.md §11-§12.version < 9 contra capturas reales — el
constructor TX solo emite versión 9, por lo que las rutas de versiones inferiores (nombre
sin etiqueta, campos de cola ausentes) están implementadas según la especificación pero
sin verificar en el wire.unknown_rest) — actualmente opaco.pcap.FrameMeta: canal, RSSI,
tasa. Actualmente solo se rellenan timestamp/MACs/BSSID.tests/samples/ — actualmente:
airmax_ac_beacon.pcap (1 trama, beacon) y
airmax_m_probe_response.pcap (1 trama, respuesta de probe). Más variantes
(assoc req/resp, capturas multi-trama) aún son bienvenidas.scan (asociación forzada activa) y emulate
inyectan vía scapy (los extras [scan] / [emulate]). Ver
scan.py / emulate.py.El paquete incluye una CLI controlada por subcomandos. Ejecútalo como
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` y `discover` aceptan **ya sea** un archivo pcap/pcapng (posicional)
**o** una interfaz inalámbrica en vivo a través de `-i / --iface IFACE`. Ambas opciones son
mutuamente excluyentes. `scan` acepta las mismas opciones de origen (además de un
modo `--active` que solo se aplica a una interfaz en vivo); `emulate` es
exclusivo de interfaces en vivo.```sh
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
La captura en vivo asume que la interfaz ya está en modo monitor en el
canal de interés — pyrmax no configura ninguno de los dos. Requiere el
extra opcional [live] (pip install pyrmax[live]) que incorpora
pcapy-ng. Presione Ctrl-C para detener: parse informa cuántas tramas
transmitió; discover imprime el resumen agregado de dispositivos al salir.
Códigos de salida (compartidos por ambos comandos y ambos modos de origen): 0 en
caso de éxito (incluyendo "sin tramas AirMAX" — un resultado válido), 1 para
errores de formato de captura (tipo de enlace incorrecto, archivo malformado, no se puede abrir
la interfaz), 2 para archivo faltante o argumentos de origen no válidos.
parse — volcado por trama```shpython -m pyrmax parse capture.pcap
Cada trama de gestión 802.11 que transporta un IE de proveedor AirMAX produce un bloque: paquete AC, paquete M y cualquier IE acompañante de Routerboard.com que se encuentre en la misma trama.```
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
El mismo walker de una sola pasada se expone programáticamente como
pyrmax.pcap.iter_airmax(path) — produce un
AirmaxRecord(meta, ac, m, routerboard) por trama que contenga AirMAX, por lo
que no necesitas correlacionar manualmente los IEs de M y Routerboard.
discover — resumen del dispositivo```shpython -m pyrmax discover capture.pcap
Las tramas se agrupan por MAC de origen 802.11, los peers se acumulan, y las
observaciones de payload AC / payload M / nombre de dispositivo Routerboard se consolidan en
un solo bloque por dispositivo.```
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
La misma agregación también es una función pública:```python 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)
## Uso
### Decodificar un único mensaje AirMAX AC
El decodificador espera los bytes de la IE específica del proveedor 802.11 **a partir
del OUI** — el envoltorio de la IE (Element ID `0xDD` + Length) ya debe haber sido
eliminado. `src_mac` / `dst_mac` se extraen de los SA / DA de la trama 802.11
exterior.```python
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
)