【宁静圈】联发科芯片任意读写毛病影响20余款手机、百万安卓设备

  • 时间:
  • 浏览:29
  • 来源:安图在线

一、毛病配景

2020年3月,谷歌修补了一个存在于联发科芯片中的宁静毛病(CVE-2020-0069),毛病影响20余款联发科芯片和数百万Android设备。该毛病存在于MediaTek Command Queue驱动(CMDQ下令行列驱动),允许当地攻击者实现对物理内存地址的任意读写,从而导致权限提升。

二、受影响国产手机型号

Huawei GR3 TAG-L21

Huawei Y5II

Huawei Y6II MT6735 series

Lenovo A5

Lenovo C2 series

Lenovo Tab E7

Lenovo Tab E8

Lenovo Tab2 A10-70F

Meizu M5c

Meizu M6

Meizu Pro 7 Plus

Oppo A59 series

Oppo A5s

Oppo A7x -- up to Android 8.x

Oppo F5 series/A73 -- up to A.39

Oppo F7 series -- Android 8.x only

Oppo F9 series -- Android 8.x only

Oppo R9xm series

Xiaomi Redmi 6/6A series

ZTE Blade A530

ZTE Blade D6/V6

ZTE Quest 5 Z3351S

三、CMDQ驱动简析

DMA(直接内存会见)是允许专用硬件直接从主存储器(RAM)发送或吸收数据的一种特性。其目的是通过允许大内存会见而不外多占用CPU来加速系统。MediaTek Command Queue驱动(CMDQ下令行列驱动)允许从用户层与DMA控制器通信,以实现媒体或显示相关的任务。

基于Redmi 6/6A 源代码分析,在cmdq_driver.h头文件中,声明cmdq驱动的IOCTL挪用如下:

CMDQ_IOCTL_ALLOC_WRITE_ADDRESS指令为分配一个DMA缓冲区

CMDQ_IOCTL_FREE_WRITE_ADDRESS指令为释放一个DMA缓冲区

CMDQ_IOCTL_READ_WRITE_ADDRESS指令为读取一个DMA缓冲区中的数据

CMDQ_IOCTL_EXEC_COMMAND指令运行发送其他下令

1、分配历程

通过CMDQ_IOCTL_ALLOC_WRITE_ADDRESS挪用cmdqCoreAllocWriteAddress ()函数,分配一个DMA缓冲区,该函数关键代码实现如下:

然后,挪用cmdq_core_alloc_hw_buffer()函数分配DMA缓冲区,pWriteAddr->va是虚拟地址,pWriteAddr->pa为物理地址,两者一一对应。并清理缓冲区。

最后,将物理地址赋值到paStart,并将pWriteAddr结构体添加到gCmdqContext.writeAddrList链表中。

2、执行下令历程

在CMDQ_IOCTL_EXEC_COMMAND挪用中,接纳cmdqCommandStruct结构体作为参数,结构体界说如下:

pVABase指向用户层存放下令的缓冲区,缓冲区巨细放在blockSize中。其中cmdqReadAddressStruct结构体界说如下:

DmaAddresses是要读取的物理地址,读取的值存放在values中。在CMDQ_IOCTL_EXEC_COMMAND下令的执行历程,实现代码如下:

函数挪用路径如下:

Cmdq_core_acquire_task()函数会将command绑定到task中执行。详细实现如下:

挪用cmdq_core_find_free_task()函数获取一个空闲task。拿到空闲task并举行一些初始化设置,然后开始挪用cmdq_core_insert_read_reg_command()函数执行下令。

该函数实现分析,先拷贝用户层传入的下令到DMA缓冲区中。

pCommandDesc->pVABase是存放下令的内存起始地址。拷贝完下令后,后面分几种方式末端。

这里不做深究,最后拷贝EOC和JUMP指令末端。这里也是将用户层传入的下令拷贝过来。

从cmdq_core_acquire_task()函数中返回后,如下:

挪用cmdq_core_consume_waiting_list()函数执行task。先从等候行列中获取task。

然后,获取空闲内核线程。

最后,将task绑定到thread中去执行。

四、读写下令分析

以cmdq_test.c测试代码为例,分析明白一个完整的读写下令结构。cmdq驱动中界说了两类寄存器,一类是地址寄存器用于存放地址,一类是数值寄存器用于存放读取或写入的数值。

regResults是虚拟地址,挪用cmdq_core_alloc_hw_buffer()函数分配一个dma地址,regResultsMVA与之对应,然后设置regResults中的数据。开始拼接读取和写入下令:

将regResults[0]的地址写入CMDQ_DATA_REG_DEBUG_DST类型的地址寄存器中。

然后,从CMDQ_DATA_REG_DEBUG_DST地址寄存器中读取数据并写入到CMDQ_DATA_REG_DEBUG数值寄存器中。这时候,CMDQ_DATA_REG_DEBUG数值寄存器中的值应该为0xdeaddead。

接着,将regResults[1]的地址转存到CMDQ_DATA_REG_DEBUG_DST地址寄存器中。

最后,将CMDQ_DATA_REG_DEBUG数值寄存器中的0xdeaddead写入到CMDQ_DATA_REG_DEBUG_DST地址寄存器中生存的regResults[1]的地址中。即regResults[1]=0xdeaddead。判断regResults[0]和regResults[1]是否相等。

如果相等,说明读写乐成。

五、PoC分析与测试

(1)PoC代码中,执行写操作的关键代码如下:

写入历程中,先将value[count]移动到CMDQ_DATA_REG_DEBUG数值寄存器中,然后将pa_address+offset地址移动到CMDQ_DATA_REG_DEBUG_DST地址寄存器中,最后将CMDQ_DATA_REG_DEBUG数值寄存器中的value写入到CMDQ_DATA_REG_DEBUG_DST地址寄存器中生存的pa_address+offset地址中,即*(pa_address+offset) = value[count]。

(2)PoC代码中,执行读操作的关键代码如下:

读取历程中,第一步先将pa_address+offset地址移动到CMDQ_DATA_REG_DEBUG_DST地址寄存器中,然后从CMDQ_DATA__REG_DEBUG_DST地址寄存器中存储的地址pa_address+offset中读取数据放到CMDQ_DATA_REG_DEBUG数据寄存器中,再将dma_address+offset地址移动到CMDQ_DATA_REG_DEBUG_DST地址寄存器中,最后将CMDQ_DATA_REG_DEBUG数值寄存器中生存的数据写入到CMDQ_DATA_REG_DEBUG_DST地址寄存器中存储的dma_address+offset地址中,即(dma_address + offset) = (pa_address + offset)。

(3)在Reami6测试机中,执行PoC测试,乐成将Linux修改成minix。

六、参考链接

https://github.com/MiCode/Xiaomi_Kernel_OpenSource/tree/cactus-p-oss/drivers/misc/mediatek/cmdq

https://github.com/quarkslab/CVE-2020-0069_poc/blob/master/jni/kernel_rw.c

https://blog.quarkslab.com/cve-2020-0069-autopsy-of-the-most-stable-mediatek-rootkit.html

https://forum.xda-developers.com/android/development/amazing-temp-root-mediatek-armv8-t3922213

https://source.android.com/security/bulletin/2020-03-01

文章泉源:seebug