一. 引言
大家好,我是来自银基TIGER TEAM的Cure。本次为大家带来的是CVE-2020-0022 漏洞研究的第一部分。近年来,蓝牙已成为可移动设备的标配,在手机、智能手表等电子产品的短距离数据传输场景得以广泛应用。蓝牙的传输除依赖硬件芯片之外,还需要相关固件与主机操作系统的支持,而后两者则常常成为RCE(远程代码执行)攻击的标靶。
CVE-2020-0022是一个由于Android 系统在蓝牙传输收包重组过程所存在的代码bug而引发的漏洞。该漏洞对Android 8、9、10均存在影响,对于Android 8、9有潜力引发Memory Leak(内存泄露)和RCE,而对于Android 10则能够引起蓝牙守护进程的崩溃。相关bug可以由2020年2月发布的Android A-143894715号安全补丁进行修复。
笔者在研究该漏洞原理的时候克服了不少困难,并将心得整理成了“CVE-2020-0022 蓝牙漏洞初探”这一系列。本系列分三个部分:
第一部分:一个bug引发的血案
该部分介绍Android 蓝牙协议的框架,详细分析相关模块的代码原理,并着重分析相关bug,结合demo给出其初步危害。
第二部分:探索Memory Leak
该部分向更高的目标进发,详细阐述arm64 架构下libc库memcpy函数的实现及其“特殊”行为,并借助这一点结合demo给出相应漏洞下使能Memory Leak的方法。
第三部分:探索RCE
该部分进一步升级目标,首先明确RCE的难点所在,并详细介绍对于C++ vtable的攻击方法,并基于此给出相应漏洞下使能RCE的延伸思路。
本文为系列的第一部分,第二、三部分会在后面的时间里逐步为大家呈现。希望本文提供的技术探索与实例,能够起到抛砖引玉的作用;同时,也为初学者提供一些思路与参考。
二. 蓝牙相关知识
2.1 生活中的蓝牙
蓝牙作为短距离无线网络通信的解决方案,蓝牙已深入人们的生活,其常见应用场景包括但不限于:
1)文件传输,例如在缺少USB线的情况下在笔记本和手机之间传输照片。
2)蓝牙耳机,使能无线地在设备(例如笔记本、MP3)与耳机之间传输音频数据,为用户带来方便。
3)蓝牙打印机,例如使用手机连接打印机而打印文件而不需额外地携带U盘。
4)蓝牙上网,例如当开启“手机热点”,笔记本等设备可以使用手机流量进行上网。其中,笔记本与手机的连接方式可以选择蓝牙。
2.2 蓝牙通信架构
笔者在检索、整合相关资料后,绘制的蓝牙设备通信架构图如下所示。
图中,左半部分和右半部分分别代表一个带有蓝牙功能的设备,例如手机。其中,金粉色的矩形框代表了设备中自应用程序至物理总线驱动的软件协议栈,而蓝色部分则代表了蓝牙硬件芯片。
当发生数据交互时,应用程序所提供的数据包,经由各协议层的封装,发送至物理总线并抵达蓝牙芯片。相应固件对数据包进行处理后,载入到无线通信信号中进行传输。
图中的“诸多上层协议层”,在蓝牙上网、蓝牙耳机等不同应用场景具备着多种多样的形式,但由于其与本系列所讨论的漏洞关系不大,故不作进一步展开。图中,标为淡蓝色的L2CAP和HCI的两个协议层,作为绝大多数场景下的统一封装层,是我们较为关注的重点。
2.3 分包与重组
L2CAP层和HCI层为绝大多数蓝牙数据传输的I/O路径所共有,本节对其与漏洞相关的工作模式进行简介。
L2CAP层和HCI层在发送和接收的过程中均涉及到数据包的分包与组包,笔者绘制下图以对分包行为进行描述。组包过程作为其逆过程,请读者自行推断。
在发送路径上,上不同的上层协议层传输给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传递给上层协议层。
蓝牙数据传输主要涉及两种链路:SCO(Synchronous)和ACL(Asynchronous Connectionless),前者常用于同步话音传输,后者主要用于分组数据传送,为本系列所关注。
对于ACL链路,HCI-ACL数据包的格式如下图所示。
其中,PB Flag是Packet Boundary Flag的缩写,该字段占用两个bit,当其数值为00或10时,表征当前HCI包是上层L2CAP PDU分解所得碎片中的首包;当其数值为01时,表征当前HCI包是上层L2CAP PDU分解所得碎片中的续包;当其值为11时,表征其封装了一个完整的上层L2CAP PDU。
对于ACL链路,L2CAP PDU的格式如下图所示。
其中,Length描述的是当前L2CAP PDU的数据部分的总长度,而SDU Length描述的是当前L2CAP所属SDU的长度。值得注意的是,L2CAP SDU字段只存在于部分协议的首个L2CAP数据包。
三. 漏洞细节
3.1 漏洞概述
CVE-2020-0022漏洞的本质在于,接收端在处理HCI ACL数据包重组的过程中,存在代码bug,使得相应的memcpy阶段能够向相应缓冲区写入超过预期长度的数据。
这种非法的写入,能够初步地使得执行收包的进程即蓝牙守护进程发生崩溃,在精心构造之下,能够导致内存内容泄露乃至RCE。
本文仅关注通过漏洞引发进程崩溃的情形,其他基于漏洞的进一步攻击方法将在本系列的后续文章中给予阐述。
3.2 代码分析
相关代码位于system/bt/hci/src/packet_fragmenter.cc,核心流程即HCI-ACL数据包的重组,对应函数为reassemble_and_dispatch。
蓝牙守护进程对与新接收的HCI-ACL数据包,作初步解析如下图所示。
这里,HCI-ACL数据包在其已有处理过程中被封装为BT_HDR结构,其中,通过packet->data可以得到HCI数据包的起点。
根据前面对HCI ACL数据包结构的分析,有:
① Line 128读取了HCI-ACL数据包的数据长度,即当前HCI-ACL数据包所包含的部分L2CAP PDU的长度,并存入acl_length;
② Line 129读取了L2CAP PDU的前两个字节所含的数字,并存入l2cap_length。若当前HCI-ACL数据包为首个碎片,则l2cap_length即相应L2CAP PDU的数据长度,;
③ Line 127和Line 133读取了HCI-ACL头的handle部分,并提取了当前HCI包是否为对应L2CAP PDU的首个碎片的信息。
若当前HCI-ACL数据包是相应L2CAP PDU的首个碎片,其相关处理流程如笔者所绘制的下图所示。
下面结合代码进行分析。
继数据包的初步解析后,若当前HCI包为首个碎片(Line 136),则l2cap_length为L2CAP PDU的数据长度,在其基础上添加L2CAP头的长度和HCI-ACL头的长度,即得到封装了整个L2CAP PDU的HCI-ACL数据包的长度,记作full_length。
在确定full_length的合法性(Line 161 ~ Line 178)后,在内存中分配相应BT_HDR结构partial(Line 180),该结构除BT_HDR头部外,其余部分大小为full_length字节,用于存放各HCI-ACL数据包的重组结果(含HCI-ACL头)。
在为partial分配空间后,
① 将partial_packet->len设定为full_length,以记录完整重组结果的大小(含HCI-ACL头)(Line 183);
② 将partial_packet->offset标记为packet->len,以记录此次整合之后,重组结果中所含的有效数据量(含HCI-ACL头)(Line 184);
③ 通过内存复制,将当前HCI-ACL碎片的内容整合到partial中(Line 186)。
若当前HCI-ACL数据包是相应L2CAP PDU的后续碎片,其相关处理流程如笔者所绘制的下图所示。
继数据包的初步解析后,若当前HCI包为后续碎片(Line 197),则计算projected_offset为partial_packet->offset + (packet->len – HCI_ACL_PREAMBLE_SIZE)。由前述内容,partial_packet->offset为当前重组结果的有效数据量;于是,projected_offset的语义为:以完整地整合当前HCI-ACL数据包为前提,所得的重组结果中的有效数据量(包括HCI-ACL头)。
若projected_offset大于根据首个HCI-ACL碎片内容计算所得的完整重组长度(含HCI-ACL头),则对当前HCL-ACL碎片的内容进行截断(Line 211 ~ Line 219),确保当前HCL-ACL长度合法。
通过内存复制,将当前HCL-ACL碎片的内容整合到重组结果之中。(Line 211 ~ Line 212),再完成相关清理工作(Line 225 ~ Line 232)。
3.3 漏洞原理与初步利用
3.2.3节对后续HCL-ACL碎片的处理中,Line 211 ~ Line 219的目的在于:要对过大的当前HCL-ACL碎片进行截断,以便按照首个HCL-ACL碎片所指定的L2CAP PDU大小完成重组。然而,Line 217的代码是存在bug的。
当不存在截断时,packet->len的长度是包含HCI-ACL头的,Line 221中减去packet->offset(被赋值为HCI_ACL_PREAMBLE_SIZE,即4字节)得到的是对应的部分L2CAP PDU的长度,从而,复制的内容为当前HCI-ACL碎片中的部分L2CAP PDU。
然而,发生截断时,packet->len被更新为:
partial_packet->len – partial_packet->offset
= 重组结果的预期完整长度(含HCI-ACL头)- 重组结果已有有效数据的长度(含HCI-ACL头)
= 重组结果中还能容纳的有效数据长度
= 后续HCI-ACL碎片中所包含的一部分L2CAP PDU的最大长度
注意,这使packet->len的语义发生了变化,它不再包含HCI-ACL头的长度,而是应当进行整合的一部分L2CAP PDU的长度,于是,Line 221使用packet->len减去packet->offset成为了多余的操作,使得应当拷贝的数据量错误地减少了一个HCI-ACL头的长度。
经验表明,内存复制量的减少会导致传输内容的丢失,而内存复制量的增加才是导致overflow和一系列攻击的关键。
有趣的是,memcpy的第三个参数本质上是一个unsigned的类型,即,无论传递的具体数值是多少,该参数都会被memcpy当作强制转换成无符号数处理。
于是,只需构造使得Line 221处的packet->len小于packet->offset,即小于4。例如,令其为2,则memcpy的第三个参数为-2,强制转化成无符号数为0xfffffffffffffffe,使得产生overflow。
四. 漏洞复现
4.1 攻击思路
L2CAP有三种格式:非链接格式(Connectionless)、面向链接格式(Connection-oriented)和讯号命令格式(Signaling Command)。其中,单个讯号命令格式的L2CAP数据包中允许携带1个或多个Command。Command有多种形式,其一为echo请求。echo请求类似于我们常用的ping指令,当向某个可达的蓝牙设备并发送echo时,默认地,该设备将返回一个echo回执,其数据内容与echo请求完全相同。
echo请求采用ACL链路层,有触发漏洞的潜力,我们对其进行构造以达成攻击。
① 第一步,我们考察一个完整的l2cap echo请求包,笔者将其格式绘制如下。
其中,L2CAP头中的Length即后续Command头+Command体的长度;L2CAP头中的CID与L2CAP格式有关,对于讯号命令格式,其值固定为0x02。
Command头中的Code表征Command的类型,对于echo请求,其值固定为0x08;而对于echo回执,其值固定为0x09;Command头中的Ident作为请求的标识号,一般地,是一个逐数据包递增的值。
Data则含有Command请求体的内容,允许任何设定。
② 第二步,我们将上述请求使用HCI-ACL数据包进行封装,得到两个请求,笔者将其绘制如下。
注意,虽然L2CAP数据包本身进行了拆分,但第一个HCI-ACL碎片所含的L2CAP头中,Length仍是整个Command的长度,即Command头长度 + Command请求体第1部分长度 + Command请求体第2部分长度。
③ 第三步,通过构造,使得第一个HCI-ACL碎片中,L2CAP头中的Length为Command请求体第1部分长度 + 2。这样一来,正确情况下,第二个HCL-ACL碎片中Command请求体第2部分的长度应为2。然而,我们故意将第二个HCL-ACL碎片的长度(通过HCL-ACL头中的Length表征)设定为大于2,从而触发截断,使得前述memcpy的第三个参数变为unsigned(2 – 4) = 0xfffffffffffffffe。
4.2 代码片段
为方便读者进一步理解4.1中的攻击思路,笔者书写了如下代码片段,这些代码实测能够达到预期攻击目的,详见下一节。
① 第一个HCI-ACL碎片的构造与发送
其中,结合上节的图解不难理解其行为。
② 第二个HCL-ACL碎片的构造与发送
其中,结合上节的图解不难理解其行为。
③ 程序自身的正确性验证
其中,我们发送一个Command体由8个A字符和8个B字符组成的正常echo数据包,若程序思路无误,则预期返回一个内容相同的echo回执包。
④ 发动攻击
其中,我们在第一个HCL-ACL分片的L2CAP头中,将Command体第2部分的大小设定为2,却按照实际大小8进行发送,从而触发截断,达成攻击。
4.3 效果演示
① 正常发送echo数据包时,使用程序对返回的l2cap数据包内容进行打印,效果如下图所示。
其中,打码部分是笔者手机(Android 8.0)的蓝牙地址。对于程序输出,09为Command中的Code,表征这是一个echo回执包;8个41即8个A字符;8个42即8个B字符。
由此,我们的发包成功触发了手机蓝牙的echo回执,程序自身的正确性得以证明。
② 发送攻击包时,观察手机状态,有,
由此,我们的发包导致了蓝牙守护进程进行预期之外的memcpy,使得进程崩溃。攻击成功。
五. 总结
至此,本系列的第一篇《CVE-2020-0022 蓝牙漏洞初探(上)一个bug引发的血案》结束了。本篇从蓝牙简介开始,逐步深入,阐述了L2CAP层与HCL-ACL层之间数据包的分段与重组,分析了相关代码,阐述了漏洞细节,并以实例加以Demo,成功触发了漏洞。
然而,至此我们仅仅达到了令蓝牙守护进程崩溃的效果,而且,0xfffffffffffffffe作为memcpy的第三个参数,似乎意味着一个十分缓慢的操作,难以在用户无察觉的前提下加以利用,当然,这并非事实,细节暂且不表。下一节中,笔者会为大家带来利用CVE-2020-0022漏洞泄露被攻击目标内存内容的原理与实例,以进一步揭开其神秘的面纱。
参考文献
https://insinuator.net/2020/04/cve-2020-0022-an-android-8-0-9-0-bluetooth-zero-click-rce-bluefrag/