Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
pyrmax — Ubiquiti AirMAX 무선 프로토콜 프레임용 디코더/인코더; pcap/실시간 캡처를 파싱하고, 장치를 발견하며, 취약한 펌웨어를 스캔하고, 변형 패킷을 제작하며, AirMAX AC/M 대상을 에뮬레이션합니다. | Kitploit
도구/GitHubGitHub/infobyte/pyrmax
Vulnerability ScannersReverse EngineeringFuzzingNetwork SecurityWireless Security
GitHubinfobyte/pyrmax

pyrmax

Ubiquiti AirMAX 무선 프로토콜 프레임용 디코더/인코더; pcap/실시간 캡처를 파싱하고, 장치를 발견하며, 취약한 펌웨어를 스캔하고, 변형 패킷을 제작하며, AirMAX AC/M 대상을 에뮬레이션합니다.

저장소 보기
3181개월 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

pyrmax

Ubiquiti AirMAX 와이어 프로토콜 프레임용 디코더 및 인코더로, AC(WA 펌웨어) 및 M(XW/XM 펌웨어) 시리즈를 모두 다룹니다.

  • 클리어텍스트 외부 헤더의 디코드는 ksy/ 아래의 Kaitai Struct 스키마에 의해 처리됩니다. AC 페이로드 (msg_type에 따라 전환되고 버전에 따라 제한되며, deauth XOR 언마스크를 포함)는 ac.py에 수기로 작성되어 있습니다 — 이 로직은 파싱 전용 Kaitai에는 맞지 않습니다.
  • 인코드는 디코드 경로를 필드 단위로 그대로 반영하도록 수기로 작성되었습니다.

상태

변형디코드인코드pcap 반복
AC✅ 5가지 메시지 유형 모두 (beacon, assoc req/resp, probe req, deauth)✅ 바이트 단위 정확 역연산 + 빌더 + seal✅
M부분적 (문서화된 9바이트; 나머지는 unknown_rest로)✅ 문서화된 헤더 + unknown_rest 왕복 지원✅
Routerboard.com IE (M의 동반)✅ 장치 이름 + 하위 IE 목록n/a✅

AC 와이어 형식은 docs/ac_wire_format.md를 따릅니다 ([P]가 ubnt_poll_host.ko에 대해 확인함). 모든 AC 멀티바이트 정수는 빅엔디언입니다.

TODO

  • pyrmax.ac.encode(AcPacket) -> bytes — 손으로 작성된 decode()의 바이트 단위 정확 역연산 (+ seal(), to_ie(), build_* 생성자).
  • pyrmax.m.encode(MPacket) -> bytes — M에 대한 동일 기능 (+ build_m, seal, to_ie).
  • [open] AC 필드 확정 — cap_flags 정확한 비트맵, mixed_mode, field_14/field_9c (assoc_req), sta_field_68/ic_6b8 (assoc_resp), 그리고 deauth enc_token (추출은 가능하지만 키가 아직 역분석되지 않아 검증할 수 없음). docs/ac_wire_format.md §11-§12 참조.
  • 실제 캡처로 version < 9 AC 디코더 검증 — TX 빌더는 항상 버전 9만 생성하므로 하위 버전(태그 없는 이름, 누락된 꼬리 필드) 경로는 사양에 따라 구현되었지만 실제 와이어에서는 검증되지 않았습니다.
  • M 페이로드의 나머지 역분석 (unknown_rest의 9바이트 이후) — 현재 불투명함.
  • pcap.FrameMeta에 radiotap 메타데이터 노출: channel, RSSI, rate. 현재는 타임스탬프/MAC/BSSID만 채워집니다.
  • tests/samples/의 실제 캡처 픽스처 — 현재: airmax_ac_beacon.pcap (1프레임, beacon) 및 airmax_m_probe_response.pcap (1프레임, probe response). 추가 변형 (assoc req/resp, 다중 프레임 캡처)은 언제든 환영합니다.
  • 패킷 전송 — scan(능동 강제 assoc) 및 emulate 명령은 scapy를 통해 주입합니다 ([scan] / [emulate] 엑스트라). scan.py / emulate.py 참조.

명령줄 도구

이 패키지는 하위 명령 기반 CLI를 제공합니다. 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).

root@kitploit:~
`parse`와 `discover`는 pcap/pcapng 파일(위치 인자) **또는**
`-i / --iface IFACE`를 통한 라이브 무선 인터페이스 **중 하나**를 받습니다. 이 두 가지는
상호 배타적입니다. `scan`은 동일한 소스 옵션을 허용합니다(라이브 인터페이스에만 적용되는
`--active` 모드 포함); `emulate`는
라이브 전용입니다.```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

라이브 캡처는 관심 채널의 인터페이스가 이미 모니터 모드라고 가정합니다. pyrmax는 어느 것도 구성하지 않습니다. 선택적 [live] 엑스트라(pip install pyrmax[live])가 필요하며, 이를 통해 pcapy-ng가 함께 설치됩니다. Ctrl-C를 눌러 중지합니다. parse는 스트리밍한 프레임 수를 보고하고, discover는 종료 시 집계된 장치 요약을 출력합니다.

종료 코드(두 명령과 두 소스 모드 모두 공통): 성공 시 0("no AirMAX frames" 포함 — 유효한 결과), 캡처 형식 오류 시 1(잘못된 링크 타입, 손상된 파일, 인터페이스를 열 수 없음), 파일 누락 또는 잘못된 소스 인수 시 2.

parse — 프레임별 덤프```sh

python -m pyrmax parse capture.pcap

root@kitploit:~
AirMAX 벤더 IE를 포함하는 각 802.11 관리 프레임은 하나의
블록, 즉 AC 패킷, M 패킷 및 동일 프레임에서 발견되는 모든 Routerboard.com 컴패니언 IE를
생성합니다.```
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

동일한 단일 패스 워커는 프로그래밍 방식으로 pyrmax.pcap.iter_airmax(path)로 노출됩니다. 이는 AirMAX를 포함하는 프레임마다 AirmaxRecord(meta, ac, m, routerboard)를 하나씩 생성하므로 M 및 Routerboard IE를 수동으로 연관시킬 필요가 없습니다.

discover — 장치 요약```sh

python -m pyrmax discover capture.pcap

root@kitploit:~
프레임은 802.11 소스 MAC별로 그룹화되고, 피어가 누적되며, AC 페이로드 / M 페이로드 / Routerboard 장치 이름 관측값은 장치별 단일 블록으로 통합됩니다.```
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

동일한 집계는 또한 공용 함수입니다:```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)

root@kitploit:~
## 사용법

### 단일 AirMAX AC 메시지 디코딩

디코더는 802.11 Vendor Specific IE의 바이트가 **OUI에서
시작하는** — IE 래퍼(Element ID `0xDD` + Length)는 이미 제거되어
있어야 합니다. `src_mac` / `dst_mac`는 외부 802.11 프레임의 SA / DA에서
가져옵니다.```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
)

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]

msg_type는 BEACON / ASSOC_REQ / ASSOC_RESP / PROBE_REQ / DEAUTH 중 하나입니다. 버전 게이트된 꼬리 필드(field_9c, rssi, fwname, txpower, …)는 프레임의 version이 해당 임계값보다 낮을 때 None입니다. Deauth는 무결성 검사 전에 src_mac를 jiffies nonce와 XOR 해제하고, 16바이트 enc_token을 불투명한 바이트로 노출합니다(해당 키는 아직 역산되지 않았으므로 검증할 수 없습니다).

AC TLV 목록 순회

이름 TLV를 담는 본문(beacon, assoc_req)은 후행 Padding 항목을 포함한 스트림을 body.tlvs에 그대로 보존합니다. radioname, ssid, fwname은 패킷의 편의 속성으로 제공되고, 그 외의 모든 것은 원래 상태로 유지됩니다.```python body = packet.body for tlv in getattr(body, "tlvs", ()): if tlv.tag == ac.TlvTag.PADDING: continue print(f"{tlv.tag.name:<10} ({len(tlv.data)} bytes): {tlv.data!r}")

root@kitploit:~
### 단일 AirMAX M 메시지 디코딩

동일한 형태, 더 작은 표면 — M 페이로드의 9바이트만 문서화되어 있습니다;
나머지는 `unknown_rest`에 그대로 보존됩니다. **참고:** M 페이로드
자체에는 SSID 필드가 없습니다 — SSID 필드를 보려면 표준 802.11 SSID IE를
이를 포함하는 비콘 또는 프로브 응답에서 확인하세요(
`pyrmax.pcap`으로 반복 처리할 때 `FrameMeta.ssid`로 표시됩니다).```python
from pyrmax import m

packet = m.decode(data, src_mac="aa:bb:cc:dd:ee:ff")

packet.version           # 1
packet.msg_type          # <MsgType.BEACON: 1>
packet.src_mac           # b'\xaa\xbb\xcc\xdd\xee\xff'
packet.enable            # 1
packet.unknown_rest      # b'\xde\xad\xbe\xef...'   # opaque, RE pending

메시지 제작 (encode)

encode(packet, dst_mac=…)는 decode의 바이트 단위 정확한 역함수입니다 —
잘 구성된 프레임의 경우 encode(decode(x)) == x입니다. 본문을 직렬화하고(버전 게이트를 다시 적용하고, deauth의 경우 src_mac XOR 마스크를 다시 적용), AES 블록으로 패딩한 다음 packet.src_mac에서 파생된 키로 암호화하고 OUI 이후 바이트를 내보냅니다. 전체 0xDD 벤더 IE를 얻으려면 to_ie()로 감쌉니다.

build_* 생성자를 사용하면 중첩된 본문을 수동으로 조립하지 않아도 됩니다:```python from pyrmax import ac

A beacon (mac_0c defaults to src_mac; broadcast key, as beacons use)

pkt = ac.build_beacon(src_mac="24:5a:4c:44:57:fd", radioname="LB1", ssid="labalUBI2", cap_flags=0x3e) ie = ac.to_ie(ac.encode(pkt)) # full 802.11 vendor IE, ready to embed

An authenticated deauth — the encoder XOR-masks the src with the jiffies nonce

deauth = ac.build_deauth(src_mac="24:5a:4c:44:57:fd", jiffies_nonce=0xdeadbeef) raw = ac.encode(deauth, dst_mac="24:a4:3c:88:d8:22")

Others: ac.build_assoc_req / build_assoc_resp / build_probe_req, and m.build_m

root@kitploit:~
퍼징 / PoC의 경우 `seal()`은 **임의의** 평문을 암호화하고
외부 헤더를 구성합니다. 따라서 구조화된 인코더가 절대 생성하지 못하는
의도적으로 형식이 잘못된 페이로드(가짜 `msg_type`, 잘못된 길이, 하위 블록 본문 등)를
제작할 수 있습니다:```python
frame = ac.seal(b"\xde\xad\xbe\xef", src_mac="aa:bb:cc:dd:ee:ff",
                dst_mac="ff:ff:ff:ff:ff:ff", msg_type=0xEE)  # zero-padded to 16

구조화된 encode는 와이어로 전송할 수 없는 모든 것 (TLV 값 > 255바이트, 암호문 ≥ 0x101)에 대해 EncodeError를 발생시킵니다. seal은 설계상 허용적입니다.

pcap 또는 pcapng 파일에서 패킷 스트리밍

pyrmax.pcap은 802.11 관리 프레임 내부에서 AirMAX 벤더 IE를 찾고, 외부 802.11 헤더에서 MAC을 추출하며, 모든 것을 올바른 디코더로 전달합니다. .pcap 및 .pcapng는 자동으로 감지됩니다. 디코딩에 실패한 프레임 (잘못된 키, 손상된 프레임, 관련 없는 벤더 IE)은 조용히 건너뜁니다 — 반복은 EOF에서만 종료됩니다.```python from pyrmax import pcap

for meta, packet in pcap.iter_ac("capture.pcap"): print( f"{meta.timestamp:.3f} " f"{meta.src_mac.hex(':')} → {meta.dst_mac.hex(':')} " f"{packet.msg_type.name:<10} " f"radio={packet.radioname!r} ssid={packet.ssid!r}" )

for meta, packet in pcap.iter_m("capture.pcapng"): print( f"{meta.timestamp:.3f} " f"{packet.msg_type.name:<10} " f"src={packet.src_mac.hex(':')} enable={packet.enable}" )

root@kitploit:~
`meta`는 `FrameMeta(timestamp, src_mac, dst_mac, bssid)`입니다. Channel/RSSI
추출은 radiotap에서 TODO 목록에 있습니다.

### Routerboard.com IE에서 장치 이름 복구

AirMAX M 프레임에는 거의 항상 Mikrotik /
Routerboard.com 벤더 IE (OUI `00:0C:42`)가 동일한 802.11 관리
프레임에 함께 포함됩니다. 해당 IE의 subtype-1 하위 IE에는 장치 이름이 포함됩니다.```python
from pyrmax import pcap

for meta, packet in pcap.iter_routerboard("capture.pcap"):
    print(f"{meta.src_mac.hex(':')}  →  {packet.device_name!r}")
    # "AP Sur HY1315"

Routerboard 디코더는 독립형으로도 사용할 수 있습니다 — OUI에서 시작하는 IE 데이터를 전달하세요:```python from pyrmax import routerboard

packet = routerboard.decode(ie_data) packet.device_name # "AP Sur HY1315" packet.sub_ies # tuple of SubIe(subtype, data)

root@kitploit:~
Correlate Routerboard 패킷과 동일한 캡처의 M 패킷을 `meta.timestamp` + `meta.src_mac` 매칭으로 연관시키세요.

### `scan` — 취약한 장치 찾기

모니터 모드 인터페이스(또는 pcap)를 스니핑하여 AirMAX 장치를 찾고 취약한 장치를 표시합니다. `[scan]` 추가 기능(`scapy`)이 필요하며, 라이브 캡처에는 root 권한이 필요합니다.```sh
# offline — scan a capture (no root)
python -m pyrmax scan capture.pcap

# live, passive — read versions only from traffic that happens to fly
sudo python -m pyrmax scan -i wlan0mon --channel 36

# live, active — force AirMAX AC APs to disclose their firmware
sudo python -m pyrmax scan -i wlan0mon --channel 36 --active

버전이 취약성을 결정하는 방식은 변형(variant)마다 다릅니다. 두 개의 버전 번호가 관련됩니다: 와이어 형식 프로토콜 버전은 모든 AirMAX IE(비콘 포함)에 포함되고, AC 펌웨어 버전 (fwname)은 연결(association) 교환에서 만 전달됩니다.``` AirMAX AC ──> proto < 9 ? ──yes──────────────┐ │no ├──> VULNERABLE ▼ │ fw <= 8.7.20 ? ──yes───────────┘ ├──no───────> PATCHED └──unknown──> UNDETERMINED

AirMAX M ───> proto < 15 ? ──yes──> VULNERABLE └──no───────────> UNDETERMINED

root@kitploit:~
`proto`가 비콘에 있으므로 **오래된** 장치(AC epoch < 9, M
version < 15)는 **수동적으로** 탐지됩니다. AC에 대해 `PATCHED` 판정을 얻으려면
펌웨어 버전이 필요하므로, 캡처된 association 또는
`--active`(능동 핸드셰이크 — auth → assoc → assoc-resp
`fwname` 읽기 — 는 완전하고 바이트 단위로 충실한 Ubiquiti 스테이션 교환)가 필요합니다.

플래그: `--channel N` (채널 하나를 고정, 그렇지 않으면 호핑), `--seconds N`,
`--cutoff X.Y.Z` (`fw <= cutoff`이면 AC 취약, 기본값 `8.7.20`),
`--src MAC` (능동 프로브의 소스, 예: PTP 피어), `--vuln-only`,
`--no-set-channel`. 취약한 장치가 하나라도 발견되면 종료 코드는 **3**
(스크립팅에 유용), 그 외에는 `0`.```
AirMAX: 5 device(s) (3 AC, 2 M), 2 vulnerable (AC fw <= 8.7.20 or protocol version below the fixed epoch).

  1c:6a:1b:00:00:01  AC  VulnAC            ch36   v8.7.19    VULNERABLE     rssi=-40dBm  peers=0
  1c:6a:1b:00:00:04  M   VulnM             ch36   v14        VULNERABLE     rssi=-42dBm  peers=0
  1c:6a:1b:00:00:03  AC  PatchedAC         ch36   v8.7.24    patched        rssi=-41dBm  peers=0
  1c:6a:1b:00:00:02  AC  UndetAC           ch36   epoch9     undetermined   rssi=-41dBm  peers=0
  1c:6a:1b:00:00:05  M   UndetM            ch36   v15        undetermined   rssi=-43dBm  peers=0

emulate — 가짜 AirMAX 대상

하나 이상의 가짜 AirMAX AC/M 장치로 비콘을 보내고, AC의 경우 활성 스캐너가 에뮬레이션된 펌웨어 버전을 읽을 수 있도록 디스커버리 핸드셰이크에 응답합니다. [emulate] 추가 기능(scapy), 모니터 모드 인터페이스 및 루트 권한이 필요합니다. 실제 하드웨어 없이 scan을 테스트하는 데 유용합니다.```sh sudo python -m pyrmax emulate -i wlan1mon --channel 36
-d ac/8.7.19/VulnAC -d ac/9/UndetAC -d ac/8.7.24/PatchedAC
-d m/14/VulnM -d m/15/UndetM

root@kitploit:~
각 `-d`(반복 가능)는 `TYPE/VERSION[/SSID[/MAC]]` 형식이며, `/`로 구분되므로 MAC의 콜론이 안전합니다:

- `ac/8.7.19` — 최신 AC, 펌웨어 `8.7.19` (epoch 9, `fwname` 공개)
- `ac/9` — 최신 AC epoch, 펌웨어 문자열 **없음** → 스캐너가 `undetermined`로 인식
- `ac/7` — **구형** AC, 와이어 포맷 epoch 7 (< 9) → 취약, 비콘에서 감지됨
- `m/14` — AirMAX M, 버전 14 (< 15) → 취약
- `m/15` — 고정 epoch의 AirMAX M → undetermined

`-d`가 없으면 데모 플릿이 에뮬레이션됩니다. `--no-respond`는 비콘만 보냅니다(그러면 AC 펌웨어가 공개하지 않습니다). AC 펌웨어 버전은 assoc-resp에만 존재하므로 responder가 필요한 것입니다.

### 한 머신에서 둘을 함께 테스트

`scripts/hwsim_testbed.sh`는 `mac80211_hwsim`을 통해 두 개의 가상 라디오를 생성하므로 하드웨어 없이 하나에서 `emulate`를, 다른 하나에서 `scan`을 실행할 수 있습니다:```sh
sudo ./scripts/hwsim_testbed.sh up 36    # prints EMU_IFACE / SCAN_IFACE
# ...run emulate on EMU_IFACE and scan on SCAN_IFACE (two terminals)...
sudo ./scripts/hwsim_testbed.sh down

scripts/demo_5_devices.sh는 전체 작업을 처음부터 끝까지 수행합니다 — 라디오를 시작하고, 위의 5개 장치 플릿을 에뮬레이션하고, scan --active를 실행한 다음, 정리합니다:```sh sudo ./scripts/demo_5_devices.sh 36

root@kitploit:~
### 오류 처리

스키마 불일치 및 무결성 실패는 `pyrmax.DecodeError`를 발생시킵니다. 가장
흔한 원인은 잘못된 키입니다(해당 프레임에 대해
`decode()`에 제공된 잘못된 `src_mac` / `dst_mac`).```python
from pyrmax import ac, DecodeError

try:
    packet = ac.decode(data, src_mac=src, dst_mac=dst)
except DecodeError as exc:
    print(f"skipping frame: {exc}")

선택적 추가 기능

  • pip install pyrmax[pcap] (또는 uv sync --extra pcap) — dpkt를 불러와서 pyrmax.pcap.iter_ac(path) / iter_m(path)가 .pcap 또는 .pcapng 캡처 (링크 타입 DLT_IEEE802_11_RADIO)에서 패킷을 스트리밍할 수 있게 합니다. 형식은 파일의 매직 넘버로 자동 감지됩니다.
  • pip install pyrmax[live] — 모니터 모드 무선 인터페이스에서 라이브 캡처를 위한 pcapy-ng를 추가합니다. CLI의 -i / --iface 플래그와 프로그래밍 방식의 pyrmax.pcap.iter_airmax_live(iface) 생성기에서 사용됩니다.
  • pip install pyrmax[scan] — scan 명령을 위한 scapy를 추가합니다 (라이브 스니핑/디코딩 + 능동적인 force-assoc 핸드셰이크).
  • pip install pyrmax[emulate] — emulate 명령을 위한 scapy를 추가합니다 (비콘 주입 + 가짜 장치로 디스커버리 핸드셰이크에 응답).

한 번에 여러 개를 설치할 수 있습니다(예: uv sync --extra scan --extra emulate).

레이아웃```

pyrmax/ ├── ksy/ # Kaitai Struct source schemas │ ├── airmax_ac.ksy # AC cleartext outer header (payload decode is hand-written in ac.py) │ ├── airmax_m.ksy # M outer (OUI marker + encrypted blob) │ ├── airmax_m_payload.ksy # M decrypted payload (9 documented bytes) │ └── routerboard.ksy # Mikrotik / Routerboard.com vendor IE ├── src/pyrmax/ │ ├── init.py │ ├── ac.py # AC decode/encode API + AcPacket dataclass │ ├── m.py # M decode/encode API + MPacket dataclass │ ├── routerboard.py # Routerboard IE decoder + RouterboardPacket │ ├── pcap.py # iter_ac / iter_m / iter_routerboard / iter_airmax / iter_airmax_live │ ├── devices.py # summarize() — per-device aggregation │ ├── vuln.py # firmware-version parse + is_vulnerable() │ ├── scan.py # Scanner — live/pcap discovery + active handshake + vuln verdict │ ├── emulate.py # Emulator — fake AC/M targets (scapy) │ ├── main.py # python -m pyrmax CLI │ ├── exceptions.py │ ├── _crypto.py # AES-128-ECB + HMAC-SHA1 KDF (internal) │ └── _generated/ # kaitai-struct-compiler output (committed) ├── scripts/ │ ├── hwsim_testbed.sh # two virtual radios (mac80211_hwsim) for scan<->emulate │ └── demo_5_devices.sh # end-to-end 5-device emulate + scan demo └── tests/ ├── samples/ # raw frame captures (currently empty) ├── test_ac.py ├── test_m.py ├── test_crypto.py ├── test_pcap.py ├── test_routerboard.py ├── test_devices.py ├── test_cli.py └── test_integration.py # real-capture round-trips

root@kitploit:~
## 개발```sh
uv sync                    # create .venv and install runtime + dev deps
uv run pytest              # run tests
uv run ruff check          # lint
uv run pyright             # static type check

설정은 pyproject.toml ([tool.pyright])에 있습니다:

  • typeCheckingMode = "basic" — 구조적 문제를 잡아내면서 dpkt / kaitaistruct / pycryptodome 경계와 씨름하지 않습니다(이 패키지들은 타입 스텁을 제공하지 않습니다).
  • src/pyrmax/_generated/는 제외됩니다 — Kaitai 생성 파일에는 이미 # type: ignore가 포함되어 있으며 kaitai-struct-compiler가 실행될 때마다 덮어쓰여집니다.
  • dpkt 경계에서 동적 속성을 다루는 부분(예: MGMT_Frame.src)은 로컬 바인딩의 Any 애너테이션으로 처리하고, 여기저기 흩어진 ignore 주석은 사용하지 않습니다.

Kaitai 파서 재생성

src/pyrmax/_generated/ 아래의 생성된 Python 파일은 커밋되어 있으므로 패키지는 Kaitai 툴체인 없이도 설치할 수 있습니다. .ksy를 편집한 후 다시 생성하려면:```sh kaitai-struct-compiler -t python --outdir src/pyrmax/_generated/ ksy/*.ksy

root@kitploit:~
도구 다운로드