[SDR] SDR 教程实战 —— 利用 GNU Radio + HackRF 手把手深入了解蓝牙协议栈(从电磁波 -> 01数据流 -> 蓝牙数据包)
0、前言
该教程详细介绍了从电磁波级别开始,反向嗅探蓝牙低功耗广播包的全过程。虽然这里面涉及极其多的专业知识,但是一步一步去阅读,小白应该也可以了解个大概 _。
如果能将本文完全理解,将会大大提升你对蓝牙协议栈的理解(是蓝牙协议栈最最底层级别的了解)、将会大大提升你对通信原理的理解、将会大大提升你对 GNU Radio 工具的理解、将会大大提升你对于无线通讯黑客嗅探的理解!
那么,让我们开始 ba~
1、体验
1)在 linux 电脑 clone 代码,并且进入 ble_adv_rx
目录:
git clone git@github.com:nbtool/auto_test_tool.git
cd auto_test_tool/app/app_sdr_ble_adv_rx
2)将 hackrf 插入电脑 USB3.0 的端口
3)运行 make , 可以看到 hackrf 收取到了 BLE 广播包
2、代码解析
2.1 目录结构
➜ app_sdr_ble_adv_rx git:(master) ✗ tree
.
├── app_frame.py # BLE 广播包协议解析逻辑
├── BT.xlsx # 蓝牙 BLE 包格式说明
├── grc # GNU RADIO 流程图
│ ├── gr_ble.grc
│ ├── gr_ble.py
│ └── __init__.py
├── main.py # 主逻辑
├── makefile # MAEKFILE
└── readme.md
2 directories, 8 files
2.2 main.py
def analysis_cmd(str):
print("analysis_cmd:[%02X][%02X]" %(str[0],str[1]),end=' ')
print("mac:",end = '')
for d in reversed(str[2:8]):
print('%02X' %(d), end='')
print(" data:",end = '')
for d in str[8:]:
print('%02X' %(d), end=' ')
print(' ')
# Initialize Gnu Radio
gr_block = gr_block() # 实例化 gnu radio 流程图
gr_block.start() # 启动 gnu radio 流程图
gr_block.set_ble_channel(app_frame.BLE_CHANS[37]) # 设置 BLE SCAN 通道为 37
zmq1 = bsp_zmq.bsp_zmq("tcp://127.0.0.1:55555") # 借助我自己封装的 zmq 类,用于从 gnu radio 流程图中通过 socket 接收数据
frame = app_frame.FRAME(analysis_cmd) # 借助我自己封装的 app_frame 类,用于分析处理从 gnu radio 流程图
# 中获取的数据,从而过滤、去白、格式分析出 BLE 广播包数据,
# 通过 analysis_cmd 回调给应用层
try: # 不断判断 zmq 的 socket 端口是否有数据,如果有读取并放入 frame 的 fifo 中
while 1<2:
if zmq1.iswaiting() != 0:
x = zmq1.read()
frame.insert_data(x)
frame.run()
except KeyboardInterrupt:
zmq1.close() # close port
gr_block.stop()
gr_block.wait()
print("safe exit")
main 文件是此程序的入口,实例并启动 gnu radio; 实例 zmq,用于该 python 程序与 gnu radio 通信; 实例 app_fram 用于分析处理 gnu radio 传来的数据。
注:如果想要更细了解 ZMQ 的机制,参考《[SDR] GNU Radio 系列教程(十四) —— GNU Radio 低阶到高阶用法的分水岭 ZMQ 的使用详解》
2.3 grc gnu radio 流程图
这里的流程图如下:
这个流程图和《SDR 教程 —— 利用 GNU Radio + HackRF + WireShark 做蓝牙抓包器(超低成本)》中的一样。
- 数据源采用 RTL-SDR Source,设备选择 hackrf =0, 其频率对应的是蓝牙广播扫描的信道。
- 源数据出来后,过一个阈值过滤 -70dB 过滤器
- 然后再过一个低通滤波器
- 然后送到 GFSK 解码模块
- 最后送到 ZMQ 将数据发布出去
本质是一个 GFSK 解码接收机,类似一个 nRF24L01+ 模块《如何为编程爱好者设计一款好玩的智能硬件(十)——无线2.4G通信模块研究·一篇说完》。当然,我们也能用一个纯具备 GFSK 制式的 2.4G 接收模块,实现 ble beacon 的接收!!!
2.4 如何从 01 数据流中解析出 BLE 广播包
想要知道如何从 01 数据流中解析出 BLE 广播包,我们首先需要看看 BLE 协议栈从下到上有哪些层:
最下面物理层是载波和制式相关的电磁波相关要求,这一层要求了电磁波的频率、编码、数据率等,实现 01 与电磁波的互相转换;次高一层是 Link Layer 层,该层约定了 01 数据流以怎样的帧组织方式是合法的。这两层组成了 BLE Controller 层,是蓝牙芯片厂家至少要实现的层。
2.4.1 物理层
1)信道
我们如果想要抓取广播信道,只要关注:37、38、39 三个信道,起频率分别为:2400KHz、2426KHz、2480KHz
注:反过来看上一节的流程图中 RTL-SDR Source 默认的频率为 2.426GHz,意味着默认采集 38 信道的数据
注:编码方式采用 GFSK
2.4.2 数据链路层
2.4.2.1 角色
在 37、28、39 信道发送信息的叫做 advertiser ; 接收信息的叫做 scanner。
2.4.2.2 数据格式
这有份 BLE 数据链路层超全的格式说明:https://github.com/nbtool/auto_test_tool/blob/master/app/app_sdr_ble_adv_rx/BT.xlsx
我们以 4.0/4.1 版本为例:
其中:
1)Preamble
所有链路层数据包都有一个 8 位前导码。 在接收机中使用前导码来执行频率同步,符号定时估计和自动增益控制(AGC)训练。
- 广告信道数据包应具有 10101010b 作为前导码。
- 数据信道分组前导码是 10101010b(0xAA)或 01010101b(0x55),具体取决于接入地址的 LSB。 如果接入地址的 LSB 是 1,则前导应为 01010101b,否则前导应为 10101010b。
2)Access Address
由发起者生成,用来在两个设备之间识别一个LL层连接
- 所有广播数据包的访问地址都是 10001110100010011011111011010110b (0x8E89BED6)。
- 所有连接数据包的访问地址都是随机值,并遵循一定规则,每次连接重新生成。
3)PDU
Protocol Data Unit,协议数据单元
PDU 有两种,广播信道传输的是广播 PDU,连接信道传输的是连接 PDU。
4)CRC
每个 Link Layer 数据包的结尾都有 24 位的 CRC 校验数据,它通过 PDU 计算得出。
2.4.3 加密相关
这里有个至关重要的流程,需要看蓝牙协议栈 《CoreSpecification_v5.0.pdf》:
2.4.3.1 Cyclic Redundancy Check(循环冗余检查)
循环冗余校验作用于数据链路层 PDU 部分
If the PDU is encrypted, the CRC is calculated after encryption.
这里的 CRC 多项式是一个 24 bit 多项式:x^24+x^10+x^9+x^6+x^4+x^3+x^1+x^0
其物理上对应一个线性反馈移位寄存器 (LFSR) with XOR taps at bit 0, 1, 3, 4, 6, 9, 10, and 24.
数据从最低有效位开始移位到移位寄存器。移位寄存器用已知的共享值或 0x555555 进行初始化。
线性反馈移位寄存器中的CRC编码示例,预设为0x555555(10101010101010101010)—— Animation by Mark Hughes
当接收到数据包时,在CRC之后检查访问地址。如果其中一个不正确,则数据包将被拒绝,并停止处理。
2.4.3.2 Data Whitening
数据白化防止重复位(00000000或11111111)的长序列。它被应用于发射机的 CRC 之后的链路层的 PDU 和 CRC 字段。在接收器中的 CRC 之前执行去白化。白化器和去白化器都使用在比特 4 和比特 7 处具有抽头的7比特线性反馈移位寄存器(LFSR)。
The shift register is initialized with a sequence that is derived from the channel index:
- 位置 0 设置为 1
- 位置 1 到 6 被设置成 the channel index of the channel used when transmitting or receiving, with the MSB in position 1 and the LSB in position 6
Bits are shifted along the shift register from 0→1, 1→2, 2→3, 4→5, 5→6, 6→0. Bit 3 and bit 6 are processed with the XOR ⊕ operator to determine bit 4 (0⊕0=0,0⊕1=1,1⊕0=1,1⊕1=0)
图示用通道26 = 0x1A (1011010)初始化的数据白化线性反馈移位寄存器内部的逻辑图像 —— Animation by Mark Hughes
1011010
https://www.allaboutcircuits.com/uploads/articles/CoreSpecification_v5.0.pdf [3.2 DATA WHITENING]
通道 26 = 00[01 1010]
position0 = 1(固定的)
position 1 = 0(通道的最高有效位MSB)
position 2 = 1
position 3 = 1
position 4 = 0
position 5 = 1
position 6 = 0(通道最低有效位LSB)
第一次移位寄存器为:
1011 010 (取position6 和 position3 进行异或得到下次的 position 4数据)
0101 101
1010 010
0101 001
...
算法实现:
- 1)channel bit 左右反转,右数第二位置1 (00[01 1010] -> [010110]10)形成 lfsr
- 2)对于每一字节的输入,bits 左右反转成 d,循环 8 次:
- 从左往右取出 lfsr 和 d 的每一 bit,亦或运算赋值给 d 的对应 bit
- 取position6 和 position3 进行异或得到下次的 position 4数据
- 注:下面代码里用了比较巧妙的方式,做到了上面两点)
PYTHON 代码为:
# Swap bits of a 8-bit value
# ➜ sdr4iot-ble-rx git:(master) ✗ python test_swap.py
# 0b11010101
# 0b10101011
def swap_bits(value):
return (value * 0x0202020202 & 0x010884422010) % 1023
# (De)Whiten data based on BLE channel
def dewhitening(data, channel):
ret = []
lfsr = swap_bits(channel) | 2
for d in data:
d = swap_bits(ord(d[:1]))
for i in 128, 64, 32, 16, 8, 4, 2, 1:
if lfsr & 0x80:
lfsr ^= 0x11
d ^= i
lfsr <<= 1
i >>= 1
ret.append(swap_bits(d))
return ret
测试代码:
for xx in self.gr_buffer[pos:pos + BLE_PDU_HDR_LEN]:
print(hex(ord(xx)),end=' ')
print('<--PRE (%d)', self.current_ble_chan)
ble_header = dewhitening(
self.gr_buffer[pos:pos + BLE_PDU_HDR_LEN], self.current_ble_chan)
for xx in ble_header:
print(hex(xx),end=' ')
print('<--AFTER')
输入输出:
0xcd 0xf7 <--PRE (%d) 37
0x40 0x25 <--AFTER
0xcd 0xf7 <--PRE (%d) 37
0x40 0x25 <--AFTER
0x3a 0x79 <--PRE (%d) 37
0xb7 0xab <--AFTER
0x93 0xf7 <--PRE (%d) 37
0x1e 0x25 <--AFTER
0x85 0x9f <--PRE (%d) 37
0x8 0x4d <--AFTER
0x60 0x15 <--PRE (%d) 37
0xed 0xc7 <--AFTER
0x6b 0xdf <--PRE (%d) 37
0xe6 0xd <--AFTER
0xea 0x95 <--PRE (%d) 37
0x67 0x47 <--AFTER
0xaa 0x6d <--PRE (%d) 37
0x27 0xbf <--AFTER
0xaa 0x86 <--PRE (%d) 37
0x27 0x54 <--AFTER
0xdb 0xb3 <--PRE (%d) 37
0x56 0x61 <--AFTER
0xd5 0x96 <--PRE (%d) 37
0x58 0x44 <--AFTER
0xa3 0xf4 <--PRE (%d) 37
0x2e 0x26 <--AFTER
0xcd 0xf8 <--PRE (%d) 37
2.4.4 在 ellsys 中的一个蓝牙 LL 层数据
下面是一个 ellsys 中的数据:
2.5 app_frame.py 之 BLE Beacon 数据解析代码实现
- insert_data 就是将 ZMQ 从 GNU Radio 中获取的数据放入 FIFO(data_buf) 中
- run 是不断被执行的函数
- 首先调用 frame_ok 进行帧解析判断,如果解析出 ble beacon 就调用 fun_analysis 同知应用层
因此,重点显而易见都在 frame_ok 函数中:
2.5.1 判断是否满足最小帧要求
str_len = len(str)
if str_len < FRAME.P_MIN_LEN:
return (-2,start_pos,end_pos)
2.5.2 找到固定帧头
while start_pos<str_len:
pos = start_pos
if(str[pos:].startswith('\xAA\xD6\xBE\x89\x8E')):
break
start_pos = start_pos+1
注:Preamble + Access Address
2.5.3 对 PDU HEADER 进行去白及验证 PDU TYPE 合法性
# Dewhitening received BLE Header
ble_header = bsp_algorithm.bt_dewhitening(str[start_pos+FRAME.P_PDU_HEADER:start_pos+FRAME.P_PDU_HEADER+BLE_PDU_HDR_LEN],37)
ll_pdu_header = (ble_header[0] << 8) | ble_header[1]
ll_pdu_type = ble_header[0] & 0x0f
ll_pdu_txadd = (ble_header[0] >> 6) & 0x01
ll_pdu_rxadd = (ble_header[0] >> 7) & 0x01
ll_pdu_lenght = ble_header[1] & 0x3f
head_pos = start_pos+FRAME.P_PDU_HEADER
adva_pos = start_pos+FRAME.P_PDU_PAYLOAD_ADVA
advdata_pos = start_pos+FRAME.P_PDU_PAYLOAD_ADVDATA
crc_pos = start_pos+FRAME.P_PDU_PAYLOAD_ADVA+ll_pdu_lenght
end_pos = start_pos+FRAME.P_PDU_PAYLOAD_ADVA+ll_pdu_lenght+BLE_CRC_LEN
# Check BLE PDU type
if ll_pdu_type not in BLE_PDU_TYPE.values():
# print("Invalid ll_pdu_type: {:x}".format(ll_pdu_type))
return (-1,start_pos,end_pos)
2.5.4 对 PDU 进行去白及验证 CRC 合法性
# Dewhitening BLE packet
self.ble_data = bsp_algorithm.bt_dewhitening(str[head_pos:crc_pos],37)
if self.ble_data[-3:] != bsp_algorithm.bt_crc(self.ble_data, 2 + ll_pdu_lenght):
if ll_pdu_type == 0:
'''
print("->head:%04x [T:%02x T:%d R:%d L:%d] adva_pos:%d advdata_pos:%d crc_pos:%d end_pos:%d str_len:%d" \
%(ll_pdu_header,ll_pdu_type,ll_pdu_txadd,ll_pdu_rxadd,ll_pdu_lenght, \
adva_pos,advdata_pos,crc_pos,end_pos,str_len))
for x in self.ble_data:
print('%02X ' %x, end = '')
print('\n')
'''
return (0,start_pos,end_pos)
参考链接
[1].MICROCHIP Developer Help 关于蓝牙协议栈的简单介绍
[2].What is Bluetooth 5? Learn about the Bit Paths Behind the New BLE Standard
教程列表
- [1]. GNU Radio 系列教程(一) —— 什么是 GNU Radio
- [2]. GNU Radio 系列教程(二) —— 绘制第一个信号分析流程图
- [3]. GNU Radio 系列教程(三) —— 变量的使用
- [4]. GNU Radio 系列教程(四) —— 比特的打包与解包
- [5]. GNU Radio 系列教程(五) —— 流和向量
- [6]. GNU Radio 系列教程(六) —— 基于层创建自己的块
- [7]. GNU Radio 系列教程(七)—— 创建第一个块
- [8]. GNU Radio 系列教程(八)—— 创建能处理向量的 Python 块
- [9]. GNU Radio 系列教程(九)—— Python 块的消息传递
- [10]. GNU Radio 系列教程(十)—— Python 块的 Tags
- [11]. GNU Radio 系列教程(十一)—— 低通滤波器
- [12]. GNU Radio 系列教程(十二)—— 窄带 FM 收发系统(基于ZMQ模拟射频发送)
- [13]. GNU Radio 系列教程(十三)—— 用两个 HackRF 实现 FM 收发
- [14]. GNU Radio 系列教程(十四)—— GNU Radio 低阶到高阶用法的分水岭 ZMQ 的使用详解
- [14]. SDR 教程实战 —— 利用 GNU Radio + HackRF 做 FM 收音机
- [15]. SDR 教程实战 —— 利用 GNU Radio + HackRF 做蓝牙定频测试工具(超低成本)
- [16]. SDR 教程实战 —— 利用 GNU Radio + HackRF + WireShark 做蓝牙抓包器(超低成本)
视频和博客
: 如果觉得不错,帮忙点个支持哈~
[SDR] SDR 教程实战 —— 利用 GNU Radio + HackRF 手把手深入了解蓝牙协议栈(从电磁波 -> 01数据流 -> 蓝牙数据包)的更多相关文章
- [SDR] GNU Radio 系列教程(十四) —— GNU Radio 低阶到高阶用法的分水岭 ZMQ 的使用详解
目录 1.前言 2.ZMQ 块的类型 3.ZMQ 块的使用 4.DEMO 4.1 同一台电脑上的两个流程图 4.2 不同电脑上的两个流程图 4.3 作为 REQ/REP 服务器的 Python 程序 ...
- GnuRadio Hacking②:使用SDR嗅探北欧芯片无线键盘鼠标数据包
0×00 前言 上半年的时候安全公司Bastille Networks(巴士底狱)安全研究员发现大多数无线鼠标和接收器之间的通信信号是不加密的,黑客可对一两百米范围内存在漏洞的无线键鼠进行嗅探甚至劫持 ...
- GNU Radio 入门培训
1. GNU Radio介绍 1.1 什么是GNU Radio GNU Radio是一个完全开源的软件无线电结构平台,它可以用来设计和仿真,也可以用来连接真实的无线电系统.GNU Radio是一个高度 ...
- [SDR] GNU Radio 系列教程(一) —— 什么是 GNU Radio
目录 1.GNU Radio 是什么 2.我为什么要用 GNU Radio 3.数字信号处理 3.1 一点信号理论 3.2 将数字信号处理应用于无线电传输 4.基于流程图的模块化数字信号处理方法 本文 ...
- [SDR] GNU Radio 系列教程(二) —— 绘制第一个信号分析流程图
目录 1.前言 2.启动 GNU Radio 3.新增块 4.运行 本文视频 参考链接 1.前言 本文将介绍如何在 GNU Radio 中创建和运行第一个流程图. 2.启动 GNU Radio GNU ...
- GNU Radio在SDR领域的应用
1 Software Defined Radio 软件无线电(Software Defined Radio,SDR)是一种实现无线通信的新概念和体制.其中以往只能在硬件中实现的组件(例如混频器,滤波器 ...
- GNU Radio安装教程: Ubuntu14.04 + uhd3.10.0 + gnuradio3.7.10.1
1. 更新和安装依赖项 在编译安装uhd和gnuradio之前,确保已安装所需依赖项.Ubuntu系统运行: sudo apt-get update 安装UHD和GNURadio所需依赖项: On U ...
- GNU Radio的hello world(转)
运行GNU Radio 需要注意的是,如果您的项目不需要用到硬件源和硬件池的话,直接使用Shell运行GRC是没有问题的.但是需要用到硬件源和硬件池的话,请记得使用管理员权限运行GRC,否则项目在执行 ...
- 【译】GNU Radio How to write a block 【如何开发用户模块及编写功能块】
本文讲解如何在GNU Radio中添加用户开发的信号处理模块,译文如有不当之处可参考原文地址:http://gnuradio.microembedded.com/outoftreemodules Ou ...
- GNU Radio无线通信嗅探基础
文章内容简介 1.使用哪些grc模块完成我们的嗅探工作 2.如何选择参数以获取最完美的波形 3.如何从波形还原回数据 我接下来会使用电视棒(RTL-SDR)嗅探一个固定码遥控锁开发组件. 我使用如下的 ...
随机推荐
- Pandas 加载数据的方法和技巧
哈喽大家好,我是咸鱼 相信小伙伴们在学习 python 数据分析的过程中或多或少都会听说或者使用过 pandas pandas 是 python 的一个拓展库,常用于数据分析 今天咸鱼将介绍几个关于 ...
- 详解RocketMQ 顺序消费机制
摘要:顺序消息是指对于一个指定的 Topic ,消息严格按照先进先出(FIFO)的原则进行消息发布和消费,即先发布的消息先消费,后发布的消息后消费. 本文分享自华为云社区<RocketMQ 顺序 ...
- Python的Lambda函数: 一把极简编程的瑞士军刀
Python中的lambda函数,或者叫匿名函数,是一个极其强大的工具.它以简洁.优雅的语法提供了创建函数的快速方式.在本篇文章中,我们将全方位地深入研究lambda函数的用法和特点,通过理论和实例相 ...
- RDD练习:词频统计
一.词频统计: 1.读文本文件生成RDD lines lines=sc.textFile("file:///home/hadoop/word.txt") #读取本地文件 lines ...
- 阿里云ASK试用心得(避坑贴)
前言 常年BP阿里云的各种服务,今天却被阿里云给上了一课,这一套组合拳把我安排的明明白白,血亏50大洋,算是提前为各位大佬排坑了,预祝大家中秋快乐 目的 最近阿里云首页放出了免费试用的活动,本着不用白 ...
- 图书商城项目练习②后端服务Node/Express/Sqlite
本系列文章是为学习Vue的项目练习笔记,尽量详细记录一下一个完整项目的开发过程.面向初学者,本人也是初学者,搬砖技术还不成熟.项目在技术上前端为主,包含一些后端代码,从基础的数据库(Sqlite).到 ...
- 详解共识算法的Raft算法模拟数
摘要:Raft算法是一种分布式共识算法,用于解决分布式系统中的一致性问题. 本文分享自华为云社区<共识算法之Raft算法模拟数>,作者: TiAmoZhang . 01.Leader选举 ...
- 痞子衡嵌入式:恩智浦i.MX RT1xxx系列MCU启动那些事(10)- 从Serial NAND启动
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是恩智浦i.MXRT1xxx系列MCU的Serial NAND启动. 最近越来越多的客户在咨询 i.MXRT1xxx 从 Serial N ...
- 手写raft(一) 实现leader选举
1. 一致性算法介绍 1.1 一致性同步与Paxos算法 对可靠性有很高要求的系统,通常都会额外部署1至多个机器为备用副本组成主备集群,避免出现单点故障. 有状态的系统需要主节点与备用副本间以某种方式 ...
- 快速切换 nodejs 的版本
最近在开发一个常驻进程.定时任务统一调度系统,以应对开发在进程管理方面遇到的各种复杂问题. 组里开发项目,一般来说是一个人承包整个项目,包括调度器设计,还有后台系统.我还有一部分工作,是队列相关的信息 ...