概述
本文档主要是针对高通平台驱动fastrpc某个漏洞介绍以下内容:一是漏洞情况分析,和提权过程部分流程,如绕过内核地址随机化(KASLR)。
提权过程
DoubleFree
从《漏洞成因》文档可以看出,该漏洞本质是竞态导致的UAF。发生UAF的函数有fastrpc_mmap_on_dsp()和fastrpc_mmap_free()。这里我们主要关注后者,相关代码如下:
从上述代码可以看出,该函数根据map的数据进行清理。程序首先需要使该函数成功执行kfree函数(行732),使得map对应的SLAB发生Double Free。为了完成上述目的,程序需要针对该函数逻辑堆喷已经释放的SLAB。这里关键点是map->flags和map->refs字段。
首先,我希望该函数执行695行处的分支,原因是该分支需要构造的数据最少,且逻辑简单。所以,map->flags的值需要为FASTRPC_DMAHANDLER_NOMAP。当flags为上述值时,该函数会执行675处的逻辑。为了使kfree()执行,这里要使map->refs为1。其他字段均为0,这样该函数可以欢快地执行kfree(),导致Double Free产生。为了完成该目标,我使用了setxattr系统调用进行堆喷,最终会调用setxattr():
该函数会为kvalue分配空间(行434),长度由用户控制,然后从用户拷贝数据(行440)。所以,我可以分配指定长度、内容可用的SLAB。堆喷成功后,会导致同一个SLAB在SLUB的freelist的链表中出现两次,这意味着后续kmalloc()会将同一个SLAB返回两次(这是攻击的核心点)。
目前,SLUB的freelist状态如下:
注意这里实际上是同一个SLAB:
由于kmalloc()是从通用的SLUB中分配SLAB(比如kmalloc-256),所以待分配的SLAB的前8个字节存放下一个待分配的SLAB地址(图示红色区域),从而形成单向链表。
信息泄露
为了绕过内核地址随机化(KASLR),我需要泄露内核某个全局变量的地址。这里用的是seq_file结构体。该结构体定义如下:
当用户进程打开/proc/下某个节点时(比如/proc/cpuinfo),内核seq_open()会创建相应的seq_file对象。选择该对象的原因是:一是它使用的SLAB与发生Double Free 的map使用的SLAB一样大,意味着该类对象会出现两个对象使用同一个SLAB的情况;二是该结构体中的buf字段与SLUB存放下一个待分配的SLAB地址的偏移相同;三是当用户进程读取节点内容时(read /proc/cpuinfo),内核会在buf中格式化信息,然后将buf中的信息拷贝至用户态;四是该结构体中的op字段指向全局变量cpuinfo_op。
我通过打开多个/proc/cpuinfo节点使内核分配多个seq_file。其中有两个seq_file会使用同一个SLAB:
此时,我读取victim_fd1中的数据,内核会在buf中格式化字符串。读取的长度是offsetof(seq_file, op),读取成功后,seq_file对象会记录用户读取到哪个位置,下次再读取时,将从上次读取的末尾开始继续拷贝。
此时我先关闭另外一个/proc/cpuinfo文件(相关fd为leak_fd),此时freelist指向该seq_file使用过的SLAB(B):
然后我释放victim_fd0,freelist指向该seq_file使用过的SLAB,同时,将B的地址保存在前8个字节位置(也就是buf的位置):
由于victim_fd1仍然使用该SLAB,经过上述过程后,seq_file->buf被SLUB“篡改”为B的地址,此时,再从victim_fd1中读取时,将把B遗留的历史信息读取出来,实际也是seq_file的数据,这样就可以读出cpuinfo_op的地址,从而绕过内核地址随机化。