首页
社区
课程
招聘
[原创]CVE-2020-12351:Linux蓝牙模块拒绝服务漏洞分析
发表于: 2021-1-30 15:50 16708

[原创]CVE-2020-12351:Linux蓝牙模块拒绝服务漏洞分析

2021-1-30 15:50
16708

CVE-2020-12351是谷歌安全研究人员在Linux内核中发现的蓝牙安全漏洞。该漏洞位于net/bluetooth/l2cap_core.c,是一个基于堆的类型混淆漏洞。攻击者在蓝牙范围内向目标设备发送恶意L2CAP载荷即可触发该漏洞,引发目标设备蓝牙服务DoS或使攻击者获得目标设备kernel权限的任意代码执行。该漏洞利用过程无需受害者参与交互,因此被称为“零点击攻击”。

Bluetooth.ko在处理L2CAP数据包时,将一个错误的数据类型作为参数传递给了内核函数,导致内核函数解析指针发生了错误,引发了空指针引用或不可修复的缺页异常。

Linux kernel 4.8以上的版本受影响,我在测试中发现从Linux kenrel 5.4.0-53开始该漏洞就已经不起作用了。Intel建议升级到Linux kernel 5.9版本。

图2-1 

攻击者和目标机都运行在Ubuntu20.04 Linux kernel 5.4.0-42上,攻击者通过一些手段获知目标机的蓝牙地址BD(方法较为简单),在本地以root权限运行poc即可使目标机蓝牙服务崩溃。

首先,目标机需要开启蓝牙并且使得自身可以被发现和连接:

图2-2 目标机开启蓝牙

接下来在攻击机中输入命令“sudo ./poc 18:26:49:CD:09:E1”执行验证程序:

图2-3 发送恶意数据包


可以看到目标机已经崩溃转储重启:

图2-4

目标机重启后观察/var/crash目录下的内存转储文件观察到了问题出在内核蓝牙服务:

图2-5 目标机蓝牙服务崩溃

观察内核日志发现当内核运行到“sk_filter_trim_cap + 0x6d”发生了空指针引用,对应于filter.h的第657行。

sk_filter_trim_cap函数位于内核文件vmlinux中,现场对应的测试指令访问[r15+2]处的内容。r15=0因此实际访问地址为0x2导致空指针引用。

                                      

图3-1 连接示意图

攻击者向目标机发送恶意数据包导致目标机的蓝牙服务崩溃,调试机通过RS-232串口线连接到目标机使用KGDB对内核进行调试。

双机调试需要目标机支持KGDB调试,可以在内核编译时设置menuconfig打开支持选项。当前Ubuntu发行版内核已经默认开启了KGDB支持,通过命令“cat /boot/config-uname -r | grep -i GDB”查看可知当前内核支持KGDB以及串口调试。

图3-2 查看KGDB开启情况

双机调试需要尽可能保持目标机和调试机运行的内核版本相同,这里目标机和调试机都是运行Linux kernel 5.4.0-42.46。为了调试的时候能够在kgdb查看符号信息,需要在调试机中安装同版本带调试信息和符号的kernel,我们可以到launchpad.net站点下载符号文件。

图3-3 符号文件和源码文件下载

文件拷贝到调试机中执行“dpkg -i”命令安装符号文件,安装完成后“usr/lib/debug/boot/vmlinux-5.4.0-42-generic”和“/usr/lib/debug/lib/modules/5.4.0-42-generic/kernel/net/bluetooth/bluetooth.ko”就是所需的符号文件。

图3-4 符号文件安装目录

目标机内核已经默认支持KGDB调试,但是默认不工作需要我们通过编辑grub文件将KGDB调试附加到启动目录中,这样就可以在开机的时候进入调试。以root权限编辑“/etc/grub.d/40-custom”文件,添加如下启动条目:

图3-5 编辑grub文件

注意到内核启动命令部分着色的内容,kgdbwait表示目标机开机后等待调试机的KGDB连接,kgdboc是KGDB over console的缩写表示通过控制台交互,ttyS0表示通过默认的第一个串口进行交互,115200为串口波特率,nokaslr表示禁用内核地址随机化便于下断点调试。

至此目标机的配置已经完成,开机后可以看到grub增加了调试启动选项,进入调试后看到如下等待连接的界面:

图3-6

在调试机中使用PL2303 USB转串口扩展计算机的串口,用RS-232电缆与目标机COM1端口相连,这时调试机的/dev目录下会多出ttyUSB0文件,该文件就是串口通信文件。由于默认情况下需要root权限才能使用串口,所以执行命令“sudo usermod -a -G dialout username” 将username用户添加到tty文件所属的dialout组,重启后可以使用普通用户权限操作端口。

使用stty命令配置调试机的串口波特率为115200与目标机一致。

图3-7 配置调试机串口

接下来编写gdb配置文件config,该文件的目的是自动加载符号文件、设置目标机架构、连接调试机,可以避免复杂的手工输入。

图3-8 gdb配置文件

为使gdb能够使用list命令显示源代码,gdb从/build/linux-kcrkKx目录下读取源代码,因此在/目录下依次建立build和linux-kcrkKx目录,将源码目录复制到linux-kcrkKx目录下并命名为linux-5.4.0。

图3-9 gdb源代码目录

至此调试机也配置完毕,加载gdb配置文件可以看到gdb自动断下,使用list命令可以看到断点处对应的内核源码。

图3-10

关闭内核地址随机化KASLR。KASLR的存在使得内核加载的基地址每次重启后都有所不同会干扰调试,因此一定要在grub文件中的启动命令行加上nokaslr。

设置gdb配置文件的目标架构。若没有指定目标架构,gdb会默认目标机是32位,这会造成后续很多麻烦,因此在配置文件中设置set architecture i386:x86-64。

拷贝内核源码文件。可能不同gdb的默认搜索源码目录是不同的,要根据调试过程中的具体报错信息创建源码目录。

在目标机中巧用Sysrq下断点。在gdb中输入continue命令后内核开始运行,再次输入Ctrl+c无法使目标机内核停下来,需要我们在目标机中向/proc/sysrq-trigger文件中写入字符“g”使内核停止等待kgdb命令。关于sysrq-trigger文件用法请参考Linux 4.x源码Documentation目录下的sysrq.txt有全面说明。

蓝牙设备由蓝牙主机、蓝牙模块和传输层组成。以具备蓝牙功能的计算机为例,蓝牙模块是主板上集成的蓝牙通信部分电路,包括蓝牙主控芯片、基带芯片、射频电路,这些现在都可以做在一块芯片里。蓝牙主机负责使用蓝牙模块实现特定的功能,例如文件传输、语音通话,这一部分既包括硬件也包括软件是最复杂的部分。传输层连接蓝牙主机和蓝牙模块,它将主机的请求发送到模块去执行并将模块接收到的数据传递给主机,这部分包括电路连接和相应的驱动,常见的有USB总线、UART、RS232及其驱动。

图4-1 蓝牙通信架构

对于语音传输这种实时性要求较高的应用,使用同步数据交换SCO发送到蓝牙模块进行传输。对于文件传输这类实时性要求不高的应用,则使用异步数据交换ACL进行传输。

进行异步数据交换时,为了能让多个上层协议使用一个基带信道,需要经过“链路适配与控制协议”层处理,简称L2CAP层。例如蓝牙耳机中既有用于播放音频的ACL数据,也有用于控制播放歌曲的ACL数据,但它们都共用一个物理信道,这时要用L2CAP定义的数据格式加以区分。

应用的原始数据需要经过L2CAP层进行分段、加上数据头打包成L2CAP格式的分组传递给HCI层,HCI层将数据打包成主机控制器能够理解的格式发送给蓝牙模块去执行;接收时蓝牙模块将接收到的数据以HCI分组的格式发送给主机,主机解析出其中的数据再传递给L2CAP层,L2CAP层接收到足够的数据包就会将其重新组合成应用数据,最后传递给上层的协议。

图4-2 分段与重组 

第2节提到漏洞位于L2CAP数据解析的位置,HCI ACL数据包承载了L2CAP数据分组。L2CAP数据分组承载了上层应用协议的数据,为使用蓝牙模块发送数据,L2CAP数据分组还需要加上HCI数据头成为L2CAP数据包通过传输层发送给蓝牙模块进行调制发送,接收过程与之相反。

图4-3 HCI ACL数据包格式

HCI数据包有命令包、事件包、ACL数据包、SCO数据包四种格式,第一个字节0x2表示为HCI ACL数据包;handle用于标识一个和远程蓝牙主机的连接,这个在多个设备连接时有用到;PB表示所搭载的L2CAP分组所在的位置,0x2表示第一个L2CAP分组,0x1表示中间分组;dlen表示HCI载荷长度,单位为字节。

L2CAP层目的有三:信道复用;分段与重组;差错控制和重传。信道复用通过L2CAP头的CID字段进行标记,不同的协议使用不同的CID来标记自己的数据包。其中命令信道用于建立本地和远程之间的服务通信;广播信道用于向一组设备广播数据,属于无连接信道;保留信道用于测试;动态分配信道用于分配给每一个上层应用协议使用。

表4-1 CID分配

Length字段标记了L2CAP载荷和FCS校验码的总长度,最大为65535字节也就是64KB。

公开PoC代码见附录2。

第10~52行代码用于生成L2CAP数据包的FCS校验码,与内核校验的逻辑完全相同所以直接从内核源码中截取,位置为linux-5.4/linux-5.4/lib/crc16.c 。

进入main函数,首先调用hci_open_dev打开一个与本地蓝牙模块通信的HCI层原始套接字,通过该套接字可以随意构造HCI数据包。

第93~114行代码生成一个L2CAP层套接字并与远程主机连接。该套接字的目的是生成一个远程连接句柄handle,后面把该handle提取出来组装到HCI原始套接字的数据包上。需要注意的是在蓝牙编程中,作为主动连接的一方也需要将套接字与本地地址绑定,这一点和基于TCP/IP的网络编程有点不同。

第116~123行调用getsockopt函数将L2CAP连接的handle提取出来。

第126~134行构造HCI ACL包的内层L2CAP包,其中L2CAP包头的CID字段设置为AMP_MGR_CID(0x3),计算L2CAP包除去FCS字段的crc校验码并添加到FCS字段。构造出来的L2CAP包如下:

图5-1 L2CAP数据结构

第149行调用自定义hci_send_acl_data函数将L2CAP包打包成HCI ACL数据包,最后通过HCI原始套接字发送。

hci_send_acl_data函数接收4个参数,hci_cocket是本地HCI原始套接字,hci_handle是已经建立的L2CAP连接的句柄,data是要发送的L2CAP数据包,data_length是L2CAP数据包的长度。

hci_handle和PC、BC标志组成HCI包头的handle字段,data_length初始化HCI包头的dlen字段。使用writev函数将HCI包头、L2CAP包打包后发送至HCI原始套接字,所以生成HCI ACL数据包是在writev完成的,其中打包的第一个字节为HCI_ACLDATA_PKT(0x2)表示这是一个ACL类型的HCI包。

构造好的HCI数据包通过HCI原始套接字发送到本地蓝牙模块,由模块中的固件识别、处理,交给基带发送到目标机。

图5-2 HCI ACL数据包


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2021-1-31 12:52 被极目楚天舒编辑 ,原因:
收藏
免费 6
支持
分享
最新回复 (8)
雪    币: 1475
活跃值: (14652)
能力值: ( LV12,RANK:380 )
在线值:
发帖
回帖
粉丝
2
好家伙,这就是掌握了网络真谛的大佬吗, 看到这个空白标题我就忍不住点进来
2021-1-30 15:55
0
雪    币: 6313
活跃值: (3212)
能力值: ( LV12,RANK:330 )
在线值:
发帖
回帖
粉丝
3
SSH山水画 好家伙,这就是掌握了网络真谛的大佬吗, 看到这个空白标题我就忍不住点进来[em_87]
稍等
2021-1-30 16:13
0
雪    币: 5235
活跃值: (3260)
能力值: ( LV10,RANK:175 )
在线值:
发帖
回帖
粉丝
4
前排膜拜
2021-1-31 08:08
0
雪    币: 261
活跃值: (83)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
5
ubuntu 20.04 64 bit LTS  5.8.0-41-generic 路过。
不过,这文章写得很解渴,我竟然看完了。
这对安卓手机有没有影响?
2021-1-31 21:20
0
雪    币: 2510
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
坛里有你更精 ,感谢分享
2021-2-1 01:53
0
雪    币: 14517
活跃值: (17538)
能力值: ( LV12,RANK:290 )
在线值:
发帖
回帖
粉丝
7
感谢分享
2021-2-1 08:46
0
雪    币: 11
活跃值: (106)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
非常好,从这种方式也可以学习内核。
2021-2-4 10:48
0
雪    币: 10984
活跃值: (7768)
能力值: ( LV12,RANK:370 )
在线值:
发帖
回帖
粉丝
9

请问师傅有没有用虚拟机复现过这个漏洞?我刚接触蓝牙不久,可能会有一些白痴问题。

我做了三种尝试,都没有成功:


第一种尝试:我用两个虚拟机(+两个适配器)按照师傅的文章进行复现的时候,攻击机器执行如下:

$ sudo ./poc 84:E0:F4:02:7D:EA
[*] Resetting hci0 device...
[*] Opening hci device...
[*] Connecting to victim...
connect: Operation now in progress

然后我查看攻击机器的日志,有这三个报错:

[ 2261.234358] debugfs: File 'le_min_key_size' in directory 'hci0' already present!
[ 2261.234487] debugfs: File 'le_max_key_size' in directory 'hci0' already present!
[ 2261.234493] debugfs: File 'force_bredr_smp' in directory 'hci0' already present!

我在网上搜资料,显示是内核bug?204765 – debugfs: File 'le_min_key_size' in directory 'hci0' already present! (kernel.org)


同时,我在执行sudo ./poc xxxxxx 的时候,在攻击机器和目标机器都进行了抓包,攻击机器上只抓到host和controller之间交互的hci的包,目标机器没有抓到包。



第二种尝试:用其中一个虚拟机当作攻击机器执行poc,连接一个物理机(Ubuntu16/5.4.0-42),攻击机器(虚拟机)dmesg仍然报上面的错,但是执行效果不一样了:

$ sudo ./poc F4:06:69:1C:CA:36
[*] Resetting hci0 device...
[*] Opening hci device...
[*] Connecting to victim...
[+] HCI handle: 4b
[*] Sending malicious L2CAP packet...

同时攻击机器抓的包里有l2cap连接了,但目标机器(物理机)仍然是没有反应(没有崩溃转储重启)。

> P.S. 我想问一下,这个漏洞触发成功的结果就是目标机崩溃转储重启吗?还有别的形式会显示攻击成功吗?


第三种尝试:反过来,物理机当作攻击机器,虚拟机当作目标机器,执行poc的效果如下,dmesg没有报错,同时抓包情况和第一种尝试一样,作为攻击机器的物理机抓的包都是host和controller之间交互的hci的包,作为目标机器的虚拟机没有抓到包。

$ sudo ./poc xxxxxx
[*] Resetting hci0 device...
[*] Opening hci device...
[*] Connecting to victim...
connect: Connection timed out

2023-4-4 00:13
0
游客
登录 | 注册 方可回帖
返回
//