CVE-2020-0022 蓝牙漏洞复现

CVE-2020-0022

参考连接:
CVE-2020-0022 蓝牙漏洞初探(上)一个bug引发的血案-安全客 - 安全资讯平台 (anquanke.com)
CVE-2020-0022 “BlueFrag”漏洞分析 (bestwing.me)
Diff - 3cb7149d8fed2d7d77ceaa95bf845224c4db3baf^! - platform/system/bt - Git at Google (googlesource.com)
个人学习记录,参考很多前辈的文章。如果有错误,请指出,我将不胜感激。

一、 环境搭建

受害机器:Nexus 5X + Android 7.1.2
攻击机器:Ubuntu + 外接蓝牙适配器

二、 漏洞分析

HCI与L2CAP介绍

​ 在手机、笔记本电脑等设备中,蓝牙的结构一般是分为host和controller,controller也就是蓝牙芯片。两个设备之间通过hci层来进行数据的传递。如下图:

image-20230512153117419

image-20230529134510819

CVE-2020-0022漏洞位于HCI层,漏洞补丁代码位于hci/src/packet_fragmenter.cc中的reassemble_and_dispatch()函数中,该函数是用于数据包分片的重组。

​ L2CAP层是HCI层的上层,可以将L2CAP协议类比为网络中的TCP/UDP协议,实现了更为完善的数据传输功能。

  • 在发送路径上,上不同的上层协议层传输给L2CAP的数据统称为SDU(Service Data Unit)。SDU的最大长度可达65536字节,当较大的SDU其进入L2CAP层后,则会触发分段(Segmentation),得到多个较小的数据片段,L2CAP层对每个片段添加L2CAP协议头,封装为PDU数据包并传递至HCI层。当单个PDU进入HCI层,若其大小超过HCI层的限定,则触发分解(Fragmentation),再对得到的每块碎片数据分别添加HCI头,并依次发送至蓝牙控制器。
  • 在接收路径上,相应地,对于蓝牙控制器收到的多个HCI数据包,HCI层需要对其进行重组(与分解对应),得到PDU传递给L2CAP层;类似地,L2CAP层需要对多个PDU进行重组(与分段对应),得到SDU传递给上层协议层。
  • 在蓝牙核心规范中,HCI ACL数据包的格式如下

image-20230512162958580

​ 其中,PB Flag是Packet Boundary Flag的缩写,该字段占用两个bit:

当其数值为00或10时,表征当前HCI包是上层L2CAP PDU分解所得碎片中的首包;

当其数值为01时,表征当前HCI包是上层L2CAP PDU分解所得碎片中的续包;

当其值为11时,表征其封装了一个完整的上层L2CAP PDU。

​ 在蓝牙核心规范中,普通有连接的L2CAP 包的格式如下 (参考L2CAP数据包)

image-20230512172845218

注:Length字段不包括Basic L2CAP header的长度

漏洞代码分析

  • CVE-2020-0022漏洞位于HCI层,漏洞补丁代码位于hci/src/packet_fragmenter.cc(以7.1.2_r1为例)packet_fragmenter.c - Android Code Search 中的reassemble_and_dispatch()函数中,该函数是用于数据包分片的重组。对于过长的ACL数据包需要进行包的重组。
  1. 首先该函数从stream中读取一些信息并且设置了一些变量:
    • l2cap_length:整个l2cap数据包的数据部分的长度
    • acl_length:整个数据包的长度
    • HCI_ACL_PREAMBLE_SIZE:是固定值4字节
    • boundary_flag:是从数据包中读取了分段标记,PB的值
      • PB FLAGS == 00(bit) 时,代表 Host -> Contoller 的 L2CAP 的首包
      • PB FLAGS == 10(bit) 时,代表 Contoller -> Host 的 L2CAP 的首包
      • PB FLAGS == 01(bit) 时,代表 Host -> Contoller 或者 Contoller -> Host 的 L2CAP 的续包
  2. 当读取道boundary_flag是10(10是二进制,也就是十进制的2)的时候,进入首包的处理逻辑:
    • 需要注意full_length代表的是组装完全的数据包的长度,可能传入的时候是分包的,第一个包的长度不等于full_length
  3. 接下来有一些错误处理掠过,如果条件全部满足,则会创建一个partial_packet来处理数据包:
    • 数据包拼接的结果就会放在这个partial_packet中,所以他的长度已经被提前设置为full_length了,拼接全部完成之后这个partial_packet的长度就会等于full_length
    • 其中还要关注offset,offset就是偏移的意思,就是标记数据包处理到了哪个位置
    • 如果是分包中的首包的话,就直接拼接道partial_packet就可以了,后续的包的处理就是把data部分截出来,拼接道partial_packet中。这里这个memcpy就是首包的拼接。

帮助理解的图

  1. 后续包的处理逻辑 (漏洞位置)
    • 首先关注projected_offset的计算是 partial_packet已经处理到的位置 + 目前这个包的数据部分。实际上projected_offset就等于拼接上现在这个包的数据部分后,这个partial_packet的长度。
    • 根据partial_packet的长度的定义可以知道,正常来说,全部拼接完成之后,长度也不会超过一开始设置的full_length,也就是partial_packet->len。
    • 所以下方有一个逻辑判断,projected_offset有没有大于partial_packet->len,如果大于了,就意味着拼接完的包,比最开始说好的长度要长,那么肯定是出了问题。
    • 下面的处理逻辑就是将目前packet的长度修正,将多余的部分减去。将projected_offset也修正,让他等于全部拼接完成后的数据包的长度。
    • 后面再开始本次数据包的拼接,然而问题在于packet->len - packet->offset这里。packet->offset是常量4,那么如果packet->len的值小于4就会出现负数的情况,memcpy函数中没有负数的概念,-1-2这种负数由于补码就会变成极大值,最终导致溢出。漏洞就出现在这里
    • 再去查看上面packet->len的修复逻辑是,partial_packet->len - partial_packet->offset,实际上等于下图的橙色部分。只要让这一部分小于4就会产生漏洞。也就是让发送的数据包正好需要分包。比如说1024个字节之后需要分包,那么就发送1026个字节,分包的同时也可以让橙色部分等于2

三、 漏洞复现

leommxj/cve-2020-0022: poc for cve-2020-0022 (github.com)
image-20230511152354363

posted @ 2023-10-30 23:28  d3solate  阅读(1211)  评论(0)    收藏  举报