深度剖析近期在Qualcomm DSP中曝出的安全漏洞

匿名者  1297天前

简介

Snapdragon是由美国高通公司设计和销售的一系列用于移动终端的片上系统(SoC)半导体产品。通常情况下,单个SoC系统内会含有多个CPU内核、Adreno图形处理单元(GPU)、Snapdragon无线调制解调器、Hexagon数字信号处理器(DSP)、Qualcomm Spectra图像信号处理器(ISP)以及其他硬件。

Snapdragon的产品层级一般是根据CPU、GPU和DSP处理器的可扩展计算资源进行区分的。其中,最低级别的产品可能只包含一个Hexagon DSP,而高级别的产品则包含多达四个具有特定用途的Hexagon DSP处理器。例如,在Pixel 4、Samsung S10、XiaomiMi 9、LG G8和OnePlus 7等手机中使用的Snapdragon 855(SM8150)SoC,通常包含一个Kryo CPU、一个Adreno 640和四个独立的DSP,每个DSP具有特定的用途,分别用于传感器(sDSP)、调制解调器(mDSP)、音频(aDSP)和计算(cDSP)。在这篇文章中,我们研究了其中的两种DSP:

  • cDSP,用于计算密集型任务,如图像处理、计算机视觉、神经网络相关计算和摄像机流数据的处理。
  • aDSP,用于音频和语音数据的低功耗处理。

就当前的研究而言,我们将cDSP和aDSP视为一个处理单元(DSP)。另外,这里发现的安全漏洞对两者都适用。

CPU与DSP之间的通信

FastRPC是Qualcomm专有的远程过程调用(RPC)机制,用于支持CPU和DSP之间的远程过程调用。需要说明的是,FastRPC框架使用的是一种典型的代理模式。

1.jpg

图1:FastRPC示意图

在图1中,您可以看到FastRPC组件之间的交互情况:

  1. 用户模式进程(客户端)启动远程调用。例如,一个Android应用程序在其本机代码中调用其中一个存根函数。
  2. 存根函数是自动生成的代码,用于将函数调用转换为RPC消息。通常情况下,存根代码将编译为单独的本机库,然后将其与客户端链接。存根代码使用libadsprpc.so和libcdsprpc.so库通过相关的ioctl来调用应用程序处理器(AP)上的DSP RPC驱动程序(/dev/adsprpc-smd或/dev/cdsprpc-smd)。
  3. DSP RPC内核驱动程序接收远程消息调用,通过共享内存驱动程序(SMD)通道,将队列中的消息发送到DSP上的DSP RPC框架,然后等待响应。
  4. DSP RPC框架从队列中删除消息,然后将其分派给骨架动态库(skeleton dynamic library)进行处理。
  5. skel是一个自动生成的库,用于对参数进行解组(unmarshal)并调用目标方法的实现。
  6. 目标方法(对象)是由Qualcomm或OEM提供的、在DSP上运行的逻辑。

谁可以在DSP上运行自己的代码?

出于安全考虑,DSP被授权给OEM和数量有限的第三方软件供应商进行编程。在DSP上运行的代码是由Qualcomm公司签名的。普通的安卓应用没有权限在DSP上执行自己的代码。不过,Snapdragon 855和865 SoC是个例外,Qualcomm公司被允许在cDSP上执行低权限的无签名动态共享对象。

应该注意的是,谷歌通过SELinux政策防止第三方应用程序和adb shell访问DSP RPC驱动程序,以保护Pixel设备。

同时,公开提供的Hexagon SDK负责将DSP对象的C/C++源代码编译成适用于DSP上执行的Hexagon(QDSP6)字节码。而存根和skel代码是根据开发人员准备的接口定义语言(IDL)模块自动生成的。Qualcomm IDL用于定义跨内存保护和处理器边界的接口。IDL只公开了该对象的功能,但没有公开它所在的位置或实现它的编程语言。

Android应用程序开发人员能够为DSP实现自定义库,但不能完全执行。Android应用程序只能调用预构建的DSP库。

谁在掌管着DSP?

QuRT是Qualcomm专有的多线程实时操作系统(RTOS),用来管理Hexagon DSP。QuRT的完整性得到了Qualcomm安全可执行环境(QSEE)的信任。QuRT的可执行二进制代码(独立于aDSP和cDSP)具有相应的签名,并分割成多个文件,这一点与Qualcomm设备上任何其他受信任的应用程序完全相同;其默认位置是/vendor/firmware目录。

对于每个启动远程调用的Android进程,QuRT系统都会在DSP上创建一个单独的进程。当用户进程被生成时,系统会将相应的shell进程(/vendor/dsp/fastrpc_shell_0用于aDSP,/vendor/dsp/fastrpc_shell_3用于cDSP)加载到DSP上。shell负责调用骨架库和对象库。此外,它还实现了DSP的RPC框架,用于为骨架库和对象库提供必要的API。

DSP软件架构提供了不同的保护域(PD)以确保内核软件的稳定性。在DSP中,共有三个保护域:

  • 内核域:拥有对所有PD的所有内存的访问权。
  • Guest OS域:可以访问自己PD的内存、用户PD的内存,以及某些系统寄存器。
  • 用户域:只能访问自己的PD的内存。

无签名的动态共享对象只能在无签名PD内运行;实际上,无签名PD属于用户PD,其对底层DSP驱动程序和线程优先级的访问是受到限制的。无签名PD被设计为只支持一般的计算应用。

对象库以及FastRPC shell都可以在用户PD中运行。

跳过FastRPC流程中的存根代码。

libadsprpc.so和libcdsprpc.so库负责与DSP RPC驱动程序进行通信。这些库导出了两个函数,它们对本研究来说是很有意义的:

  • int remote_handle_open(const char* name, remote_handle *ph)函数。这个函数用于在AP上的调用者进程和DSP上一个新的FastRPC shell进程之间打开一个远程会话。这个会话用于与作为第一个参数传递的骨架库进行通信。
  • int remote_handle_invoke(remote_handle h, uint32_t scalars, remote_arg*pra)函数。这个函数能够调用骨架库的导出方法。需要注意的是,必须将会话处理程序作为第一个参数进行传递。

使用这两个函数,客户端就可以执行骨架库中实现的所有DSP方法了。此外,由Qualcomm或OEM提供的存根代码可以从该链中跳过。

1.jpg

图2:直接调用DSP

让我们来看看remote_handle_invoke函数的第二个和第三个参数,它们用于对目标方法及其参数进行编码。

参数scalars是一个字,包含以下元数据信息:

  • 方法索引和属性(最高字节,以0xFF000000为掩码)。
  • 输入参数的数量(以0x00FF0000为掩码)。
  • 输出参数的数量(以0x0000FF00为掩码)。
  • 输入和输出句柄的数量(以0x000000FF为掩码,其中有四位用于输入,另外四位用于输出)。在现代手机上,如果这个字节不等于0,则意味着DSP调用失败。

参数pra是一个指向目标方法的参数数组的指针(对应于remote_arg元素)。其中,这些参数的顺序如下:输入参数、输出参数、输入句柄、输出句柄。

2.png

如您所见,每个输入和输出参数都被转换为一个通用的remote_buf元素。

需要注意的是,如果我们准备的remote_arg数组元素比目标方法所需的多,那么多余的参数就会被骨架库忽略。

对于参数scalars和pra,它们将通过DSP RPC驱动程序和DSP RPC框架进行“原封不动的”传输,并用于每个骨架库提供的特殊invoke函数的第一个和第二个参数。例如,libfastcvadsp_skel.so库提供了一个名为fastcvadsp_skel_invoke的invoke函数,该函数负责按索引调用适当的skel方法。对于每个skel方法来说,它首先对收到的远程参数进行相应的检查,然后将remote_bufs解组为常规类型,并调用相应的对象方法。

正如你所看到的,要从一个skel库中调用一个方法,只需要知道它的索引,然后用remote_buf结构体封装其各个参数即可。我们不需要提供调用函数的名称、参数的类型和数量就能执行调用,这使得骨架库成为非常不错的模糊测试目标。

降级漏洞

Qualcomm在安卓手机上预装了大量的骨架库,并且绝大部分都是专有的;然而,也有一些开源的库,如libdspCV_skel.so和libhexagon_nn_skel.so。

许多骨架库,如libfastcvadsp_skel.so和libscveBlobDesc riptor_skel.so,几乎在所有安卓设备上都能找到。然而,像libVC1DecDsp_skel.so和libsysmon_cdsp_skel.so这样的库,则只出现在现代Snapdragon SoC上。

实际上,还有些库是由OEM实现的,它们只能在特定供应商的设备上使用。例如,libedge_smooth_skel.so库可以在Samsung S7 Edge手机上找到,而libdepthmap_skel.so库则只能在OnePlus 6T手机上找到。

一般来说,所有的skel库都位于/dsp或/vendor/dsp或/vendor/lib/rfsa/adsp目录下。在默认情况下,remote_handle_open函数就是扫描的这些路径。此外,还有一个存放路径的环境变量,即ADSP_LIBRARY_PATH,我们可以将新的搜索路径加入其中。

如前所述,所有的DSP库都具有相应的签名,所以,我们无法给它们补丁。然而,任何Android应用程序都可以完成下列任务:在其资产中引入一个具有Qualcomm签名的骨架库,将其提取到应用程序的数据目录中,将该路径添加到ADSP_LIBRARY_PATH的开头,然后打开一个远程会话。这样的话,该库就能成功加载到DSP上,因为它的签名是正确的。

由于加载骨架库时不会进行版本检查,因此,在DSP上运行的可能是一个古董级的骨架库,并且其中很可能存在1-day漏洞。即使较新的骨架库已经存在于设备上,只要在ADSP_LIBRARY_PATH中,把旧版本的的路径放到新版本的路径之前,系统就仍然加载这个库的旧版本。通过这种方式,任何DSP补丁都可以被攻击者轻松绕过。此外,通过分析DSP软件补丁,攻击者就能找到库内已修复的漏洞,然后,只要加载未打补丁的版本,就能利用其中的安全漏洞了。

读者可能会问:对于特定的设备,是否存在该设备已经批准/拒绝的骨架库清单呢?答案是:没有!这就是说,我们可以在Sony Xperia手机上运行为Samsung手机实现的库。换句话说,在某个OEM库中发现的漏洞会危及所有基于Qualcomm芯片的安卓设备。

对Hexagon库继续基于反馈的模糊测试

由于DSP库使用了专有的Hexagon ELF格式,因此,检测Hexagon可执行文件时,最简单的方法就是使用QEMU。实际上,直到2019年底,QEMU才开始支持Hexagon指令集。同时,我们修复了其中的很多bug之后,现在终于能够在模拟器的用户模式下运行真正的DSP库了。

通过将QEMU和AFL结合起来,我们就能够在Ubuntu PC对相应的骨架库和DSP库进行模糊测试了。

为了在模拟器上执行一个库的代码,我们准备了一个简单的程序(Hexagon ELF二进制文件),它负责以下工作:

  1. 将收到的作为第一个命令行参数的数据文件解析为scalars字和remote_arg数组。
  2. 利用dlopen函数打开由第二个命令行参数指定的骨架库。该库可能依赖于其他骨架库和对象库。例如,libfastcvadsp_skel.so依赖于libapps_mem_heap.so、libdspCV_skel.so和libfastcvadsp.so lib。所有这些库不仅可以从固件中提取,也可以从真实设备中提取。
  3. 以scalars和指向remote_arg数组的指针作为函数参数,通过地址来调用invoke函数。例如,fastcvadsp_skel_invoke函数就是对libfastcvadsp_skel.so库进行模糊测试的起始点。

对于我们的程序来说,其输入文件的格式如下所示:

  1. scalars值(4字节)。在图3中,scalars等于0x08020200,这意味着通过两个输入和两个输出参数来调用8号方法。
  2. 输入参数的大小(每个参数占4字节):0x10和0x20。
  3. 输出参数的大小(每个参数占4字节):0x80200和0x1000。
  4. 输入参数的值。在这个例子中,第一个参数的值为0x11,占用0x10字节;第二个参数的值是0x22,占用0x20字节。

3.png

图3:输入数据文件,用于对DSP库进行模糊测试                                   

对于每个输出参数,我们都为其分配乐指定大小的内存,并将其内容填充为0x1F。

对于大多数骨架库来说,通常都会使用DSP框架和系统调用。不过,我们的简单程序还不能处理这种请求。因此,在执行其余代码之前,我们必须先在模拟器上加载QuRT。为了完成这个任务,最简单的方法不是使用真正的QuRT操作系统,而是使用其“精简”版本runelf.pbn——该版本可以在Hexagon模拟器上运行,并且可以在Hexagon SDK中找到这个版本。

AFL fuzzer不仅可以修改数据文件的内容,并且还能在模拟器上触发runelf.pbn的执行。QuRT首先会加载准备好的ELF二进制文件,然后调用一个目标骨架库。QEMU在执行测试用例后,将向AFL返回代码覆盖矩阵。

4.png

图4:DSP库的模糊测试方案

在进行模糊测试后,测试结果让我们大吃一惊:在我们进行测试的所有DSP库中都发现了崩溃现象。仅在libfastcvadsp_skel.so库中就发现了数百个不同的崩溃报告。

有趣的是,大多数崩溃问题都是在骨架库中发现的,而不是在对象库中发现的。这就意味着,Hexagon SDK生成的代码容易产生安全问题。

自动生成的代码

下面,让我们看一下开源的hexagon_nn库,它是Hexagon SDK 3.5.1的一部分。这个库导出了很多用于神经网络计算的函数。

对库进行编译时,Hexagon SDK会自动生成存根文件hexagon_nn_stub.c以及skel模型hexagon_nn_skel.c。通过对这些模块进行手动审查,我们轻松找到了多处安全问题,下面我们就举两个例子。

编组字符串(char *)参数

int hexagon_nn_op_name_to_id(const char*name, unsigned int* node_id) 函数需要一个输入参数(name)和一个输出参数(node_id)。下面的存根代码是由SDK生成的,专门用于处理这两个参数:

3.png

我们可以看到,除了现有的两个参数外,在_pra数组的开头处还创建了第三个remote_arg元素。这个特殊的_pra[0]参数用于保存字符串name的长度。

实际上,字符串name本身被保存在第二个remote_arg元素(_praIn[0])中,其长度将再次存储在该数组元素中,但这次是存放到_praIn[0].buf.nLen字段中。

之后,skel代码会提取这两个长度,并将它们作为有符号的int值进行比较——问题就出在这里。攻击者可以忽略存根代码,将一个负值(大于或等于0x80000000)写入第一个remote_arg元素中,从而绕过这个验证。然后,这个伪造的长度被用作内存偏移,最终导致崩溃(越过堆的边界进行读取操作)。

2.png

对于所有需要字符串参数的对象函数来说,都会生成相同的代码。

编组输入输出缓冲区

下面,我们考察inthexagon_nn_snpprint(hexagon_nn_id, unsigned char* buf, int bufLen)函数。该函数需要一个缓冲区及其长度作为参数。需要注意的是,该缓冲区同时用于输入和输出数据。因此,在存根代码中,它被分成两个独立的缓冲区(输入缓冲区和输出缓冲区)。同样的,两个缓冲区的长度(_in1Len和_rout1Len)都存储在另外的remote_arg元素(_pra[0])中。

5.png

skel函数在调用对象函数之前,会将输入缓冲区复制(使用_MEMMOVEIF宏)到输出缓冲区。在这里,需要复制的数据的长度,就是保存在特定的remote_arg元素(_pra[0])中的输入缓冲区的长度。

因此,只要攻击者控制了这个值,所有的验证检查都可以轻松绕过——只需将输入缓冲区的长度变为负值即可。

在检查缓冲区边界时,将长度的类型转换为有符号的int类型,就是导致溢出漏洞的罪魁祸首。

6.png

总而言之,自动生成的代码将漏洞引入到了Qualcomm、OEM和所有其他使用Hexagon SDK的第三方开发者的库中。由于SDK中存在严重的漏洞,导致预装在安卓智能手机上的几十个DSP骨架库都面临严重的安全威胁。

如何利用DSP的漏洞

让我们来看看在专有DSP骨架库中发现的众多漏洞中的一个,并着手创建“read-what-where”原语和“write-what-where”原语。

在大多数Android设备上都可以找到libfastcvadsp_skel.so库。在下面的例子中,这个库的版本为1.7.1,提权自Sony Xperia XZ Premium设备。恶意的安卓应用程序可以通过向remote_handle_invoke函数提供特制的参数,导致libfastcvadsp_skel.so库发生崩溃。图5中的数据文件展示了这种精心制作的参数示例。

5.png

 图5:导致libfastcvadsp_skel.so崩溃的数据文件

正如你所看到的,这里调用了0x3F方法,并为其提供了一个输入参数和三个输出参数。其中,输入参数的内容以字节0x14开始,并包含以下主要字段:

  • 红色0x02表示要读多少个半字(长度)。
  • 黄色0x44332211表示从哪里开始该读取操作(源地址)。这个值是相对于DSP堆中第一个输出参数起始地址的偏移量。使用这个偏移量,我们就可以控制读取的起始地址。我们可以让这个偏移量变成自己想要的长度,甚至可以是负数。
  • 青色的0x04显示该读取操作的结束位置(目的地址)。这个值也是偏移量。

实际上,崩溃是由于源地址不正确造成的。

6.png

图6:转储的崩溃信息

下面是读取原语的缩减版POC代码。

7.png

其中,输入参数总是紧跟在位于DSP堆中输出参数之后。因此,在写入原语中,我们需要根据第一个输出参数的长度来移动源地址(所有其他参数都是空的)。

8.png

攻击者可以在DSP进程(User PD)的地址空间中操纵源和目的偏移量进行读写操作。第一个输出参数和内存中的libfastcvadsp_skel.so库之间的偏移量是一个常值。同时,在skel或对象库的数据段中找到一个指针来触发调用也是很容易的事情。出于安全考虑,我们就不公布利用DSP进程实现代码执行的完整POC代码了。

关于DSP用户域的研究总结

对Qualcomm DSP用户域的部分骨架库和对象库的安全研究过程中,我们发现了两个具有全局性的安全问题:

  • DSP库缺乏相应的版本控制。这使得恶意的Android应用程序可以进行降级攻击,从而在DSP上运行含有已知漏洞的库。
  • Hexagon SDK中的漏洞导致隶属于Qualcomm和手机供应商的代码中出现了数百个隐蔽的漏洞。由于Hexagon SDK存在安全漏洞,使得几乎所有嵌入基于Snapdragon的智能手机中的DSP骨架库都容易受到攻击。

我们向Qualcomm报告了在几十个DSP库中发现的近400个独特的崩溃问题,其中包括下列库:

  • libfastcvadsp_skel.so
  • libdepthmap_skel.so
  • libscveT2T_skel.so
  • libscveBlobDesc riptor_skel.so
  • libVC1DecDsp_skel.so
  • libcamera_nn_skel.so
  • libscveCleverCapture_skel.so
  • libscveTextReco_skel.so
  • libhexagon_nn_skel.so
  • libadsp_fd_skel.so
  • libqvr_adsp_driver_skel.so
  • libscveFaceRecognition_skel.so
  • libthread_blur_skel.so

为了证明这一点,我们利用其中一个已发现的漏洞,获得了在基于Snapdragon处理器的设备上执行无签名代码的能力,受影响的设备包括Samsung、Pixel、LG、小米、OnePlus、HTC和Sony手机。

利用这些漏洞,一个可以访问DSP的用户域的安卓应用就能:

  • 触发DSP内核崩溃并重启移动设备。
  • 隐藏恶意代码,因为反病毒软件不会扫描Hexagon指令集。
  • 由于cDSP负责对来自摄像头传感器的流媒体视频进行预处理,因此,攻击者可以接管这些数据。
  • 访问DSP内核驱动程序。驱动程序中的漏洞可以将应用程序的权限提升为虚拟机操作系统或DSP内核的权限。

DSP驱动程序

QuRT操作系统实现了自己的设备驱动程序模型,称为QuRT驱动调用(QDI)。并且,QDI是无法通过Android API进行访问的。像POSIX一样,QDI设备驱动程序的操作权限高于请求驱动服务的用户代码。此外,QDI还提供了一个简单的驱动程序调用API,用以屏蔽所有与特权模式相关的实现细节。

实际上,作为Hexagon SDK的一部分的libqurt.a库中就包含了QDI基础设施。同时,FastRPC shell就是利用这个库静态链接的。

在QuRT的可执行二进制文件中,我们可以找到几十个QDI驱动程序。它们通常被命名为/dev/...、/qdi/...、/power/...、/drv/...、/adsp/...或/qos/...。同时,我们可以使用int qurt_qdi_open(const char* drv)函数来访问QDI驱动程序,该函数将返回设备句柄,其实就是一个小整数。设备句柄的作用类似于POSIX文件描述符。

实际上,QDI只提供了一个宏,即必要的、用户可见的API。这个qurt_qdi_handle_invoke宏负责所有通用驱动程序操作。事实上,qurt_qdi_open只是这个宏的一个特例,下面是宏的参数:

  • QDI句柄或预定义的常量值之一。
  • 定义所请求的操作的方法编号。在SDK的头文件中,我们可以看到下列编号:
  • 编号1和2是为名称注册和名称查询保留的。
  • 编号3-31保留用于对已经打开的句柄的POSIX类型的操作。
  • 编号32-127保留给QDI基础设施。
  • 编号128-255保留给自动生成的方法使用,比如由IDL生成的方法。
  • 256和更高的编号用于私有方法。驱动程序可以根据需要使用这些方法。
  • 零到九个可选的32位参数。

qurt_qdi_handle_invoke宏用于调用相关的设备驱动调用函数,该函数实现了主要的驱动逻辑,并提供了指定的方法编号和可选参数。

这面是一个从用户PD代码中调用QDI驱动程序的例子:

2.png

QDI驱动程序需要使用int qurt_qdi_devname_register(const char *name, qurt_qdi_obj_t*opener)函数在QuRT中注册自己。为此,驱动程序需要提供自己的名字和一个指向opener对象的指针,来作为该函数的参数。

3.png

opener对象的第一个字段是驱动程序的调用函数。QuRT将调用这个函数来处理来自用户PD或另一个驱动程序的驱动请求,并提供以下参数:

  • QDI句柄,代表发送QDI请求的客户端。
  • 发出该QDI请求的opener对象。
  • 由调用者提供的QDI方法。
  • 由调用者提供的九个可选参数。

一般来说,驱动程序的调用函数相当于一个通过QDI方法ID进行切换的“运算符”。并且,每个方法都可以使用与所提供的参数数量不同的参数;参数类型是qurt_qdi_arg_t。

5.png

请注意,驱动程序调用函数是基于模糊测试的漏洞研究的一个很好的测试对象,因为其方法是通过ID识别的,而不是通过名字识别的,因此,调用者不需要知道参数的确切数量和它们的实际类型,就能调用驱动程序的各种方法。

利用基于反馈的方式对QDI驱动程序进行模糊测试

为了在Ubuntu电脑上对QDI驱动进行模糊测试,我们依然是组合使用了QEMU Hexagon和AFL,这与前面对DSP库进行模糊测试一样。但是,这里没有实现skel_loader程序,而是实现了另一个Hexagon ELF二进制文件qdi_exec,该程序用于:

  • 将作为第一个命令行参数收到的数据文件解析为QDI方法ID和一个由9个参数组成的数组,供驱动程序调用函数使用。
  • 通过第二个命令行参数中指定的地址调用驱动程序调用函数,并提供QDI方法ID和从数据文件中解码的参数。

对于qdi_exec程序,我们使用的输入文件的格式为:

  • 头部(4字节)。它包含三个非常有用的字段:
  • QDI方法ID(10位,位于低地址处)。在图7的示例中,其值为0x01。
  • 参数的数量(4位)。在这个示例中,只用了一个参数;其余八个参数被认为是零。
  • 参数类型的掩码(9位)。正如我们前面提到的,每个参数都是一个数字或一个指向缓冲区的指针。在掩码中,每个参数用一个比特表示。值为0意味着该参数是一个数字,正值意味着该参数是一个缓冲区。
  • 缓冲区参数的长度(每个参数占4字节)。在这个例子中,长度为0x0A的/dev/diag字符串被用作参数。
  • 缓冲区参数的内容。

7.png

图7:用于模糊测试QDI驱动程序的输入数据文件

实际上,QDI驱动程序是作为QuRT ELF的一部分实现的。但是,它们没有被Qualcomm包含在QuRT的runelf.pbn版本中,而我们在模拟器中运行的正是这个版本,因此,我们必须对runelf.pbn ELF文件做如下修补:

  • 在runelf.pbn中加入用于物理设备的QuRT ELF的程序段。我们使用了从Pixel 4设备上提取的aDSP二进制文件。
  • 将QDI驱动程序使用的malloc和memcpy内核函数重定向到其用户模式实现。因为这两个内核级内存函数限制了用户和内核空间之间的数据传输。

8.png

图8:QDI驱动程序的模糊测试方案

之后,我们使用AFL对数据文件的内容进行处理,并在模拟器上触发执行经过修补的runelf.pbn。这样,runelf.pbn就会加载我们的qdi_exec程序,而后者将直接调用QDI驱动程序的调用函数。

我们通过对QuRT二进制文件进行逆向分析,通过手动方式找到了QDI驱动调用函数的起始地址。同时,我们发现opener对象恰好位于驱动程序名称旁边的代码中。

借助于AFL,我在Snapdragon 855 aDSP内置的十几个QDI驱动程序中发现了许多崩溃现象;其中大部分也适用于cDSP。

如何利用QDI驱动程序中的安全漏洞

QDI驱动程序中的任何故障,都可以被用来导致DSP内核崩溃,并重新启动移动设备。例如,下面的每一行代码都会引起DSP崩溃,也就是说,可以用来对设备发动DoS攻击。

3.png

出于研究目的,我们成功地利用了/dev/i2cQDI驱动程序中的几个任意内核读写漏洞,以及/dev/glink QDI驱动程序中的两个代码执行漏洞。出于安全考虑,我们就不公布POC代码了,但我们注意到,这些漏洞的利用方法都相当简单。例如,下面是实现读取原语的例子:

4.png

实际上,恶意的安卓应用可以利用QDI驱动程序中发现的漏洞,以及前面在用户PD的DSP库中发现的安全漏洞,这样的话,它们就能够在DSP虚拟机操作系统的上下文中执行自定义的代码。

从虚拟机操作系统PD请求安卓服务

如果我们试图从DSP虚拟机操作系统代码中打开一个与Android系统有关的文件,会发生什么情况呢?答案是,QuRT会将我们的请求重定向到一个特殊的Android守护进程。如图9所示,在Snapdragon855设备上,存在两个aDSP守护进程和一个cDSP守护进程,并且,它们是以不同的权限运行的:

9.png

图9:DSP Android守护进程

在Pixel 4设备上,这些守护进程的启动命令可以在init.sm8150.rc文件中找到。

10.png

图10:Pixel 4上的init.sm8150.rc启动文件

这些高权限的vendor.adsprpcd和vendor.cdsprpcd守护进程用于处理DSP虚拟机操作系统请求。其中,在u:r:adsprpcd:s0和u:r:cdsprpcd:s0上下文中,只能访问与DSP相关的目录和对象。

小结

我们认为,aDSP和cDSP子系统是非常有前途的安全研究领域。首先,DSP可以被第三方的Android应用程序调用。其次,DSP可以处理个人信息,如流经设备传感器的视频和语音数据。第三,正如我们在本文中介绍的那样,DSP组件中存在许多安全问题。

Qualcomm公司为我们披露的DSP漏洞分配了相应的漏洞编号,分别为:CVE-2020-11201、CVE-2020-11202、CVE-2020-11206、CVE-2020-11207、CVE-2020-11208和CVE-2020-11209。对于QDI驱动程序中发现的漏洞,Qualcomm公司决定不为其分配CVE编号。本文中介绍的所有漏洞,都已通过2020年11月的高通公司安全补丁成功修复。

另外,出于研究目的,我们利用了一些已发现的漏洞,并获得了在所有基于Snapdragon的移动终端的aDSP和cDSP上执行特权代码的能力。


原文地址:https://research.checkpoint.com/2021/pwn2own-qualcomm-dsp/

最新评论

昵称
邮箱
提交评论