BLE CTF 应用层协议练习(上)
1、固件刷写
这里选择的是一个ESP32开发板加一个蓝牙适配器
也可以命令行看: python -m serial.tools.list_ports 3. 安装 esptool pip install esptool 如果 pip 不识别,用: python -m pip install esptool 4. 下载 BLE CTF 项目 git clone https://github.com/hackgnar/ble_ctf.git cd ble_ctf 5. 刷写固件 如果你的串口是 COM20,命令大概是: python -m esptool --chip esp32 --port COM20 --baud 460800 write_flash -z 0x1000 build\bootloader\bootloader.bin 0x8000 build\partition_table\partition-table.bin 0x10000 build\ble_ctf.bin安装好了启动一下,就能监听到他的Boot了。按左侧EN按钮并监听本机端口即可,同时需要一个蓝牙适配器。
连这个MAC地址就对了。
2、设备扫描
这是我们的蓝牙设备
我们启动蓝牙适配器
sudo hcitool -i hci1 lescan
使用蓝牙适配器扫描周围设备
这里我们设备名就是这个BLECTF,记下他的蓝牙MAC地址,后续使用。
20:9B:A9:7D:22:4E BLECTF
gatttool -i hci1 -b 20:9B:A9:7D:22:4E -t public --char-read -a 0x002a
这条命令是在Ubuntu中通过蓝牙适配器读取ESP32上的0x002a句柄数据,是我们当前CTF得分。
gatttool:操作BLE GATT服务工具 -i hci1:指定蓝牙适配器 -b xxx:指定MAC地址 -t public:ESP32使用公开地址类型 --char-read:执行读取操作 -a 0x002a:读取编号为0x002a的ATT HandleASCII转换过来是0分。
GATT commands --primary 发现GATT服务 --characteristics 发现设备上所有的characteristics --char-read 读某个characteristics,需要指定一个handle(句柄) --char-write 写某个characteristics,需要指定一个handle,使用Write Without Response的方式 --char-write-req 写某个characteristics,需要指定一个handle,使用Write Request的方式 --char-desc 发现所有的Characteristics Descriptor --listen 监听Characteristics的notification或者indication Primary Services/Characteristics arguments -s, --start=0x0001 起始handle -e, --end=0xffff 结束handle -u, --uuid=0x1801 16比特或者128比特的UUID Characteristics Value/Descriptor Read/Write arguments -a, --handle=0x0001 通过handle来读写characteristic,后面接handle值 -n, --value=0x0001 写characteristic时候的参数,后面接具体的值 Application Options: -i, --adapter=hciX 后面接设备描述, 如hci0等 -b, --device=MAC 远端设备的蓝牙地址 -t, --addr-type=[public | random] 远端设备蓝牙地址的类型,默认为public -m, --mtu=MTU att协议的MTU大小 -p, --psm=PSM 制定gatt的PSM,默认值为0 -l, --sec-level=[low | medium | high] 安全等级,默认为low -I, --interactive 交互式模式完整架构
我们再一次提及这个大框架,首先就是我们的应用和控制层,我们涉及的就是GATT、ATT协议等。
顶部是应用,这是用户通过API交互的层,以便于蓝牙LE协议。应用层重要部分包括配置文件、服务和特性。接下来的层构成了主机,基本上决定蓝牙设备之间如何存储和数据交换。
蓝牙主机由以下层组成:
逻辑链路控制与适配协议(L2CAP):为上层提供数据封装服务。
安全管理协议(SMP):定义并提供安全通信和方法
属性协议(ATT):运行设备将某些数据片段暴露给另一设备
通用属性配置文件(GATT):定义使用ATT层所需的子程序
通过访问配置文件(GAP):直接与应用程序接口,处理设备发现和连接相关服务
1、GAP:设备角色与拓扑
面向连接通信和广播通信。
定义:
面向连接通信,当设备之间存在专用连接,形成双向通信时。
广播通信:当设备在为先建立连接的情况下通过广播数据包进行通信,且范围内的设备接收数据包。
设备角色:
GAP层定义了蓝牙LE网络中节点的特点设备角色。这些角色决定了设备如何宣传其存在,或如何扫描并连接其他节点等。
广告和扫描指蓝牙LE设备项目感知彼此存在和连接可能性的过程。两个蓝牙LE设备要互相连接,其中一个需要宣传其存在和连接意愿,另一个负责扫描此类设备。
这里又出现了一个新的定义,中央与外围,我们这里向外发广播、宣传愿意存在和愿意连接的设备当作外设,而我们扫描的设备作为核心。
这里有几个特点,中央设备可同时向多个外设发送连接请求,并承担主机角色。外设也可通过连接建立后重启广告流程,接收其他中心节点的连接请求。
这里指我们的角色身份。
该通信方式常用于近距离信标、室内导航以及许多需要低功耗设备同事向多个设备传输少量数据的应用中。
广播拓扑的优点是可以广播到的设备数量没限制。
如图所示。
2、ATT&GATT:数据表示与交换
属性协议ATT层是蓝牙LE设备连接阶段数据传输、接收和处理的基础。它基于客户端-服务器架构,服务器持有数据,可以直接发送给客户端,或者客户端从服务器轮询数据。
该层定义的客户端和服务器角色与GAP层定义和外围中心角色独立分配。所以中央设备既可以是客户端也可以是服务器,外设也一样。
大多数情况下,外设是服务器,因为外设是获取数据并保存数据的设备。中央设备通常是客户端,因为他是接收服务器数据的设备。
ATT层定义了一个成为属性的数据结构,GATT服务器用它来存储数据。服务器可以同时持有多种不同属性。
GATT,通用属性配置文件,位于ATT之上,将属性层级分类为配置文件、服务和特性来构建。GATT层利用这些概念来规范蓝牙设备间数据传输。
简介、服务于特征
我们用一个心率传感器为例。心率值将被保存为一个属性,成为特征值属性。还有另一个属性,存储价值属性中存储的数据元数据,称为特征声明属性。这两个属性共同构成所谓特征。
总结:我们需要记住,GAP负责”找到并连接设备“,GATT负责”数据如何组织“,ATT负责”按照Handle读写数据“,L2CAP负责”承载和分流“
本题拓扑
能清晰看到两条链路。
这是我们的协议层,层层封装。
我们这里的ESP32开发板启动后会进行广播(名称、MAC、Service、UUID),通过37、38、39进行广播。
然后我们的Ubuntu执行 sudo hcitool -i hci1 lescan 去监听身边蓝牙设备。
然后建立BLE连接,这里用的是GAP角色,我们Ubuntu就是Central中心,负责主动扫描和发起连接,ESP32服务端,进行广播并接收连接。
此时GAP解决:
设备在哪儿?
名称、是否可连接?
由谁发起?
这是我们的GATT架构图,建立连接后,客户端需知道ESP32提供哪些功能,GATT便可以用以下结构。
他由服务->特征->(描述、值等)链路组成。
如图我们的BLE CTF Service如下,有得分、提交、挑战等组成。
GATT解决的是设备有什么功能、数据支持哪个服务、数据支持读写还是通知、语义等。
这里ATT其实是一张属性表格,如下:
其中0x2800=Primary Service声明
0x2803=Characteristic声明
0x2902=CCCD通知配置描述符
Service、Characteristic 和 Descriptor,本质上都是由 Attribute 组合出来的。
第六部分,为什么一个特征占有多个Handle,一个特征通常至少需要两条Attribute
这是Characteristic Declaration告诉客户端:
特征支持Read
真正数据在0x002e
他的UUID,下一步才是实际数据。
所以读取:
gatttool ... --char-read -a 0x002e,读取的是Characteristic Value,不是Service声明。
最后举例我们协议是如何流动的。
gatttool -i hci1 -b 20:9B:A9:7D:22:4E --char-read -a 0x002e
1、gatttool用户要求读取Handle 0x002e
2、GATT判断这是一次读取Characterisic Vlaue操作
3、ATT生成ATT Read Request Opcode=0x0A Handle =0x002e
4、L2CAP 把ATT请求放入固定信道CID 0x0004
5、HCI Unbuntu 把数据交给USB蓝牙适配器
6、Link Layer 安排连接事件、选择数据信道、添加校验
7、PHY 通过2.4GHz无线发送
8、ESP32收到请求后查找Handle 0x002e
9、权限检查 确认该属性允许Read
10、ATT Read Response 返回0x002e对应的Value
11、gatttool将字节显示为16进制
但有时我们协议是并行关系
GAP比较特殊,它更像一套管理设备行为的规范,不单纯承载GATT上的数据包协议。
最终总结:
ESP32通过GAP广播
Ubuntu扫描并建立连接
GATT描述ESP32有哪些服务和数据
ATT通过Handle读取或写入具体属性
L2CAP承载并区分ATT、SMP等协议
HCI把请求交给蓝牙控制器
Link Layer和PHY通过2.4GHz完成无线通信
思考:
在这里我们可以理解为
ATT(怎么读写这些数据):
是真正按Handle读写通知、指示的协议。
GATT(设备提供什么数据和功能):
基于ATT定义数据如何组织和操作,不负责实际存储,真正存储数据在服务端Attribute Table
GAP(找到并连接设备):
不负责数据传输,它规定设备如何广播、扫描、发现、连接以及Central/Peripheral角色
L2CAP+Link Layer +PHY主要负责承载、链路和无线传输
最小化描述
一个Service里有什么?
这是他的一组相关功能,这些功能在GATT中叫做Characteristic(特征)。
Service自己也需要一条Attribute来声明:
他表达的是这是一个新的Service,这个Service的类型又Value中的UUID来表示。
一个Characteristic里面有什么?
如上图,Characteristic Declaration声明功能是什么、支持什么操作、实际数据在哪儿。
例如这里表达的是声明了一个Characteristic,他支持Read,实际数据在0x002e。
Characteristic Value,保存业务数据的位置
Descriptor是对于Characteristic的补充说明或配置
FLAG1
看一下第一关的readme
第一关检查我们的分数,然后提交以下参数获取flag,最后检查我们的分数,第一关是送分的,我们直接引用就可以了。
这是0分,把MAC地址改一下即可。
FLAG2
gatttool -b 20:9B:A9:7D:22:4E --char-write-req -a 0x002c \ -n $(echo -n "d205303e099ceff44835" | xxd -ps)第二关,检查一下handle 0x002e 的ASCII码,并且提交flag,确保我们覆盖这个值。
我们读取即可,看一下读取命令。
我们这里0x002e是BLE属性的地址,里面存着一串字节
gatttool读取后默认是16进制显示例如:66 6c 61 67
我们按照要求去做,这是我们读取的Handle 0x002e的Value,我们再进行ASCII解码。得到6432303533303365303939636566663434383335,然后我们再次提交。
然后查看得分。
我们把GATT/ATT数据库结构读取出来,把Service、Characteristic、Vlaue Handle和Descriptor都枚举出来。
gatttool -b 20:9B:A9:7D:22:4E --char-desc
gatttool -b 20:9B:A9:7D:22:4E --characteristics
FLAG3
gatttool -b 20:9B:A9:7D:22:4E --char-write-req -a 0x002c \ -n $(echo -n "5cd56d74049ae40f442e" | xxd -ps) gatttool -b 20:9B:A9:7D:22:4E --char-read -a 0x002a \ | awk -F: '{print $2}' | tr -d ' ' | xxd -r -p; printf '\n'分数读取
看一下第三题,他让我们查看0x0030的ascii值,我们按照要求去做。
设备名的MD5值。
这里有几个细节,第一点就是我们md5要小写,并且取前20位的md5值进行ascii转换。
FLAG4
gatttool -b 20:9B:A9:7D:22:4E --char-write-req -a 0x002c -n $(echo -n "2b00042f7481c7b056c4"|xxd -ps)到这里才慢慢的涉及到了GATT服务,他让我们去找额外的设备属性,让我们找一下通用访问->设备名称的值。
这里用sudo gatttool -b 20:9B:A9:7D:22:4E --primary命令发现设备服务。
这里的handle是0x0014到0x001e,UUID=0x1800,意思是发现了一个UUID为0x1800(Genneric Access)的Service,它位于Handle0x0014-0x001e。
这里有三个handle。
这里GATT的Generic Access,每个BLE设备几乎都有一个标准服务。
GATT是BLE提供数据的数据库
Generic Access 是BLE标准规定的一个服务(固定UUID:0X1800)
Device Name 是这个服务的标准特征(固定UUID:0X2A00)
那我们就关注这个UUID为0X1800的,也就是handle = 0x0014句柄。 Handle 只是告诉你:这个服务在当前设备的 GATT 数据库中存放的位置。
gatttool --characteristics
查看这个Service里的Characteristic。
注意,这里的参数--char-read,他读取的是Characteristic里面的Value Handle,而不是Service Handle。
sudo gatttool -b 20:9B:A9:7D:22:4E --characteristics
先执行,看所有的特征。
仅有这三个在Generic范围内。这三个的UUID是2A00、2A01、2A06,我们依次读取。
2b00042f7481c7b056c4b410d28f33cf,尝试提交吧。
这里需要注意,gatttool -n 会把数据变成16进制字节,我们要转换为32位ASCII
这个表格足够清晰吧,这次我们就知道了,要读取的是Value Handle,而不是其他的。
转换成ASCII码。2b00042f7481c7b056c4b410d28f33cf
FLAG5
根据提示来做。
gatttool -b 20:9B:A9:7D:22:4E --char-read -a 0x0032
我们转换为ASCII码数。
提示我们有一个地方可以任意写。
因为这个Handle是一个Value Handle,那我们就去找一下他前后是什么。
在这一条。我们缩小范围,从0x0028到0x0038。
这里的0x0032就是我们读取的Write anything here。
这里有一个char properties = 0x0a,这里的二进制是00001010,代表0x02 = Read,0x08 = Write。
所以0x0a = Read + Write。
说明这个Characteristic的UUID:FF05,Value Handle:0x0032,可读又可写。
gatttool -b 20:9B:A9:7D:22:4E --char-write-req -a 0x0032 -n $(echo -n "test" | xxd -ps)
gatttool -b 20:9B:A9:7D:22:4E --char-write-req \ -a 0x002c \ -n $(echo -n "3873c0270763568cf7aa" | xxd -ps)这一题就是我们先根据提示,然后找到这个关Value Handle一组的数据,然后读取,得到他这个特征值可以任意读写,0x0a,然后我们写入再读取,就能得到20位的flag,然后再写入即可。
Characteristic 的属性允许写入;写入会触发设备逻辑,并使该 Value 的内容发生变化。
FLAG6
先读取0x0034读取指令,这里提示我们要用一些工具写入16进制或ASCII的方法。
gatttool -b 20:9B:A9:7D:22:4E --char-read -a 0x0034 | awk -F: '{print $2}' | tr -d ' ' | xxd -r -p在这里写入ASCII码的值。yo写在这里吧,推测。
我们可以查看是否可写,就这个0x0034。
gatttool -b 20:9B:A9:7D:22:4E --characteristics依旧先看特征。
可写又可读,那我们尝试把yo写入到这个value handle吧, 使用 ATT Write Request,把 ASCII 字符串yo写入 Value Handle0x0034。
应该就是把yo转换成Ascii码,然后写入进。我们再次读取。
这里BLE接收的信息是16进制的,我们直接传输字节是不可以的。
包括我们的字符串、数字、JSON、密钥都是上层对这些字节的解释。从这个点触发,BLE、CAN、TCP、UART、MQTT本质都是这个逻辑。
比如UDS的10 01,是两个字节“0x10 0x01”,ECU根据协议解释 0x10 = DiagnosticSessionControl 0x01 = Default Session。
FLAG7
gatttool -b 20:9B:A9:7D:22:4E --char-read -a 0x0036 | awk -F: '{print $2}' | tr -d ' ' | xxd -r -p依旧ASCII码转换然后写入。
FLAG8
gatttool -b 20:9B:A9:7D:22:4E \ --char-write-req \ -a 0x003A \ -n c9看一下,还是读取0x0038。
这里让我们把0xC9写入到58。
再次读取0x0038,转换ASCII。
这关就是考一个16进制转换,如果58没转换肯定是写不进去的,并且需要在0x0038里面找flag。
FLAG9
这是描述,看来要搓脚本了。
我们就像这个0x003cHandle去尝试写入00到ff,然后每次写入读取他的值,查看是否有变化。
FLAG10
gatttool -b 20:9B:A9:7D:22:4E --char-read -a 0x002a \ | awk -F: '{print $2}' | tr -d ' ' | xxd -r -p printf '\n'这里需要注意一点,就是gatttool --char-read每一次都是connect->read->disconnect,所以非常慢,这也是官方说的方法正确,读取90s左右。