2. Beacon帧间隔与SSID广播配置实验

Beacon帧间隔与SSID广播配置实验 学习卡片

任务目标

编写一个Python脚本,解析WiFi Beacon管理帧的二进制数据,提取SSID、BSSID、信道、支持速率、DS参数集等关键信息。脚本需支持从pcap文件中读取原始帧数据,并输出可读的解析结果。

环境依赖

仅需Python 3.6+标准库,无需第三方包。pcap文件解析部分自行实现最小读取逻辑,不依赖scapy等库,便于理解帧结构。

Beacon帧结构要点

Beacon帧属于管理帧(type=0),subtype=8。帧体(Frame Body)前12字节为固定字段,之后是变长的Tagged Parameters(TLV格式)。每个Tag由1字节Tag Number、1字节Length和对应的Value组成。本案例解析以下关键Tag:

  • SSID (Tag 0):服务集标识
  • Supported Rates (Tag 1):支持速率,单位Mbps,低7位为速率值
  • DS Parameter Set (Tag 3):当前信道号
  • HT Capabilities (Tag 45):提取信道宽度信息

完整实现代码

import struct
import sys

def parse_pcap(filepath):
    """读取pcap文件,返回所有Beacon帧的原始字节列表"""
    beacons = []
    with open(filepath, 'rb') as f:
        # pcap全局头:magic(4) + version(4) + 保留(8) + snaplen(4) + network(4)
        global_header = f.read(24)
        if len(global_header) < 24:
            return beacons
        magic = struct.unpack('<I', global_header[:4])[0]
        # 判断字节序(处理0xa1b2c3d4或0xd4c3b2a1)
        little_endian = (magic == 0xa1b2c3d4)
        endian = '<' if little_endian else '>'
        
        while True:
            # 每个包记录头:ts_sec(4) ts_usec(4) incl_len(4) orig_len(4)
            rec_header = f.read(16)
            if len(rec_header) < 16:
                break
            ts_sec, ts_usec, incl_len, orig_len = struct.unpack(endian + 'IIII', rec_header)
            packet_data = f.read(incl_len)
            if len(packet_data) < incl_len:
                break
            
            # 跳过Radiotap头(如果有),这里假设链路层为IEEE 802.11
            # 检查是否为Beacon帧:Frame Control前2字节
            if len(packet_data) < 24:
                continue
            frame_control = struct.unpack(endian + 'H', packet_data[:2])[0]
            frame_type = (frame_control >> 2) & 0x3
            frame_subtype = (frame_control >> 4) & 0xF
            if frame_type == 0 and frame_subtype == 8:  # 管理帧-Beacon
                beacons.append(packet_data)
    return beacons

def parse_beacon(frame):
    """解析单个Beacon帧,返回信息字典"""
    info = {}
    # Frame Control(2) + Duration(2) + DA(6) + SA(6) + BSSID(6) + Seq(2) = 24字节
    # 帧体从偏移24开始
    if len(frame) < 24:
        return info
    
    # BSSID:帧头中偏移16-21(SA之后的地址字段)
    bssid_bytes = frame[16:22]
    info['BSSID'] = ':'.join(f'{b:02X}' for b in bssid_bytes)
    
    # 帧体
    body = frame[24:]
    if len(body) < 12:
        return info
    
    # 固定字段:Timestamp(8) + Beacon Interval(2) + Capability(2)
    info['Timestamp'] = struct.unpack('<Q', body[:8])[0]
    info['Beacon Interval'] = struct.unpack('<H', body[8:10])[0]
    capability = struct.unpack('<H', body[10:12])[0]
    info['Capability'] = f'0x{capability:04X}'
    
    # Tagged Parameters从偏移12开始
    pos = 12
    ssid = None
    rates = []
    channel = None
    ht_cap = None
    
    while pos + 2 <= len(body):
        tag_num = body[pos]
        tag_len = body[pos + 1]
        tag_value = body[pos + 2 : pos + 2 + tag_len]
        if len(tag_value) < tag_len:
            break
        
        if tag_num == 0:  # SSID
            ssid = tag_value.decode('utf-8', errors='replace')
        elif tag_num == 1:  # Supported Rates
            for rate_byte in tag_value:
                rate = rate_byte & 0x7F
                rates.append(rate / 2 if rate_byte & 0x80 else rate)
        elif tag_num == 3:  # DS Parameter Set
            if tag_len >= 1:
                channel = tag_value[0]
        elif tag_num == 45:  # HT Capabilities
            if tag_len >= 2:
                ht_cap = tag_value[1]  # 第2字节包含信道宽度位
        
        pos += 2 + tag_len
    
    info['SSID'] = ssid if ssid is not None else '(隐藏)'
    info['Supported Rates (Mbps)'] = rates
    info['Channel'] = channel
    if ht_cap is not None:
        # 第1位为0表示20MHz,为1表示20/40MHz
        info['HT Channel Width'] = '20/40 MHz' if (ht_cap & 0x01) else '20 MHz'
    return info

def main():
    if len(sys.argv) < 2:
        print('用法: python beacon_parser.py <pcap文件路径>')
        sys.exit(1)
    
    beacons = parse_pcap(sys.argv[1])
    if not beacons:
        print('未找到Beacon帧')
        return
    
    print(f'共捕获 {len(beacons)} 个Beacon帧\n')
    for i, frame in enumerate(beacons[:5]):  # 最多显示前5个
        info = parse_beacon(frame)
        print(f'--- Beacon {i+1} ---')
        for key, value in info.items():
            print(f'{key}: {value}')
        print()

if __name__ == '__main__':
    main()

运行与验证

运行方式:

python beacon_parser.py capture.pcap

验证步骤:

  1. 准备一个包含Beacon帧的pcap文件(可用Wireshark抓包或从公开数据集获取)
  2. 运行脚本,观察输出是否包含SSID、BSSID、信道等字段
  3. 用Wireshark打开同一pcap文件,对比某个Beacon帧的解析结果:展开IEEE 802.11 Beacon frame,检查SSID、BSSID、DS Parameter Set中的信道号是否与脚本输出一致

通过条件:脚本输出的BSSID与Wireshark中显示的Source/BSSID一致;SSID、信道号与Wireshark中Tag解析结果完全匹配;Supported Rates数值与Wireshark中列出的速率一致(注意Wireshark可能显示为Mbps值,需对比换算关系)。

常见问题

  • pcap字节序问题:脚本已自动检测magic number判断字节序,若解析失败请确认pcap文件为标准格式
  • Radiotap头:如果pcap文件包含Radiotap头(如使用monitor模式抓包),需在解析前跳过Radiotap头长度。本脚本假设链路层直接为802.11帧,若遇到解析异常可先检查pcap的Network字段
  • 隐藏SSID:Tag 0长度为0时,SSID显示为“(隐藏)”