4. 解析WPA3 Probe Response帧

任务说明

本案例实现一个WPA3 Probe Response帧解析器,输入为完整的802.11管理帧字节序列,输出为解析后的帧字段结构,并识别该帧宣告的RSN安全能力属于WPA3-Personal还是WPA3-Enterprise。判定依据为RSN IE中的AKM套件类型:00-0F-AC-8(WPA3-Personal SAE)或00-0F-AC-9(WPA3-Enterprise Suite B-192),以及00-0F-AC-12(WPA3-Enterprise过渡模式)。

核心实现

import struct
from dataclasses import dataclass, field
from typing import List, Tuple

# ---- 802.11管理帧常量 ----
FRAME_TYPE_MGMT = 0x00
FRAME_SUBTYPE_PROBE_RESP = 0x05

# ---- RSN IE中的AKM套件标识 ----
AKM_SAE = (0x00, 0x0F, 0xAC, 0x08)          # WPA3-Personal
AKM_FT_SAE = (0x00, 0x0F, 0xAC, 0x09)       # WPA3-Personal (FT)
AKM_SUITE_B_192 = (0x00, 0x0F, 0xAC, 0x0C)  # WPA3-Enterprise (Suite B 192-bit)
AKM_SUITE_B = (0x00, 0x0F, 0xAC, 0x0B)      # WPA3-Enterprise (Suite B)

@dataclass
class TaggedElement:
    tag_number: int
    tag_length: int
    tag_value: bytes

@dataclass
class RSNAKMSuite:
    oui: Tuple[int, int, int]
    suite_type: int

@dataclass
class RSNInfo:
    version: int
    group_cipher: Tuple[int, int, int, int] = None
    pairwise_ciphers: List[Tuple[int, int, int, int]] = field(default_factory=list)
    akm_suites: List[RSNAKMSuite] = field(default_factory=list)
    rsn_capabilities: int = 0

@dataclass
class ProbeResponseFrame:
    frame_control: int
    duration: int
    dst_addr: str
    src_addr: str
    bssid: str
    seq_ctrl: int
    timestamp: int
    beacon_interval: int
    capability_info: int
    ssid: str = ""
    rsn: RSNInfo = None
    raw_tagged_elements: List[TaggedElement] = field(default_factory=list)

def mac_to_str(mac_bytes: bytes) -> str:
    return ":".join(f"{b:02X}" for b in mac_bytes)

def parse_tagged_elements(data: bytes) -> List[TaggedElement]:
    """解析Tagged Parameters区域,返回所有Tagged Element列表"""
    elements = []
    offset = 0
    while offset < len(data):
        tag_num = data[offset]
        tag_len = data[offset + 1]
        tag_val = data[offset + 2 : offset + 2 + tag_len]
        elements.append(TaggedElement(tag_num, tag_len, tag_val))
        offset += 2 + tag_len
    return elements

def parse_rsn_ie(tag_value: bytes) -> RSNInfo:
    """解析RSN (Tag 48) Information Element"""
    if len(tag_value) < 2:
        raise ValueError("RSN IE长度不足")
    
    rsn = RSNInfo(version=struct.unpack("<H", tag_value[0:2])[0])
    offset = 2
    
    # Group Cipher Suite (4 bytes)
    if offset + 4 <= len(tag_value):
        rsn.group_cipher = tuple(tag_value[offset : offset + 4])
        offset += 4
    
    # Pairwise Cipher Suite Count + List
    if offset + 2 <= len(tag_value):
        pairwise_count = struct.unpack("<H", tag_value[offset : offset + 2])[0]
        offset += 2
        for _ in range(pairwise_count):
            if offset + 4 > len(tag_value):
                break
            rsn.pairwise_ciphers.append(tuple(tag_value[offset : offset + 4]))
            offset += 4
    
    # AKM Suite Count + List
    if offset + 2 <= len(tag_value):
        akm_count = struct.unpack("<H", tag_value[offset : offset + 2])[0]
        offset += 2
        for _ in range(akm_count):
            if offset + 4 > len(tag_value):
                break
            akm_bytes = tag_value[offset : offset + 4]
            rsn.akm_suites.append(RSNAKMSuite(
                oui=(akm_bytes[0], akm_bytes[1], akm_bytes[2]),
                suite_type=akm_bytes[3]
            ))
            offset += 4
    
    # RSN Capabilities (2 bytes, optional)
    if offset + 2 <= len(tag_value):
        rsn.rsn_capabilities = struct.unpack("<H", tag_value[offset : offset + 2])[0]
    
    return rsn

def parse_probe_response(frame: bytes) -> ProbeResponseFrame:
    """解析完整的802.11 Probe Response管理帧"""
    if len(frame) < 36:
        raise ValueError("帧长度不足,无法构成完整Probe Response")
    
    # 固定头部 (24字节)
    frame_ctrl = struct.unpack("<H", frame[0:2])[0]
    subtype = (frame_ctrl >> 4) & 0x0F
    frame_type = (frame_ctrl >> 2) & 0x03
    
    if frame_type != FRAME_TYPE_MGMT or subtype != FRAME_SUBTYPE_PROBE_RESP:
        raise ValueError(f"不是Probe Response帧 (type={frame_type}, subtype={subtype})")
    
    duration = struct.unpack("<H", frame[2:4])[0]
    dst = mac_to_str(frame[4:10])
    src = mac_to_str(frame[10:16])
    bssid = mac_to_str(frame[16:22])
    seq_ctrl = struct.unpack("<H", frame[22:24])[0]
    
    # 帧体 (Frame Body)
    body = frame[24:]
    if len(body) < 12:
        raise ValueError("帧体长度不足")
    
    timestamp = struct.unpack("<Q", body[0:8])[0]
    beacon_interval = struct.unpack("<H", body[8:10])[0]
    capability_info = struct.unpack("<H", body[10:12])[0]
    
    # Tagged Parameters 从偏移12开始
    tagged_data = body[12:]
    elements = parse_tagged_elements(tagged_data)
    
    result = ProbeResponseFrame(
        frame_control=frame_ctrl,
        duration=duration,
        dst_addr=dst,
        src_addr=src,
        bssid=bssid,
        seq_ctrl=seq_ctrl,
        timestamp=timestamp,
        beacon_interval=beacon_interval,
        capability_info=capability_info,
        raw_tagged_elements=elements
    )
    
    # 提取SSID (Tag 0) 和 RSN (Tag 48)
    for elem in elements:
        if elem.tag_number == 0:  # SSID
            result.ssid = elem.tag_value.decode("utf-8", errors="replace")
        elif elem.tag_number == 48:  # RSN
            result.rsn = parse_rsn_ie(elem.tag_value)
    
    return result

def classify_wpa3(rsn: RSNInfo) -> str:
    """根据RSN IE中的AKM套件判断WPA3类型"""
    if rsn is None or not rsn.akm_suites:
        return "非WPA3(无RSN或AKM套件)"
    
    for akm in rsn.akm_suites:
        suite = (akm.oui[0], akm.oui[1], akm.oui[2], akm.suite_type)
        if suite == AKM_SAE or suite == AKM_FT_SAE:
            return "WPA3-Personal"
        if suite == AKM_SUITE_B_192 or suite == AKM_SUITE_B:
            return "WPA3-Enterprise"
    
    return "非WPA3(AKM套件不支持WPA3)"

# ---- 样例输入:构造一个WPA3-Personal的Probe Response帧 ----
def build_sample_frame_wpa3_personal() -> bytes:
    """构造一个宣告WPA3-Personal (SAE) 的Probe Response帧"""
    # 固定头部
    fc = (FRAME_TYPE_MGMT << 2) | (FRAME_SUBTYPE_PROBE_RESP << 4) | 0x00  # 无加密
    header = struct.pack("<H", fc)
    header += struct.pack("<H", 0)  # duration
    header += bytes.fromhex("FFFFFFFFFFFF")  # dst: broadcast
    header += bytes.fromhex("AABBCCDDEEFF")  # src
    header += bytes.fromhex("AABBCCDDEEFF")  # bssid
    header += struct.pack("<H", 0x0000)  # seq_ctrl
    
    # 帧体
    body = struct.pack("<Q", 12345678)  # timestamp
    body += struct.pack("<H", 100)      # beacon interval
    body += struct.pack("<H", 0x0001)   # capability: ESS
    
    # Tagged Elements
    # SSID (Tag 0)
    ssid = b"WPA3-Lab"
    body += bytes([0, len(ssid)]) + ssid
    
    # RSN IE (Tag 48)
    rsn_body = struct.pack("<H", 1)  # version
    rsn_body += bytes([0x00, 0x0F, 0xAC, 0x04])  # group cipher: CCMP
    rsn_body += struct.pack("<H", 1)  # pairwise count
    rsn_body += bytes([0x00, 0x0F, 0xAC, 0x04])  # pairwise: CCMP
    rsn_body += struct.pack("<H", 1)  # akm count
    rsn_body += bytes([0x00, 0x0F, 0xAC, 0x08])  # AKM: SAE (WPA3-Personal)
    rsn_body += struct.pack("<H", 0x0000)  # capabilities
    
    body += bytes([48, len(rsn_body)]) + rsn_body
    
    return header + body

# ---- 主流程 ----
if __name__ == "__main__":
    # 使用样例帧进行解析
    sample_frame = build_sample_frame_wpa3_personal()
    parsed = parse_probe_response(sample_frame)
    
    print(f"源MAC: {parsed.src_addr}")
    print(f"BSSID: {parsed.bssid}")
    print(f"SSID: {parsed.ssid}")
    print(f"Beacon Interval: {parsed.beacon_interval}")
    
    if parsed.rsn:
        print(f"RSN版本: {parsed.rsn.version}")
        print(f"AKM套件数量: {len(parsed.rsn.akm_suites)}")
        for akm in parsed.rsn.akm_suites:
            print(f"  AKM OUI: {akm.oui[0]:02X}-{akm.oui[1]:02X}-{akm.oui[2]:02X}, 类型: {akm.suite_type:02X}")
        print(f"WPA3分类: {classify_wpa3(parsed.rsn)}")
    else:
        print("未找到RSN IE")

验证方法

直接运行上述Python脚本,观察输出。预期输出应包含:源MAC为AA:BB:CC:DD:EE:FF,SSID为WPA3-Lab,AKM套件类型为08(SAE),最终分类结果为WPA3-Personal。若需验证WPA3-Enterprise识别,可将样例帧构造函数中AKM套件字节改为00 0F AC 0C(Suite B 192-bit),重新运行后分类结果应变为WPA3-Enterprise。判定标准:解析出的帧字段与构造输入完全一致,且AKM套件类型与WPA3分类结果正确对应。

Probe Response 帧结构 (802.11管理帧) Frame Control (2B) Duration (2B) DA (6B) SA (6B) BSSID (6B) Seq Ctrl (2B) Timestamp (8B) Beacon Int (2B) Capability (2B) Tagged Elements (可变) Tagged Elements 展开 Tag 0: SSID Tag 48: RSN IE (关键) 其他Tag... RSN IE 内部结构 Version Group Cipher Pairwise AKM套件 AKM套件判定逻辑 00-0F-AC-08/09 → WPA3-Personal 00-0F-AC-0B/0C → WPA3-Enterprise 其他 → 非WPA3