Nordic nRF Sniffer实战:5分钟搞定BLE加密报文抓包与解密(附LESC配对调试技巧)
如果你正在开发基于BLE的物联网设备,或者需要对蓝牙通信进行安全分析,那么抓包和解密加密报文无疑是绕不开的关键技能。很多开发者第一次尝试抓取BLE通信时,常常在设备配对后陷入困境——Wireshark里一片“Encrypted packet decrypted incorrectly”的红色警告,加密后的数据包完全无法解析。这背后其实是BLE安全机制在起作用,特别是从BLE 4.2开始引入的LE Secure Connections(LESC)配对机制,它让传统的抓包工具束手无策。
不过别担心,今天我要分享的这套方案,能让你在5分钟内搭建起完整的BLE抓包环境,并且连LESC加密后的通信也能轻松解密。这套方案基于Nordic官方的nRF Sniffer固件和Wireshark,成本极低(只需要一块几十元的nRF52840 Dongle),却能提供接近专业协议分析仪的效果。更重要的是,我会详细拆解如何通过修改SDK启用Debug模式来获取私钥,让你不仅能抓包,还能深入理解BLE加密通信的每一个细节。
1. 环境搭建:从零开始的抓包平台
1.1 硬件选择与准备
要搭建BLE抓包环境,首先需要选择合适的硬件。市面上有多种BLE Sniffer方案,但Nordic的nRF Sniffer因其开源、易用且成本低廉而备受青睐。
硬件对比表:
| 硬件设备 | 价格范围 | 优点 | 缺点 | 推荐场景 |
|---|---|---|---|---|
| nRF52840 Dongle | 50-100元 | 成本极低,官方支持完善,性能强劲 | 需要烧录Sniffer固件 | 个人开发者、教育研究 |
| nRF52 DK开发板 | 300-500元 | 自带调试器,功能全面 | 价格较高,体积较大 | 专业开发团队 |
| Ellisys蓝牙分析仪 | 20-30万元 | 专业级,功能强大,支持多种协议 | 价格昂贵 | 企业级产品开发、深度安全分析 |
| Ubertooth One | 1000-2000元 | 开源硬件,社区活跃 | 配置复杂,性能有限 | 安全研究人员 |
对于大多数开发者来说,nRF52840 Dongle是最佳选择。它基于Nordic的nRF52840芯片,支持BLE 5.0,性能足够应对绝大多数场景。
注意:购买时请确保选择正品,市面上有些仿制品可能存在固件兼容性问题。推荐从官方授权渠道或信誉良好的代理商处购买。
1.2 软件环境配置
软件环境的配置相对简单,但有几个关键步骤需要注意:
第一步:安装Wireshark
# Ubuntu/Debian系统
sudo apt update
sudo apt install wireshark
# macOS系统
brew install wireshark
# Windows系统
# 从官网下载安装包:https://www.wireshark.org/download.html
安装完成后,需要配置Wireshark以支持nRF Sniffer插件:
- 打开Wireshark,进入“帮助”->“关于Wireshark”->“文件夹”
- 找到“个人插件”目录(通常是
~/.local/lib/wireshark/plugins或%APPDATA%\Wireshark\plugins) - 下载nRF Sniffer插件并放置到该目录
第二步:获取并烧录Sniffer固件
Nordic官方提供了预编译的Sniffer固件,可以从GitHub仓库获取:
# 克隆nRF Sniffer for Bluetooth LE仓库
git clone https://github.com/nordicsemiconductor/nRF-Sniffer-for-Bluetooth-LE.git
# 进入固件目录
cd nRF-Sniffer-for-Bluetooth-LE/hex
固件文件按照硬件型号分类:
sniffer_nrf52840dongle_nrf52840_*.hex- 用于nRF52840 Donglesniffer_nrf52dk_nrf52832_*.hex- 用于nRF52 DK开发板sniffer_nrf5340_dk_nrf5340_*.hex- 用于nRF5340 DK开发板
烧录固件有多种方法,这里以nRF52840 Dongle为例:
使用nRF Connect for Desktop烧录:
- 安装nRF Connect for Desktop(可从Nordic官网下载)
- 打开Programmer工具
- 将Dongle插入电脑,点击“Select device”
- 选择对应的Dongle,点击“Write”按钮
- 选择下载的hex文件,开始烧录
使用命令行工具烧录:
# 安装nrfutil
pip install nrfutil
# 进入DFU模式:按住Dongle上的按钮,插入USB,等待LED开始呼吸闪烁
nrfutil dfu usb-serial -pkg nrf_sniffer.zip -p COMx # Windows
nrfutil dfu usb-serial -pkg nrf_sniffer.zip -p /dev/ttyACM0 # Linux/macOS
第三步:配置Wireshark抓包
烧录完成后,重新插入Dongle,打开Wireshark:
- 在捕获接口列表中,你应该能看到“nRF Sniffer for Bluetooth LE COMx”
- 双击该接口开始捕获
- 在工具栏中,点击“nRF Sniffer”->“Device”,选择要监控的BLE设备地址
提示:如果看不到nRF Sniffer接口,可能需要手动安装USB驱动。在Windows上,可以使用Zadig工具为Dongle安装WinUSB驱动。
1.3 基础抓包实战
让我们先从一个简单的例子开始。假设我们要监控一个心率监测设备(比如Nordic的HRM示例):
# 模拟BLE设备广播(仅用于理解广播过程)
import time
def simulate_ble_advertisement():
# BLE设备上电后开始广播
print("设备开始广播...")
# 广播数据包含:
# 1. 设备名称
# 2. 服务UUID(心率服务、电池服务等)
# 3. 发射功率
# 4. 连接参数建议
advertisement_data = {
"device_name": "Nordic_HRM",
"services": ["180D", "180F", "180A"], # 心率、电池、设备信息服务
"tx_power": -20, # dBm
"connectable": True
}
return advertisement_data
# 在Wireshark中,你可以看到类似这样的广播包:
# ADV_IND类型,包含完整的广播数据
在Wireshark中捕获到的广播包会显示详细的信息:
关键字段解析:
- PDU Type:
ADV_IND(可连接的非定向广播) - AdvA: 广播设备地址(通常是随机地址)
- AdvData: 广播数据,包含:
- Flags: LE通用发现模式
- 完整的16位服务UUID列表
- 设备名称
- 发射功率级别
Wireshark过滤技巧:
# 只显示特定设备的广播包
btle.advertising_address == aa:bb:cc:dd:ee:ff
# 只显示包含特定服务的广播
btle.advertising_data.service_uuid16 == 0x180D
# 排除空数据包
btle.length != 0
# 组合过滤条件
btle.advertising_address == aa:bb:cc:dd:ee:ff && btle.advertising_data.service_uuid16 == 0x180D
2. BLE协议层深度解析:从GAP到GATT
2.1 GAP层:连接建立的基石
GAP(Generic Access Profile)负责BLE设备的发现、连接建立和安全初始化。理解GAP过程对于调试连接问题至关重要。
连接建立过程详解:
当Central设备(如手机)扫描到Peripheral设备(如心率监测器)的广播后,会发起连接请求:
# 连接请求包(CONNECT_IND)的关键参数
connect_ind_packet = {
"initiator_address": "11:22:33:44:55:66", # 发起者地址
"advertiser_address": "aa:bb:cc:dd:ee:ff", # 广播者地址
"access_address": 0x8E89BED6, # 访问地址(随机生成)
"crc_init": 0x555555, # CRC初始值
"connection_parameters": {
"conn_interval": 30, # 连接间隔:30 * 1.25ms = 37.5ms
"conn_latency": 0, # 从设备延迟
"supervision_timeout": 600 # 监控超时:600 * 10ms = 6s
},
"channel_map": 0x1FFFFFFFFF, # 信道映射(使用所有37个数据信道)
"hop_increment": 11 # 跳频增量
}
在Wireshark中分析连接建立过程时,要特别关注以下几个关键点:
- 连接参数协商:连接间隔、从设备延迟、监控超时这三个参数直接影响功耗和响应速度
- 信道选择算法:BLE使用自适应跳频来避免干扰
- 访问地址:每个连接都有唯一的访问地址,用于区分不同连接
常见连接问题排查:
| 问题现象 | 可能原因 | 排查方法 |
|---|---|---|
| 连接频繁断开 | 监控超时设置过短 | 检查supervision_timeout是否足够大 |
| 数据传输延迟大 | 连接间隔过长 | 检查conn_interval是否合理 |
| 功耗过高 | 连接间隔过短 | 适当增大conn_interval |
| 数据传输不稳定 | 信道质量差 | 检查channel_map和实际环境干扰 |
2.2 链路层控制协议:连接维护的核心
连接建立后,设备会通过链路层控制协议(LLCP)交换能力和更新参数。这是BLE通信中最容易被忽视但至关重要的部分。
能力交换(Feature Exchange):
# LL_FEATURE_REQ/RSP报文结构
feature_exchange = {
"supported_features": {
"le_encryption": True, # 支持加密
"conn_param_req": True, # 支持连接参数请求
"extended_reject": True, # 支持扩展拒绝指示
"slave_initiated_feature_exchange": True, # 从设备发起能力交换
"le_ping": True, # 支持Ping
"le_data_packet_length_extension": True, # 支持数据包长度扩展
"ll_privacy": False, # 是否支持隐私
"extended_scanner_filter_policies": False, # 扩展扫描器过滤策略
"le_2m_phy": True, # 支持2M PHY
"le_coded_phy": False, # 支持Coded PHY
"le_extended_advertising": False, # 支持扩展广播
"le_periodic_advertising": False # 支持周期广播
}
}
关键参数更新过程:
数据长度更新(Data Length Update):
# 在Wireshark中过滤LLCP数据包
btl2cap.cid == 0x0004 # LLCP信道
# 查看数据长度更新过程
btle.ll.opcode == 0x22 # LL_LENGTH_REQ
btle.ll.opcode == 0x23 # LL_LENGTH_RSP
数据长度更新是BLE 4.2引入的重要特性,将最大数据包长度从27字节扩展到251字节,显著提升了吞吐量。
连接参数更新(Connection Parameters Update):
# 连接参数更新请求
conn_param_update = {
"conn_interval_min": 15, # 最小连接间隔:15 * 1.25ms = 18.75ms
"conn_interval_max": 30, # 最大连接间隔:30 * 1.25ms = 37.5ms
"conn_latency": 0, # 从设备延迟
"supervision_timeout": 600, # 监控超时
"preferred_periodicity": 0, # 首选周期
"reference_conn_event_count": 0, # 参考连接事件计数
"offset0": 0,
"offset1": 0,
"offset2": 0,
"offset3": 0,
"offset4": 0
}
PHY更新(PHY Update): BLE 5.0引入了2M PHY和Coded PHY,PHY更新过程允许设备协商使用更高速率或更长距离的物理层。
注意:PHY更新可能会影响通信距离和功耗,需要根据实际应用场景谨慎选择。
2.3 GATT服务发现与数据交换
GATT(Generic Attribute Profile)是BLE应用层通信的核心,定义了服务(Service)、特征(Characteristic)和描述符(Descriptor)的发现和访问机制。
MTU交换过程: 在开始服务发现之前,设备需要交换最大传输单元(MTU):
# Exchange MTU Request/Response
mtu_exchange = {
"client_rx_mtu": 247, # 客户端接收MTU
"server_rx_mtu": 247 # 服务器接收MTU
}
MTU大小直接影响数据传输效率。较大的MTU可以减少协议开销,但需要设备支持数据长度扩展特性。
服务发现流程:
def discover_services(connection_handle):
# 1. 发现所有主要服务
services = []
start_handle = 0x0001
end_handle = 0xFFFF
while True:
# 发送Read By Group Type Request
request = {
"opcode": 0x10, # Read By Group Type Request
"starting_handle": start_handle,
"ending_handle": end_handle,
"attribute_type": 0x2800 # Primary Service
}
# 接收响应
response = send_request(request)
if response["opcode"] == 0x11: # Read By Group Type Response
for service in response["services"]:
services.append({
"handle": service["handle"],
"group_end_handle": service["group_end_handle"],
"uuid": service["uuid"]
})
# 更新起始句柄
start_handle = services[-1]["group_end_handle"] + 1
if start_handle > end_handle:
break
else:
# 错误响应,结束发现
break
return services
特征发现与读写操作: 发现服务后,需要进一步发现每个服务包含的特征:
def discover_characteristics(service):
characteristics = []
start_handle = service["handle"] + 1
end_handle = service["group_end_handle"]
while start_handle <= end_handle:
# 发送Read By Type Request
request = {
"opcode": 0x08, # Read By Type Request
"starting_handle": start_handle,
"ending_handle": end_handle,

&spm=1001.2101.3001.5002&articleId=153487041&d=1&t=3&u=78e9ae2657a741bbb245c045b447a2ac)
3003

被折叠的 条评论
为什么被折叠?



