作者:启明星辰ADLab
原文链接:https://mp.weixin.qq.com/s/8qIleHZpkJ1a5kMQDG5c8A

一、漏洞概述

2020年10月,谷歌安全研究人员披露了三个Linux内核蓝牙协议栈漏洞,可导致远程代码执行,被称为BleedingTooth。这三个漏洞中,一个是堆溢出,编号为CVE-2020-24490;另一个是类型混淆,编号为CVE-2020-12351,最后一个是信息泄露,编号为CVE-2020-12352。近日,谷歌安全研究人员又披露了BleedingTooth中CVE-2020-12351和CVE-2020-12352组合的漏洞利用及细节,并在蓝牙4.0下,实现了零点击远程代码执行。

二、漏洞分析

(一)CVE-2020-12351

该漏洞出现在net/bluetooth/l2cap_core.c中。l2cap_recv_frame()是解析和处理l2cap协议数据包的函数。代码实现如下所示:

获取通道cid和l2cap数据包长度len。代码实现如下所示:

根据不同的通道cid,进入不同的子过程进行处理,进入l2cap_data_channel()函数。代码实现如下所示:

首先,通过cid找到通道chan;如果没有找到,判断cid是否为L2CAP_CID_A2MP;如果是,调用a2mp_channel_create()创建一个新的通道chan。a2mp_channel_create()函数实现如下所示:

调用amp_mgr_create()创建mgr,在amp_mgr_create()函数中,代码实现如下所示:

调用a2mp_chan_open()创建通道chan,该函数将初始化一部分数据,代码实现如下所示:

如将chan->mode初始化为L2CAP_MODE_ERTM。chan->data赋值为mgr,类型为struct amp_mgr。成功创建a2mp通道返回到l2cap_data_channel()中,代码实现如下所示:

根据chan->mode的不同,进入不同的data处理子过程,当mode为L2CAP_MODE_ERTM和L2CAP_MODE_STREAMING时,进入l2cap_data_rcv()函数中,代码实现如下所示:

该if条件中,会调用sk_filter()函数,此时chan->data为参数。而sk_filter()函数定义如下所示:

第一个参数类型为struct sock,而chan->data类型为struct amp_mgr,发生类型混淆。

(二)CVE-2020-12352

该漏洞是出现在a2mp协议中,漏洞代码位于net/bluetooth/a2mp.c,多个函数使用未初始化的结构体,将数据返回到用户层,导致信息泄露,可泄露内核栈上的内存数据。漏洞原理较为简单,以a2mp_getinfo_req()函数为例,该函数是响应getinfo请求时调用的,代码实现如下所示:

行304,通过req->id获取hdev,如果不存在hdev或hdev->type不是HCI_AMP,进入if语句中,定义struct a2mp_info_rsp类型的 rsp,该结构体定义如下所示:

其只使用了rsp.id和rsp.status,其他的数据域未使用也未初始化,可以泄露16字节数据,然后调用a2mp_send()函数将响应包发送到用户层,泄露内存数据。

(三)CVE-2020-24490

该漏洞只能在bluetooth 5.0下触发,在bluetooth 5.0之前,HCI进行广播的最大数据长度为0x1F,0x20-0xFF保留。如下所示:

在bluetooth 5.0中,该length最大扩展到229字节。如下所示:

该漏洞代码位于net/bluetooth/hci_event.c中,在处理HCI_LE_Extended_Advertising_Report事件中,未判断广播数据长度最大值,后续拷贝广播Data导致溢出。调用过程如下所示:

process_adv_report()函数处理广播数据,将广播数据拷贝到发现的设备中,代码实现如下所示:

调用store_pending_adv_report()函数,该函数实现广播数据拷贝,代码实现如下所示:

其中,discovery_state结构体定义如下所示:

last_adv_data数据大小为HCI_MAX_AD_LENGTH,共31字节,当执行memcpy时发生溢出。

三、利用分析与复现

(一)控制代码执行流程

前文分析到CVE-2020-12351类型混淆是在sk_filter()函数中发生的,sk_filter()函数调用sk_filter_trim_cap()函数,该函数代码实现如下:

该函数第一个参数为sk,参数类型为sock结构体,这部分代码中对sk和skb的检查容易绕过。接下来关键代码如下所示:

行113,对sk->sk_filter进行解引用,如果成功获取filter指针,进入行115。行119,调用 bpf_prog_run_save_cb()函数,参数分别为filter->prog和skb,该函数代码实现如下所示:

然后,行676,调用__bpf_prog_run_save_cb()函数,该函数实现代码如下:

接着,行662,调用BPF_PROG_RUN(prog,skb),该函数定义为一个宏,实现代码如下所示:

一路调用下来,最终会调用到红框中的代码,简化一下调用过程为: sk->sk_filter->prog->bpf_func(skb, sk->sk_filter->prog->insnsi)。因此,只要控制sk->sk_filter就可以控制执行流程。

(二)堆喷占位

函数sk_filter()的第一个参数类型为struct sock,而实际传入的参数类型为struct amp_mgr,可以采用堆喷128大小的内存块进行占位,伪造amp_mgr 对象。这里有个问题,sk->sk_filter在sock中的偏移为0x110,而amp_mgr结构体大小为0x70,偏移已经超出了范围。要解决这个问题,这里可以采用如下巧妙的堆喷布局:

结构体amp_mgr在kmalloc-128类型的slub中被分配,从第三个块开始,amp_mgr结构体偏移0x10处,可以被伪造成sk_filter,便可以满足sk对sk_filter域的解引用,并且可控。

(三)布局载荷

通过堆喷占位控制代码执行流程后,接下来就是布局攻击载荷。可以采用堆喷1024大小的内存块去伪造l2cap_chan对象,因为结构体大小为792,正好落在kmalloc-1024 slub块中,而且a2mp通道也属于l2cap通道中,释放a2mp通道时,l2cap通道也将被释放,操控起来较为灵活,最终布局如下所示:

(四)泄露l2cap_chan对象地址

通过堆喷布局和创建释放l2cap_chan通道等一系列操作后,可能存在一个指向kmalloc-1024内存块地址的l2cap_chan对象,可以通过CVE-2020-12352漏洞泄露一个内核栈上面的内核地址,如下图中红框所示:

通过该内地地址减去一个0x110偏移便可以找到一个l2cap_chan对象地址,可以通过amp_mgr结构体内存地址检查一下是否正确,因为amp_mgr结构体偏移0x18处为l2cap_chan指针,如下图中红框所示:

成功泄露l2cap_chan对象地址后,然后去填充amp_mgr结构体偏移0x10处的数据域。

(五)复现测试

我们在ubuntu 5.4.0-26-generic系统下复现测试漏洞利用,执行过程如下:

成功反弹root级shell,如下所示:

四、参考链接

1、 https://google.github.io/security-research/pocs/linux/bleedingtooth/writeup
2、 https://github.com/google/security-research/security/advisories/GHSA-ccx2-w2r4-x649
3、 https://github.com/google/security-research/security/advisories/GHSA-7mh3-gq28-gfrq
4、 https://github.com/google/security-research/security/advisories/GHSA-h637-c88j-47wq


Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1564/