
BLE GATT / ATT 抓包实战BLE GATT / ATT 基础 —— 属性、Handle、Characteristic 与 ATT 协议一、GATT 是什么1.1 GATT 站在哪一层1.2 GATT 和 ATT 的关系1.3 属性Attribute—— GATT 的最小单位1.4 Handle句柄—— GATT 的地址1.5 UUID —— 属性的类型标识1.6 常见标准 UUID 速查1.7 GATT 层级结构二、Characteristic 的完整解剖2.1 一个 Characteristic 至少占 2 个属性2.2 Properties 位域2.3 Notification vs Indication2.4 CCCD0x2902—— 通知开关三、ATT 协议3.1 ATT 在协议栈的位置3.2 ATT 的 6 种 PDU 类型3.3 ATT PDU 的通用格式3.4 PDU 详细 opcode 速查3.4.1 Requests需要 Response3.4.2 Responses3.4.3 Commands不需要 Response3.4.4 Notifications3.4.5 Indications3.4.6 Confirmations3.5 ATT_MTU3.6 ATT 错误处理Error Response0x01一、GATT 是什么1.1 GATT 站在哪一层应用层心率应用 ↑ GATT Generic Attribute Profile ← 数据组织规范 ↑ 基于 ATT Attribute Protocol ← 传输协议本文重点 ↑ 基于 L2CAP逻辑链路控制与适配协议CID 0x0004 ↑ 基于 Link Layer链路层 ↑ 基于 Physical Layer1M / 2M / Coded PHY记住这条链GATT 基于 ATTATT 跑在 L2CAP CID 0x0004 上L2CAP 跑在链路层上。 抓包时从上往下解每一层都有自己的头部。1.2 GATT 和 ATT 的关系协议角色类比ATT传输协议定义怎么读写数据像 SQL 语言SELECT/UPDATEGATT框架/规范定义数据怎么组织像数据库表结构规范有哪些表、字段1.3 属性Attribute—— GATT 的最小单位一个属性包含4 个部分组成部分说明例子Attribute Handle属性的 “门牌号”16-bit0x000dAttribute Type属性的类型用 UUID 表示0x2a01AppearanceAttribute Value属性的实际值0x0341心率带Attribute Permissions读/写权限Read Only / ReadWrite⚠️ 注意Permissions 不出现在空口上它是服务端内部的安全策略。空口上客户端只能看到 Properties见第 2.2 节。1.4 Handle句柄—— GATT 的地址为什么需要 Handle为什么不直接用 UUID 来读写数据因为同一个 UUID 可能出现多次但 Handle 是全局唯一的。比如一个设备可能有 3 个温度传感器特征UUID 都是0x2A6E但 handle 分别是0x0012、0x0018、0x0024。Handle 的特点特点说明16-bit范围0x0001~0xFFFF服务器分配GATT Server板子在建库时分配单调递增通常按声明顺序递增客户端靠它操作所有 ATT 请求都填 handleUUID 靠它发现客户端先通过发现流程知道handle X 是 UUID Y核心结论所有 ATT 操作读/写/通知都是针对 handle 的不是针对 UUID。1.5 UUID —— 属性的类型标识两种长度类型长度格式用途16-bit UUID2 字节0x180D蓝牙 SIG 定义的标准类型128-bit UUID16 字节0000180D-0000-1000-8000-00805F9B34FB厂商自定义转换规则蓝牙 SIG 定义了一个基础 UUIDBase UUID00000000-0000-1000-8000-00805F9B34FB ↑ 这 16 位用 16-bit UUID 替换所以0x180D → 0000180D-0000-1000-8000-00805F9B34FB 0x2A37 → 00002A37-0000-1000-8000-00805F9B34FB 因为 16-bit UUID 都能无损展开成 128-bit所以协议里传输 16-bit UUID 只是省带宽两者语义完全等价。1.6 常见标准 UUID 速查服务ServiceUUID名称0x1800Generic AccessGAP0x1801Generic AttributeGATT0x180ADevice Information0x180DHeart Rate⭐0x180FBattery⭐0x1812Human Interface DeviceHID0x110AAudio Source特征CharacteristicUUID名称0x2A00Device Name0x2A01Appearance外观0x2A04Peripheral Preferred Connection ParametersPPCP0x2A19Battery Level⭐0x2A37Heart Rate Measurement⭐0x2A38Body Sensor Location0x2A39Heart Rate Control Point0x2A29Manufacturer Name0x2A24Model Number声明/描述符Declaration / DescriptorUUID名称作用0x2800Primary Service声明这是一个主服务0x2801Secondary Service声明次服务0x2802Include引用其他服务0x2803Characteristic声明这是一个特征0x2902Client Characteristic ConfigurationCCCD控制通知开关⭐0x2901Characteristic User Description人类可读描述0x2900Characteristic Extended Properties扩展属性0x2904Characteristic Presentation Format数据格式单位、指数等1.7 GATT 层级结构Profile 规范非协议实体 │ ├── Service 1 服务如心率服务 0x180D │ │ │ ├── Characteristic 1 特征如心率测量 0x2A37 │ │ ├── Declaration声明UUID 0x2803 │ │ ├── Value值UUID 0x2A37 │ │ └── Descriptor描述符如 CCCD 0x2902 │ │ │ └── Characteristic 2 如 Body Sensor Location 0x2A38 │ ├── Declaration0x2803 │ └── Value0x2A38 │ └── Service 2 如电池服务 0x180F │ └── Characteristic Battery Level0x2A19 ├── Declaration0x2803 ├── Value0x2A19 └── CCCD0x2902概念说明是否协议实体Profile一组服务的 “规范/约定”如 “心率 Profile” 规定了必须有 HRS DIS❌ 概念不是实体Service一组相关特征的集合✅ 实体Characteristic数据的基本单位✅ 实体Descriptor特征的附加元数据✅ 实体二、Characteristic 的完整解剖2.1 一个 Characteristic 至少占 2 个属性一个 Characteristic 在 GATT 表里占用至少 2 个属性Attribute属性 1Characteristic Declaration特征声明UUID 0x2803Value 包含 3 个部分字段长度说明Properties1 字节读/写/通知权限位域见 2.3Value Handle2 字节特征值的 handle指向下一个属性Characteristic UUID2 或 16 字节特征的 UUID2.1.2 属性 2Characteristic Value特征值UUID 特征本身的 UUID如0x2A37Value 实际数据属性 3Descriptors可选如 CCCD0x2902。2.2 Properties 位域Properties 是1 字节的位域定义在 Characteristic Declaration 里Bit名称含义0Broadcast允许广播该特征值1Read允许读2Write Without Response允许写不需要回应3Write允许写需要回应4Notify允许通知服务端主动发无确认5Indicate允许指示服务端主动发需确认6Authenticated Signed Writes允许签名写7Extended Properties有扩展属性速记常数值Properties 值拆解含义0x02bit1Read0x08bit3Write0x0Abit1 bit3Read Write0x10bit4Notify0x12bit1 bit4Read Notify0x20bit5Indicate2.3 Notification vs Indication这是 GATT 数据上报的两种方式对比NotificationIndicationOpcode0x1BHandle Value Notification0x1DHandle Value Indication方向Server → ClientServer → Client应用层确认❌ 不需要✅ 需要Client 回0x1EConfirmation链路层 ACK✅ 有✅ 有可靠性较低可能丢较高有确认速率快可连发慢要等确认触发条件写 CCCD 0x0001写 CCCD 0x0002典型用途传感器数据心率、温度关键告警、状态变更Notification 的 “不需要确认” 是指应用层不确认但链路层仍然有 ACK 和重传机制。 所以 Notification 在链路层是可靠的只是应用层不知道对方有没有收到。2.4 CCCD0x2902—— 通知开关CCCD Client Characteristic Configuration Descriptor是客户端用来控制服务端是否发通知的开关。值含义0x0000关闭通知和指示0x0001使能Notification0x0002使能Indication0x0003同时使能很少用为什么需要 CCCD因为服务端板子不知道客户端手机想不想收通知。所以手机连接后写 CCCD 0x0001告诉板子“我要收通知”板子收到后才在特征值变化时发 Notification如果手机不写板子不会主动发任何通知三、ATT 协议3.1 ATT 在协议栈的位置GATT数据组织规范Service / Characteristic / Descriptor ↑ 基于 ATT传输协议Read / Write / Notify / Indicate ← 这一层 ↑ 基于 L2CAPCID 0x0004固定给 ATT 用 ↑ 基于 Link LayerATT 占用的 L2CAP 通道是固定的CID 0x0004。3.2 ATT 的 6 种 PDU 类型类型方向需要回应典型 opcodeRequestClient → Server✅ 必须等 Response0x08Read By Type ReqResponseServer → Client—本身就是回应0x09Read By Type RspCommandClient → Server❌ 不等0x52Write CommandNotificationServer → Client❌ 不确认0x1BIndicationServer → Client✅ 需 Confirmation0x1DConfirmationClient → Server—回应 Indication0x1E3.3 ATT PDU 的通用格式┌──────────┬─────────────────────────┐ │ Opcode │ Parameters参数 │ │ 1 byte │ 长度取决于 opcode │ └──────────┴─────────────────────────┘Opcode 的位结构严格定义Bit: 7 6 5 4 3 2 1 0 ┌────────┬────────┬──────────────────────────┐ │AuthSig │Command │ Method │ │ Flag │ Flag │ (6 bits) │ └────────┴────────┴──────────────────────────┘位名称含义Bit 7Auth Sig Flag1 该 PDU 带认证签名Bit 6Command Flag1 Command不需回应0 Request需回应Bit 0~5Method方法编号0~63决定 “做什么操作”这个位结构解释了那些 “奇怪的” opcode 值所有 Write 操作的Method 字段都是0x12区别只在上面两个 flag 位完整 Opcode二进制bit7 AuthSigbit6 CommandMethod名称0x120001 0010000x12WriteRequest要回应0x520101 0010010x12WriteCommand不回应0xD21101 0010110x12SignedWrite Command带签名3.4 PDU 详细 opcode 速查3.4.1 Requests需要 ResponseOpcode名称作用0x02Exchange MTU Request协商 MTU0x04Find Information Request查某 handle 的类型0x08Read By Type Request按 UUID 查属性0x0ARead Request按 handle 读值0x10Read By Group Type Request按 UUID 查一组服务发现0x12Write Request写值要回应0x16Prepare Write Request准备写长数据分块0x18Execute Write Request执行写提交分块特点Client 发完必须等 Response期间不能发第二个 Request串行。3.4.2 ResponsesOpcode名称0x01Error Response任何请求失败都回这个0x03Exchange MTU Response0x05Find Information Response0x09Read By Type Response0x0BRead Response0x11Read By Group Type Response0x13Write Response3.4.3 Commands不需要 ResponseOpcode名称用途0x52Write Command快速写不等回应如串口透传 TX0xD2Signed Write Command带签名的写特点Client 发完就走可以连发多个流控靠链路层。3.4.4 NotificationsOpcode名称0x1BHandle Value NotificationServer 主动发Client 不回任何确认。3.4.5 IndicationsOpcode名称0x1DHandle Value IndicationServer 主动发Client 必须回 Confirmation。3.4.6 ConfirmationsOpcode名称0x1EHandle Value ConfirmationClient 回应 Indication。3.5 ATT_MTUATT_MTU ATT 层一次能传输的最大字节数。项值默认 ATT_MTU23 字节最大 ATT_MTU517 字节协商方式Exchange MTU Request0x02/ Response0x03为什么默认是 23链路层 payload 默认 27 字节 - L2CAP 头 4 字节 23 字节 ← ATT_MTU 默认值这就是 ATT_MTU 和 DLE 的绑定关系想让 ATT_MTU 变大必须先把链路层的 DLE 协商上去。否则链路层一次只能装 27 字节MTU 再大也没用。MTU 协商流程Client → Server: Exchange MTU Request (Client RX MTU 247) Server → Client: Exchange MTU Response (Server RX MTU 65) ──────────────────────────────────────────────────────── 最终 ATT_MTU min(247, 65) 65和 DLE 一样也是取min。MTU 对实际数据的影响Notification 的 PDU 格式[1 byte opcode][2 bytes handle][Value...]所以实际能传的数据 ATT_MTU - 3ATT_MTUNotification 最大数据2320 字节6562 字节247244 字节517514 字节 这解释了那个经典数字BLE 4.0/4.1 时代一次通知最多只能带 20 字节有效数据23 - 3。3.6 ATT 错误处理Error Response0x01当任何 Request 失败时Server 返回 Error Response┌────────┬─────────────────────────┬───────────────────────────┬─────────────┐ │ Opcode │ Request Opcode In Error │ Attribute Handle In Error │ Error Code │ │ 1 byte │ 1 byte │ 2 bytes │ 1 byte │ └────────┴─────────────────────────┴───────────────────────────┴─────────────┘字段含义Request Opcode In Error哪个请求出错了Attribute Handle In Error哪个 handle 出问题Error Code错误原因常见错误码Code名称含义0x01Invalid Handlehandle 不存在0x02Read Not Permitted不允许读0x03Write Not Permitted不允许写0x05Insufficient Authentication需要认证配对0x06Request Not Supported不支持该请求0x07Invalid Offset偏移错误0x08Insufficient Authorization需要授权0x0AAttribute Not Found找不到属性0x0CInsufficient Encryption Key Size密钥长度不够0x0DInvalid Attribute Value Length值长度错误★ 0x0A Attribute Not Found 的特殊用途服务发现就是靠这个错误码 结束的Client: Read By Group Type Request (handle 0x0001~0xffff) Server: Read By Group Type Response (服务 1, 2, 3) Client: Read By Group Type Request (handle 0x0015~0xffff) ← 从下一个 handle 继续 Server: Read By Group Type Response (服务 4, 5) Client: Read By Group Type Request (handle 0x0020~0xffff) Server: Error Response (0x0A Attribute Not Found) ← 没有了收到0x0A客户端就知道 “所有服务都发现完了”。这是一个非常巧妙的设计 —— 用错误码表示 “迭代结束”。所以抓包里看到0x0A不要慌在发现流程里它是正常的终止信号不是故障。