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 BLECTFgatttool -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, --start0x0001 起始handle -e, --end0xffff 结束handle -u, --uuid0x1801 16比特或者128比特的UUID Characteristics Value/Descriptor Read/Write arguments -a, --handle0x0001 通过handle来读写characteristic后面接handle值 -n, --value0x0001 写characteristic时候的参数后面接具体的值 Application Options: -i, --adapterhciX 后面接设备描述 如hci0等 -b, --deviceMAC 远端设备的蓝牙地址 -t, --addr-type[public | random] 远端设备蓝牙地址的类型默认为public -m, --mtuMTU att协议的MTU大小 -p, --psmPSM 制定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、ATTGATT数据表示与交换属性协议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其实是一张属性表格如下其中0x2800Primary Service声明0x2803Characteristic声明0x2902CCCD通知配置描述符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 0x002e1、gatttool用户要求读取Handle 0x002e2、GATT判断这是一次读取Characterisic Vlaue操作3、ATT生成ATT Read Request Opcode0x0A Handle 0x002e4、L2CAP 把ATT请求放入固定信道CID 0x00045、HCI Unbuntu 把数据交给USB蓝牙适配器6、Link Layer 安排连接事件、选择数据信道、添加校验7、PHY 通过2.4GHz无线发送8、ESP32收到请求后查找Handle 0x002e9、权限检查 确认该属性允许Read10、ATT Read Response 返回0x002e对应的Value11、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 TableGAP找到并连接设备不负责数据传输它规定设备如何广播、扫描、发现、连接以及Central/Peripheral角色L2CAPLink 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地址改一下即可。FLAG2gatttool -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-descgatttool -b 20:9B:A9:7D:22:4E --characteristicsFLAG3gatttool -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转换。FLAG4gatttool -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,UUID0x1800意思是发现了一个UUID为0x1800Genneric Access的Service它位于Handle0x0014-0x001e。这里有三个handle。这里GATT的Generic Access,每个BLE设备几乎都有一个标准服务。GATT是BLE提供数据的数据库Generic Access 是BLE标准规定的一个服务固定UUID0X1800Device Name 是这个服务的标准特征固定UUID0X2A00那我们就关注这个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码。2b00042f7481c7b056c4b410d28f33cfFLAG5根据提示来做。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的UUIDFF05,Value Handle0x0032可读又可写。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。FLAG7gatttool -b 20:9B:A9:7D:22:4E --char-read -a 0x0036 | awk -F: {print $2} | tr -d | xxd -r -p依旧ASCII码转换然后写入。FLAG8gatttool -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然后每次写入读取他的值查看是否有变化。FLAG10gatttool -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左右。