
Postdissector de Wireshark basado en Lua que descifra y analiza las IEs de proveedor 802.11 de Ubiquiti AirMAX/RouterBoard y las convierte en campos filtrables.
Un disector de Wireshark/tshark para los AirMAX (AC + M) de Ubiquiti y los elementos de información de proveedor complementarios de Mikrotik / RouterBoard, escrito en Lua.
Es la contraparte en el cable de pyrmax: la lógica de decodificación refleja ese paquete de Python campo por campo. El formato de paquetes AC se obtuvo mediante ingeniería inversa del binario de firmware AirMAX AC; los diseños de M y RouterBoard provienen de notas publicadas anteriormente sobre el IE de proveedor AirMAX-M más antiguo.
AirMAX viaja dentro de los IE específicos de proveedor de 802.11 (etiqueta 221). Wireshark ya analiza la trama 802.11 y muestra el IE de proveedor como un blob opaco; wiremax toma ese blob, lo descifra (AES-128-ECB, clave derivada de las MAC de la trama) y representa un subárbol analizado y filtrable.
Tag: Vendor Specific: Ubiquiti Inc (built-in 802.11 dissector)
AirMAX AC (Vendor Specific IE) ← added by wiremax
Flags: 0x02 ( .... ..1. = Encrypted: True )
Message Type: Beacon (1)
Encrypted Length: 48
[Decrypted payload (AES-128-ECB)]
[AES Key …: 1f162a13… (dst=broadcast)]
Version: 9
Source MAC: 24:5a:4c:44:57:fd
Radio MAC (mac_0c): 24:5a:4c:44:57:fd
Capability Flags: 0x0000003e
Mixed Mode: 0
Radioname: LB1
SSID: labalUBI2
TLV: Radioname (1), len 3
TLV: SSID (2), len 9
TLV: Padding (0)
El diseño descifrado depende de Message Type: los beacons llevan mac_0c / cap_flags / mixed_mode + TLVs de nombre (arriba); assoc req/resp llevan chainmasks, cap_flags y campos finales controlados por versión (field_9c, rssi, fwname, txpower); probe req es solo cabecera; deauth lleva un nonce de jiffies y un token de autenticación opaco (y su MAC de origen está enmascarada con XOR, que wiremax desenmascara).
| Variante | Cobertura |
|---|---|
| AirMAX AC | cabecera externa + cabecera común descifrada (version + src_mac) + cuerpo por tipo de mensaje y controlado por versión (beacon, assoc req/resp, probe req, deauth) + TLVs de nombre |
| AirMAX M | cáscara externa + 9 bytes documentados descifrados (version, msg_type, src_mac, enable) + resto crudo |
| RouterBoard (Mikrotik) | OUI/tipo + sub-IE + nombre de dispositivo (texto claro) |
Las regiones de bytes no documentadas se muestran crudas (Unknown [n:len]), nunca inventadas: la misma disciplina que pyrmax y la base de conocimiento ac/.
Un postdisector (no reemplaza al disector 802.11 integrado, sino que se ejecuta después de él). Para cada trama:
wlan.tag.oui más wlan.sa / wlan.da mediante extractores Field;00:27:22), M (00:15:6d) o RouterBoard (00:0c:42) — para AC/M también requiere el tipo de OUI FF FF FF, porque 00:15:6d se comparte con Mikrotik y con otro IE de Ubiquiti que aparece en la misma trama;AES-128-ECB, clave = HMAC-SHA1(dst_mac, src_mac)[:16], probando primero el destino 802.11 capturado y recurriendo a broadcast (las tramas de identidad usan la clave de broadcast incluso cuando son unicast) — el ganador es aquel cuya MAC de origen incrustada en la carga útil descifrada coincide con el origen 802.11;La criptografía (SHA-1, HMAC-SHA1, descifrado AES-128) es Lua puro autocontenido — Wireshark no expone ninguna API criptográfica a Lua. Las cargas útiles son unos pocos bloques de 16 bytes, por lo que el rendimiento es irrelevante. La clave se deriva de MAC públicas, por lo que esto es ofuscación, no seguridad.
Puntual (sin instalación):
tshark -X lua_script:airmax.lua -r capture.pcap -V
wireshark -X lua_script:airmax.lua capture.pcap
Persistente (GUI de Wireshark + tshark): coloca toda la carpeta wiremax/ en un directorio de complementos Lua personales. La ruta exacta se muestra en Wireshark en Ayuda ▸ Acerca de Wireshark ▸ Carpetas ▸ Complementos Lua personales; los valores predeterminados son:
| Sistema operativo | Ruta predeterminada de complementos Lua personales |
|---|---|
| Linux | ~/.local/lib/wireshark/plugins/ |
| macOS | ~/.local/lib/wireshark/plugins/ (o ~/.config/wireshark/plugins/) |
| Windows |
Un enlace simbólico es ideal mientras se itera: los cambios se detectan al recargar (Ctrl+Shift+L) sin necesidad de copiar:
ln -s "$PWD" ~/.local/lib/wireshark/plugins/wiremax
Wireshark carga recursivamente todos los .lua dentro de la carpeta de complementos; los submódulos en wsairmax/ son módulos simples (inofensivos si se cargan de forma independiente) y test/selftest.lua no hace nada dentro de Wireshark.
Ejemplos:
tshark -r cap.pcap -Y 'airmax.ac.ssid' -T fields -e wlan.sa -e airmax.ac.ssid
tshark -r cap.pcap -Y 'airmax.m.msg_type == 1' -T fields -e airmax.m.src_mac
tshark -r cap.pcap -Y 'airmax.rb.device_name' -T fields -e airmax.rb.device_name
Las capturas deben ser en modo monitor 802.11 (radiotap); las tramas de gestión que transportan los IE (beacon / probe response / assoc) no están cifradas en la capa 802.11 — solo la carga útil de AirMAX dentro del IE está envuelta en AES, y wiremax la desenvuelve.
test/selftest.lua ejecuta la criptografía y los decodificadores bajo el intérprete Lua 5.4 independiente (sin Wireshark), comprobando contra vectores de referencia más los vectores SHA-1 / HMAC estándar de RFC-2202:
lua test/selftest.lua # from this directory
De extremo a extremo contra una captura real (pyrmax incluye pcaps de muestra en tests/samples/):
tshark -X lua_script:airmax.lua -r airmax_ac_beacon.pcap -V
tshark -X lua_script:airmax.lua -r airmax_m_probe_response.pcap -V
wiremax/
├── airmax.lua # Wireshark entry: ProtoFields + postdissector + tree
├── wsairmax/ # pure-Lua modules (no Wireshark dependency)
│ ├── util.lua # hex / MAC helpers
│ ├── sha1.lua # SHA-1 + HMAC-SHA1
│ ├── aes.lua # AES-128 ECB decrypt
│ ├── crypto.lua # key derivation + decrypt (mirrors pyrmax/_crypto.py)
│ ├── ac.lua # AirMAX AC decoder (outer ksy: airmax_ac; payload hand-written per 09b)
│ ├── m.lua # AirMAX M decoder (ksy: airmax_m[_payload])
│ └── routerboard.lua # RouterBoard decoder (ksy: routerboard)
├── test/selftest.lua # standalone-Lua test harness
└── README.md
msg_type de M etiqueta 1 = Beacon según las notas más antiguas de AirMAX-M, pero el valor 1 también se observa en respuestas de sonda (probe responses) — el espacio de tipos de mensaje de M no está completamente mapeado, así que confía en el número crudo.enc_len, un u16 big-endian (09b §3); la antigua lectura de 1 byte era accidentalmente correcta solo para cargas útiles < 256.version < 9 tampoco están verificados (TX solo emite 9). Consulta los vectores sintéticos en test/selftest.lua.Cuando se añade/renombra un campo en pyrmax (src/pyrmax/ac.py o sus esquemas ksy/*.ksy), reprodúcelo en el decodificador wsairmax/*.lua correspondiente y, si es visible para el usuario, añade un ProtoField en airmax.lua. Los vectores de referencia en test/selftest.lua provienen de los pcaps de muestra de pyrmax, por lo que cualquier divergencia aparecerá allí.
%APPDATA%\Wireshark\plugins\| Filtro | Significado |
|---|
airmax.ac.msg_type | tipo de mensaje: 1 Beacon, 2 Assoc Req, 3 Assoc Resp, 4 Probe Req, 0xC Deauth |
airmax.ac.flags.encrypted | bit de cifrado (0x02) |
airmax.ac.version | versión de formato descifrada (la puerta; TX emite 9) |
airmax.ac.src_mac | MAC de origen de la cabecera descifrada (deauth la desenmascara) |
airmax.ac.mac_0c | MAC de radio del beacon (BSSID) |
airmax.ac.cap_flags + airmax.ac.cap_flags.chanbw/.high_density/.auth_deauth/.compat_11ax | campo de bits de capacidad (§11) |
airmax.ac.radioname, airmax.ac.ssid, airmax.ac.fwname | cadenas de nombre / firmware del AC |
airmax.ac.jiffies_nonce, airmax.ac.enc_token | nonce de deauth + token de autenticación opaco |
airmax.ac.tlv.tag / .len / .data | recorrido TLV crudo |
airmax.m.version, airmax.m.msg_type, airmax.m.src_mac, airmax.m.enable | campos documentados de M |
airmax.m.unknown_rest | cola M no documentada |
airmax.rb.device_name | nombre de dispositivo RouterBoard |
airmax.ac.key / airmax.m.key | clave AES derivada (generada, para verificación) |