蓝牙核心规范-快速入门

一、蓝牙架构

  • 蓝牙分为BR/EDR和BLE两个标准,前者被称为传统蓝牙,后者则为低功耗蓝牙,其中BLE是在蓝牙4.0中添加的,但这并不意味着蓝牙4.0就等于BLE,而是蓝牙4.0之后,设备可以支持蓝牙双栈。蓝牙拥有类似于OSI参考模型的架构,但是由于其自身特点,还是有些不同。

1. 蓝牙基本协议

  • 物理层:蓝牙使用2.4GHz——2.480GHz作为无线的信道。为了避免出现WLAN中信道冲突的情况,蓝牙会在40个2.4GHz频道之间进行跳频,但是在BLE配对时,只会使用2.402GHz(Band 37)、2.426GHz(Band 38)和2.480GHz(Band 39)这三个信道进行,对于BR/EDR,这个数据还有待补充。
  • 链路管理协议(Link Management Protocol, LMP):LMP是蓝牙的数据链路层,LMP是Bluetooth SIG强制要求实现的协议之一,在真正的蓝牙设备上,LMP是在芯片中实现的,就好像FullMAC模式下的WLAN芯片一样。在2019年的35C3会议上,seemoo-lab展示了internalblue项目,使得我们可以通过Broadcom和Cypress的蓝牙芯片固件接口发送LMP报文。
  • 主机控制器接口(Host Controller Interface, HCI):HCI是操作系统和蓝牙芯片的通信协议接口,是Bluetooth SIG的可选标准,但主流的操作系统都是通过HCI来控制蓝牙设备的(至少开源的Linux和Android是这样)。在Android中可以在开发者选项里打开蓝牙HCI日志记录,就可以查看Android系统的HCI交互报文。
  • 逻辑链路控制和适配接口(Logical Link Control and Adaptation Protocol, L2CAP):L2CAP是Bluetooth SIG强制要求实现的协议之一,有点类似于TCP/IP协议中的网络层协议,但是存在端口的概念,L2CAP的端口只绑定不同的蓝牙Profile,而不是系统的不同进程。
  • 射频通讯(Radio Frequency Communication, RFCOMM):RFCOMM是Bluetooth SIG的可选标准,也是类似于L2CAP一样存在端口,但是端口数量不如L2CAP那样多,具体和L2CAP的区别还有待补充。
  • 服务发现协议(Service Discovery Protocol, SDP):SDP是用来发现L2CAP和RFCOMM上面各个端口服务的Profile,客户端可以通过SDP协议,获取服务器侧开放的L2CAP和RFCOMM端口,以及这些端口所绑定的Profile类型,SDP是Bluetooth SIG强制要求实现的协议之一。

2. 蓝牙配置文件

  • AT命令(AT Commands):AT命令是用于和基带通信的方法,蓝牙也允许通过AT来使得远程蓝牙设备和本机的基带进行交互,为Bluetooth SIG可选标准。
  • 高级音频分发配置文件(Advanced Audio Distribution Profile, A2DP):A2DP可用来实现蓝牙耳机媒体音频播放,为Bluetooth SIG可选标准。
  • 免提配置文件(Handset-Free Profile, HFP):HFP可用来实现蓝牙耳机的通话音频播放,为Bluetooth SIG可选标准。
  • 属性配置文件(Attribute Profile, ATT):ATT是BLE中使用的Profile,可以通过简单的属性读写和通知来传输数据,无需定义复杂的Profile,为Bluetooth SIG可选标准。
  • 通用属性配置文件(Generic Attribute Profile, GATT):GATT是对ATT的标准化,定义了Service, Characteristic等概念,为Bluetooth SIG可选标准。
  • 还有一些配置文件是引进的其它协议的,下面举几个例子:
    • 点对点协议(Point-to-Point Protocol, PPP)
    • TCP/UDP/IP
    • 对象交换协议(Object Exchange Protocol, OBEX)
    • vCard/vCal
    • 人机界面设备(Human Interface Device, HID)

二、蓝牙连接过程

1. 蓝牙扫描——Inquiry

  • Inquiry(直译为查询)就是蓝牙信息发现的过程,目标设备必须处于Discoverable的状态下,才能被周边设备扫描到。在Inquiry之后会得到目标设备的蓝牙MAC地址。换句话说如果已经知道蓝牙MAC地址,其实是不需要执行Inquiry操作的。

2. 蓝牙连接——Page

  • Page过程会建立蓝牙物理链路,也称为ACL Link,目标设备必须处于Connectable的状态下,才会接受连接。在Page的过程中会协商很多参数,例如蓝牙版本和MTU等,最重要的是要交换CHANNEL_MAP参数,该参数使得通信双方可以进行可靠的跳频,而不至于丢失连接。

3. 蓝牙配对——Pairing

  • 在蓝牙物理连接建立后,可由一方发起配对请求,从而进入蓝牙配对流程,目标设备必须处于Pairable的状态下,才会接受连接。配对流程由安全管理器(Security Manager, SM)完成。第一步是交换安全参数,以确定配对方法,BR/EDR的配对方法有
    • BR/EDR Legacy Pairing:蓝牙2.0引入,只保护机密性,不保护完整性
    • Secure Simple Pairing:蓝牙2.1引入,对Legacy Pairing进行了增强,使用链路密钥(Link Key)加密通信链路
    • BR/EDR Secure Connections:蓝牙4.1引入,能保护机密性和完整性,使用长期密钥(LTK)加密通信链路
  • BLE的配对方法有
    • LE Legacy Pairing:蓝牙4.0引入,和Secure Simple Pairing比较类似,使用短期密钥(STK)加密通信链路
    • LE Secure Connections:蓝牙4.2引入,将BR/EDR Secure Connections移植到了BLE上,同样使用长期密钥(LTK)加密通信链路
  • 在Secure Simple Pairing中,定义了输入输出方法和密钥生成方法(俗称配对方法),用于适配种类繁多的蓝牙设备的认证,这个机制也在后续的Secure Connections机制中被沿用。
  • Secure Simple Pairing中对用户输入能力定义
CapabilityDescription
No inputDevice does not have the ability to indicate ‘yes’ or ‘no’
Yes/NoDevice has at least two buttons that can be easily mapped to ‘yes’ and ‘no’ or the device has a mechanism whereby the user can indicate either ‘yes’ or ‘no’ (see note below).
KeyboardDevice has a numeric keyboard that can input the numbers ‘0’ through ‘9’ and a confirmation. Device also has at least two buttons that can be easily mapped to ‘yes’ and ‘no’ or the device has a mechanism whereby the user can indicate either ‘yes’ or ‘no’ (see note below).

Note: ‘yes’ could be indicated by pressing a button within a certain time limit otherwise ‘no’ would be assumed.

  • Secure Simple Pairing中用户输出能力定义
CapabilityDescription
No outputDevice does not have the ability to display or communicate a 6 digit decimal number
Numeric outputDevice has the ability to display or communicate a 6 digit decimal number
  • Secure Simple Pairing中定义的I/O能力映射表,该表根据设备的输入输出能力组合确定一个唯一的I/O能力
No outputNumeric output
No inputNoInputNoOutputDisplayOnly
Yes/NoNoInputNoOutputDisplayYesNo
KeyboardKeyboardOnlyKeyboardDisplay
  • 在确定了I/O能力之后,可以确定密钥生成方法,但在BLE中,还需要先检查OOB和MITM标志位的情况,而BR/EDR不需要做这个检查(官方文档未提及)。LE Legacy Pairing使用下表进行检查,其中Check MITM代表继续进行MITM标志位的检查,而Use IO Capabilities代表使用I/O能力的匹配结果:
Initiator OOB SetInitiator OOB Not SetInitiator MITM SetInitiator MITM Not Set
Responder OOB SetUse OOBCheck MITM
Responder OOB Not SetCheck MITMCheck MITM
Responder MITM SetUse IO CapabilitiesUse IO Capabilities
Responder MITM Not SetUse IO CapabilitiesUse Just Works
  • 而LE Secure Connections使用下表进行检查,区别就是在一侧设置了OOB另一侧未设置OOB的场景表现不同:
Initiator OOB SetInitiator OOB Not SetInitiator MITM SetInitiator MITM Not Set
Responder OOB SetUse OOBUse OOB
Responder OOB Not SetUse OOBCheck MITM
Responder MITM SetUse IO CapabilitiesUse IO Capabilities
Responder MITM Not SetUse IO CapabilitiesUse Just Works
  • 如果BLE在上一步选择了Use IO Capabilities或者对于BR/EDR,则进入双方的I/O能力检查,在BLE配对方法是LE Legacy Pairing时(对于BR/EDR应该也适用)规则如下:
Initiator DisplayOnlyInitiator DisplayYesNoInitiator KeyboardOnlyInitiator NoInputNoOutputInitiator KeyboardDisplay
Responder DisplayOnlyJust Works UnauthenticatedJust Works UnauthenticatedPasskey Entry: responder displays, initiator inputs AuthenticatedJust Works UnauthenticatedPasskey Entry: responder displays, initiator inputs Authenticated
Responder DisplayYesNoJust Works UnauthenticatedJust Works (For LE Legacy Pairing) UnauthenticatedPasskey Entry: responder displays, initiator inputs AuthenticatedJust Works UnauthenticatedPasskey Entry (For LE Legacy Pairing): responder displays, initiator inputs Authenticated
Responder KeyboardOnlyPasskey Entry: initiator displays, responder inputs AuthenticatedPasskey Entry: initiator displays, responder inputs AuthenticatedPasskey Entry: initiator and responder inputs AuthenticatedJust Works UnauthenticatedPasskey Entry: initiator displays, responder inputs Authenticated
Responder NoInputNoOutputJust Works UnauthenticatedJust Works UnauthenticatedJust Works UnauthenticatedJust Works UnauthenticatedJust Works Unauthenticated
Responder KeyboardDisplayPasskey Entry: initiator displays, responder inputs AuthenticatedPasskey Entry (For LE Legacy Pairing): initiator displays, responder inputs AuthenticatedPasskey Entry: responder displays, initiator inputs AuthenticatedJust Works UnauthenticatedPasskey Entry (For LE Legacy Pairing): initiator displays, responder inputs Authenticated
  • 在BLE配对方法是LE Secure Connections时(对于BR/EDR应该也适用)规则如下:
Initiator DisplayOnlyInitiator DisplayYesNoInitiator KeyboardOnlyInitiator NoInputNoOutputInitiator KeyboardDisplay
Responder DisplayOnlyJust Works UnauthenticatedJust Works UnauthenticatedPasskey Entry: responder displays, initiator inputs AuthenticatedJust Works UnauthenticatedPasskey Entry: responder displays, initiator inputs Authenticated
Responder DisplayYesNoJust Works UnauthenticatedNumeric Comparison (For LE Secure Connections) AuthenticatedPasskey Entry: responder displays, initiator inputs AuthenticatedJust Works UnauthenticatedNumeric Comparison (For LE Secure Connections) Authenticated
Responder KeyboardOnlyPasskey Entry: initiator displays, responder inputs AuthenticatedPasskey Entry: initiator displays, responder inputs AuthenticatedPasskey Entry: initiator and responder inputs AuthenticatedJust Works UnauthenticatedPasskey Entry: initiator displays, responder inputs Authenticated
Responder NoInputNoOutputJust Works UnauthenticatedJust Works UnauthenticatedJust Works UnauthenticatedJust Works UnauthenticatedJust Works Unauthenticated
Responder KeyboardDisplayPasskey Entry: initiator displays, responder inputs AuthenticatedNumeric Comparison (For LE Secure Connections) AuthenticatedPasskey Entry: responder displays, initiator inputs AuthenticatedJust Works UnauthenticatedNumeric Comparison (For LE Secure Connections) Authenticated
  • 可以看到LE Secure Connections对下列场景下的安全性有明显的增强:
    • 双方都为DisplayYesNo
    • 一方为KeyboardDisplay而另一方为DisplayYesNo
    • 双方都为KeyboardDisplay
  • 在蓝牙配对流程的Pairing Request和Pairing Response报文中,只有双方都将其中的Secure Connections(SC)标志位设置为1的时候,最终才会使用Secure Connections,否则就会回退到旧版配对机制。由于蓝牙4.0的设备大量存在,所以目前针对Secure Connections的降级攻击是较常见的一个威胁。

4. 蓝牙绑定——Bonding

  • 在蓝牙配对流程的Pairing Request和Pairing Response报文中,可以设置Bonding Flags(BF)标志位为01,这样双方就会存储上述用于加密链路的密钥(Link Key, STK, LTK)并通过密钥分发生成IRK以备未来使用。
  • 生成IRK就意味着在双方之间创建了永久安全性,直到用户取消绑定删除IRK为止。显然必须要在配对之后才能执行绑定。

三、Linux蓝牙实现——BlueZ

  • BlueZ是Linux的官方蓝牙协议栈,目前BlueZ5的蓝牙上层API是通过Gnome DBus提供的。
  • 在Linux上可以通过bluetoothctl命令行工具直接调用BlueZ的上层接口。
  • 最近在做的是调用BlueZ的HCI接口,其原理是一个BTPROTO_HCI的Socket,BlueZ中这方面的代码在lib/hci.c中。我们可以使用HCI接口发送各类HCI报文,从而和蓝牙控制器直接交互。
  • 下面是一个获取蓝牙控制器列表的简单实现
int get_hci_dev_list(int dev_id_list[], size_t len, size_t *dev_num) {
    struct hci_dev_list_req *dl;
	struct hci_dev_req *dr;

    int i, sk, err = 0;

    sk = socket(AF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC, BTPROTO_HCI);
	if (sk < 0)
		return sk;

    dl = malloc(HCI_MAX_DEV * sizeof(*dr) + sizeof(*dl));
	if (!dl) {
        free(dl);
		err = -errno;
		return err;
	}
    
    memset(dl, 0, HCI_MAX_DEV * sizeof(*dr) + sizeof(*dl));

    dl->dev_num = HCI_MAX_DEV;
    dr = dl->dev_req;

    if (ioctl(sk, HCIGETDEVLIST, (void *) dl) < 0) {
        free(dl);
		err = -errno;
		return err;
	}

    *dev_num = dl->dev_num;

    if (len >= dl->dev_num) {
        len = dl->dev_num;
    } else {
        printf("[dev_mgmt] get_hci_dev_list: dev_id buffer too small, %d < %d.\n", len, dl->dev_num);
    }

    for (i = 0; i < len; i++, dr++) {
        if (i < len) {
            dev_id_list[i] = dr->dev_id;
        }
	}

    free(dl);
    close(sk);
	errno = err;

    return err;
}
  • 可以看到其核心还是通过Bluetooth HCI Socket的ioctl方法去做的。

四、Android蓝牙实现——BlueDroid

1. BlueDroid结构

  • BlueDroid在Android 4.2中引入BlueDroid作为蓝牙协议栈,此前一直使用的是BlueZ
  • BlueDroid的结构如下
        +-----------------+
        | BT API & BT App |
        +-----------------+
Java
+--------------------------------+
Native  +-----------------+
        |       BTIF      |
        +-----------------+
        |       BTA       |
        +-----------------+
        | BlueDroid Stack |
        +-----------------+
User space
+---------------------------------+
Kernel space
        +-----------------+
        |  Bluetooth HAL  |
        +-----------------+

2. BlueDroid中的Temporary Pairing

  • 在BlueDroid中一个特别奇怪的名词称为Temporary Pairing,翻译过来就是临时配对,但是这个词并没有出现在Bluetooth SIG的规范中,在源代码中可以找到它的类型定义BOND_TYPE_TEMPORARY,代码路径为/system/bt/btif/src/btif_dm.cc
/* if just_works and bonding bit is not set treat this as temporary */
  if (p_ssp_cfm_req->just_works &&
      !(p_ssp_cfm_req->loc_auth_req & BTM_AUTH_BONDS) &&
      !(p_ssp_cfm_req->rmt_auth_req & BTM_AUTH_BONDS) &&
      !(check_cod((RawAddress*)&p_ssp_cfm_req->bd_addr, COD_HID_POINTING)))
    pairing_cb.bond_type = BOND_TYPE_TEMPORARY;
  else
    pairing_cb.bond_type = BOND_TYPE_PERSISTENT;
  • 这里面发现BOND_TYPE_TEMPORARY意思是JustWorks模式并且没有设置Bonding Flags的情况,不设置Bonding Flags也就是所谓的配对但不绑定,并且对于这种情况下Android是会自动接受连接的,对于CVE-2019-2225漏洞之后的版本也是如此:
/* If JustWorks auto-accept */
  if (p_ssp_cfm_req->just_works) {
    /* Pairing consent for JustWorks NOT needed if:
     * 1. Incoming temporary pairing is detected
     */
    if (is_incoming && pairing_cb.bond_type == BOND_TYPE_TEMPORARY) {
      BTIF_TRACE_EVENT(
          "%s: Auto-accept JustWorks pairing for temporary incoming", __func__);
      btif_dm_ssp_reply(bd_addr, BT_SSP_VARIANT_CONSENT, true);
      return;
    }
  }
  • 所以其实攻击者可以使用临时配对发起某些攻击,因为临时配对没有任何提示,不会引起Android用户的注意。不过在Android上面对于临时配对是不会发送携带BT_BOND_STATE_BONDED这一状态的BOND_STATE_CHANGED广播的,而是会发送携带BT_BOND_STATE_NONE这一状态BOND_STATE_CHANGED广播。
if ((p_auth_cmpl->success) && (p_auth_cmpl->key_present)) {
    if ((p_auth_cmpl->key_type < HCI_LKEY_TYPE_DEBUG_COMB) ||
        (p_auth_cmpl->key_type == HCI_LKEY_TYPE_AUTH_COMB) ||
        (p_auth_cmpl->key_type == HCI_LKEY_TYPE_CHANGED_COMB) ||
        (p_auth_cmpl->key_type == HCI_LKEY_TYPE_AUTH_COMB_P_256) ||
        pairing_cb.bond_type == BOND_TYPE_PERSISTENT) {
      bt_status_t ret;
      BTIF_TRACE_DEBUG("%s: Storing link key. key_type=0x%x, bond_type=%d",
                       __func__, p_auth_cmpl->key_type, pairing_cb.bond_type);
      ret = btif_storage_add_bonded_device(&bd_addr, p_auth_cmpl->key,
                                           p_auth_cmpl->key_type,
                                           pairing_cb.pin_code_len);
      ASSERTC(ret == BT_STATUS_SUCCESS, "storing link key failed", ret);
    } else {
      BTIF_TRACE_DEBUG(
          "%s: Temporary key. Not storing. key_type=0x%x, bond_type=%d",
          __func__, p_auth_cmpl->key_type, pairing_cb.bond_type);
      if (pairing_cb.bond_type == BOND_TYPE_TEMPORARY) {
        BTIF_TRACE_DEBUG("%s: sending BT_BOND_STATE_NONE for Temp pairing",
                         __func__);
        btif_storage_remove_bonded_device(&bd_addr);
        bond_state_changed(BT_STATUS_SUCCESS, bd_addr, BT_BOND_STATE_NONE);
        return;
      }
    }
  }
  • 这种BOND_TYPE_TEMPORARY类型的连接构建也很简单,使用Linux上的pybluez就可以实现,例如实现一个Rfcomm的:
sock = bluetooth.BluetoothSocket(bluetooth.RFCOMM)
sock.connect((bd_addr, port))

五、iOS蓝牙实现——CoreBluetooth

  • iOS中的蓝牙开发框架有ExternalAccessory.framework和CoreBluetooth.framework两种
    • ExternalAccessory.framework:是BR/EDR的开发接口(蓝牙2.1+),但是只能和MFi认证的设备通信
    • CoreBluetooth.framework:是BLE的开发接口(蓝牙4.0+),可以任意使用,但并不支持蓝牙4.0+的BR/EDR
  • 也就是说,如果不考虑MFi认证的话,iOS是不支持BR/EDR的,仅支持BLE。