一、蓝牙架构
- 蓝牙分为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中对用户输入能力定义
Capability |
Description |
No input |
Device does not have the ability to indicate ‘yes’ or ‘no’ |
Yes/No |
Device 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). |
Keyboard |
Device 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中用户输出能力定义
Capability |
Description |
No output |
Device does not have the ability to display or communicate a 6 digit decimal number |
Numeric output |
Device has the ability to display or communicate a 6 digit decimal number |
- Secure Simple Pairing中定义的I/O能力映射表,该表根据设备的输入输出能力组合确定一个唯一的I/O能力
|
No output |
Numeric output |
No input |
NoInputNoOutput |
DisplayOnly |
Yes/No |
NoInputNoOutput |
DisplayYesNo |
Keyboard |
KeyboardOnly |
KeyboardDisplay |
- 在确定了I/O能力之后,可以确定密钥生成方法,但在BLE中,还需要先检查OOB和MITM标志位的情况,而BR/EDR不需要做这个检查(官方文档未提及)。LE Legacy Pairing使用下表进行检查,其中Check MITM代表继续进行MITM标志位的检查,而Use IO Capabilities代表使用I/O能力的匹配结果:
|
Initiator OOB Set |
Initiator OOB Not Set |
Initiator MITM Set |
Initiator MITM Not Set |
Responder OOB Set |
Use OOB |
Check MITM |
|
Responder OOB Not Set |
Check MITM |
Check MITM |
|
Responder MITM Set |
|
Use IO Capabilities |
Use IO Capabilities |
Responder MITM Not Set |
|
Use IO Capabilities |
Use Just Works |
- 而LE Secure Connections使用下表进行检查,区别就是在一侧设置了OOB另一侧未设置OOB的场景表现不同:
|
Initiator OOB Set |
Initiator OOB Not Set |
Initiator MITM Set |
Initiator MITM Not Set |
Responder OOB Set |
Use OOB |
Use OOB |
|
Responder OOB Not Set |
Use OOB |
Check MITM |
|
Responder MITM Set |
|
Use IO Capabilities |
Use IO Capabilities |
Responder MITM Not Set |
|
Use IO Capabilities |
Use Just Works |
- 如果BLE在上一步选择了Use IO Capabilities或者对于BR/EDR,则进入双方的I/O能力检查,在BLE配对方法是LE Legacy Pairing时(对于BR/EDR应该也适用)规则如下:
|
Initiator DisplayOnly |
Initiator DisplayYesNo |
Initiator KeyboardOnly |
Initiator NoInputNoOutput |
Initiator KeyboardDisplay |
Responder DisplayOnly |
Just Works Unauthenticated |
Just Works Unauthenticated |
Passkey Entry: responder displays, initiator inputs Authenticated |
Just Works Unauthenticated |
Passkey Entry: responder displays, initiator inputs Authenticated |
Responder DisplayYesNo |
Just Works Unauthenticated |
Just Works (For LE Legacy Pairing) Unauthenticated |
Passkey Entry: responder displays, initiator inputs Authenticated |
Just Works Unauthenticated |
Passkey Entry (For LE Legacy Pairing): responder displays, initiator inputs Authenticated |
Responder KeyboardOnly |
Passkey Entry: initiator displays, responder inputs Authenticated |
Passkey Entry: initiator displays, responder inputs Authenticated |
Passkey Entry: initiator and responder inputs Authenticated |
Just Works Unauthenticated |
Passkey Entry: initiator displays, responder inputs Authenticated |
Responder NoInputNoOutput |
Just Works Unauthenticated |
Just Works Unauthenticated |
Just Works Unauthenticated |
Just Works Unauthenticated |
Just Works Unauthenticated |
Responder KeyboardDisplay |
Passkey Entry: initiator displays, responder inputs Authenticated |
Passkey Entry (For LE Legacy Pairing): initiator displays, responder inputs Authenticated |
Passkey Entry: responder displays, initiator inputs Authenticated |
Just Works Unauthenticated |
Passkey Entry (For LE Legacy Pairing): initiator displays, responder inputs Authenticated |
- 在BLE配对方法是LE Secure Connections时(对于BR/EDR应该也适用)规则如下:
|
Initiator DisplayOnly |
Initiator DisplayYesNo |
Initiator KeyboardOnly |
Initiator NoInputNoOutput |
Initiator KeyboardDisplay |
Responder DisplayOnly |
Just Works Unauthenticated |
Just Works Unauthenticated |
Passkey Entry: responder displays, initiator inputs Authenticated |
Just Works Unauthenticated |
Passkey Entry: responder displays, initiator inputs Authenticated |
Responder DisplayYesNo |
Just Works Unauthenticated |
Numeric Comparison (For LE Secure Connections) Authenticated |
Passkey Entry: responder displays, initiator inputs Authenticated |
Just Works Unauthenticated |
Numeric Comparison (For LE Secure Connections) Authenticated |
Responder KeyboardOnly |
Passkey Entry: initiator displays, responder inputs Authenticated |
Passkey Entry: initiator displays, responder inputs Authenticated |
Passkey Entry: initiator and responder inputs Authenticated |
Just Works Unauthenticated |
Passkey Entry: initiator displays, responder inputs Authenticated |
Responder NoInputNoOutput |
Just Works Unauthenticated |
Just Works Unauthenticated |
Just Works Unauthenticated |
Just Works Unauthenticated |
Just Works Unauthenticated |
Responder KeyboardDisplay |
Passkey Entry: initiator displays, responder inputs Authenticated |
Numeric Comparison (For LE Secure Connections) Authenticated |
Passkey Entry: responder displays, initiator inputs Authenticated |
Just Works Unauthenticated |
Numeric 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。