想学习下android漏洞方面的知识,搜了下发现Flanker Edward在知乎上有个回答,提到了binder的经典漏洞cve-2015-6620,所以就从这个漏洞开始学习。作者提供了poc以及文档,这篇笔记主要记录下学习中遇到的问题,以及自己的一些理解。
第一次调试android漏洞,搭建环境花了些力气,主要有如下环境,推荐安装pead-arm和shadow这两个gdb插件。
这是android平台上的binder方面的漏洞,所以涉及一些android底层的知识需要学习下。
cve-2015-6620包含两个漏洞,编号分别为24123723和24445127。主要分析的是24445127 MediaCodecInfo越界访问,因为这个漏洞可以利用的点更多些。漏洞存在于MediaCodcList服务。该Binder服务提供了一个getCodecInfo的功能,存在漏洞的代码如下:
从Parce中读取从客户端传来的索引index,然后调用在服务端的实现的getCodecInfo。看下在MediaCodecList中实现的getCodecInfo
可以看到直接调用了vector的itemAt函数,并未进行任何边界检查。而index是我们作为客户端程序可以控制的,这个地方就存在一个越界访问的漏洞。
根据漏洞的成因,我们现在有这样一个能力:可以越界访问Binder服务所在进程中的一个vector<sp<MediaCodecInfo>>,但是只能读取不能写入。漏洞的作者利用这样一种能力可以实现任意地址读取和pc寄存器的控制,很是神奇。主要分析下pc控制的原理。在分析poc原理前,需要了解相关对象在内存的布局,如下图所示:
一个越界读可以造成pc的控制,关键在于getCodecInfo的调用: const sp\<MediaCodecInfo\> info = getCodecInfo(index);
上面的代码是用getCodecInfo函数的返回新建了一个info对象,这就会调用info的拷贝构造函数。info的类型为sp,sp的拷贝构造函数如上所示。可以看看getCodecInfo的汇编版本,像这样返回对象的函数,一般会把R0指向返回对象保存的地址。
可以看到会将vector的内容读取到R0中,如果R0不为零,会调用incStrong, 代码如下:
汇编代码版本,可以清楚看到存在虚函数的调用:
梳理一下就是,越界读取的内容放入R0,然后进行如下操作:
也就是说如果我们在内存中伪造了合适的MeidaCodecInfo,并且将指向该伪造的MediaCodecInfo的指针放入vector<sp<MediaCodecInfo>>存储区的后面,这样我们就可以通过越界访问,读取到指向该伪造的MediaCodecInfo的指针,进而控制pc。我们可以在内存中伪造如下的MediaCodecInfo:
最终poc运行成功,mediaserver运行到我们指定的位置:0x61616161
要成功的运行poc实现漏洞利用的目的,要进行两次堆喷射,第一次是将我们伪造的MediaCodecInfo喷射到内存中,第二次是将我们伪造的MediaCodecInfo的地址喷射到vector<sp<MediaCodecInfo>>的存储区的后面,这样可以通过越界读取,来触发漏洞。
但是遇到了如下问题:
1.作者的poc中,硬编码了一个地址0xb3003010,就是在作者的测试机器上,作者喷射的伪造的MediaCodecInfo有很大概率会落在这个地址上。作者说之所以是0xb3003010而不是0xb3003000是因为数据前面还有0x10字节的元数据,但是我们知道jemalloc中存放数据的region和run都不包含元数据,那么这个元数据是哪里来的?
先看下是如何堆喷射的,作者使用IDrm.provideKeyResponse进行堆喷射的,服务端在拿到response后,最终会调用Session.provideKeyResponse处理:
可以发现喷射的数据最终是保存在android::Vector中的,通过查看源码发现:android::Vector的数据是保存在SharedBuffer中的。0x10字节保存的就是SharedBuffer的私有变量
2.第一次堆喷射需要将指向伪造的MediaCodecInfo的指针喷射到vector<sp<MediaCodecInfo>>的存储区的后方,但是我在运行作者poc的时候,越界读取很少可以命中,所以就想如何可以提高命中率。
作者的方法是:vector的存储区肯定是jemalloc分配的,肯定是落在某个大小的region内,所以作者首先计算出这个大小,然后后面堆喷射时,喷射出大量相同大小的region,这样后面越界读取就有很大概率命中。所以关键是如下步骤:
通过调试发现vector<sp<MediaCodecInfo>>的存储区所在region大小和作者中poc给的一致,但是在调试时发现喷射的payload并没有落在大小为160的region内,而是在0x100的region内。
原因就是payload在mediaserver中是保存在Vector中的,Vector在分配空间时会多分配一些,所以大小为160的payload,最终会放置在大小大于160的region中,通过调试,我把payload大小改为96就可以保证分配在160的region中。
修改后,运行poc后,内存布局如下图所示:
3.作者并未说明是如何找到0xb3003010这个地址的,存在这样一个地址的依据是什么呢?
查了一些关于android堆喷射的资料,都提到jemalloc相比之前的dlmalloc更脆弱些,具体表现在如下方面:
上面的堆地址熵较小表现在:32位ARM系统上的ASLR算法的实现很简单,ASLR会将所有的模块随机向下移动几页,范围在0~255页,代码如下,mmap_rnd_bits可以通过/proc/sys/vm/mmap_rnd_bits 来改变。
可以看到随机移动的范围不大,而进程的各个模块在大范围的是相对固定的,比如在我的机器上,堆分配的空间基本落在0xaf000000 - 0xb6000000 之间,当分配的内存远大于255页时,就基本可以找到一个稳定的地址来放置payload,可以粗略计算下:
作者构造的payload在binder服务端,最终是保存在Vector中的,Vector会多分配一些空间。在我的机器上最终分配的空间大小为6144字节,放在大小为0x1800的region内,都是放在大小为0x3000的run内,每个run可以放置两个这样的region,并且run是页对齐的。也就是说分配两次payload就占用了3页大小空间,作者的poc一共分配了0x1200次,理论讲会一共分配了6912页,当然实际中会存在回收后再利用的情况,在我的机器上实际上分配了3000多页,这是远远大于随机移动的0~255页。所以可以找到这样一个相对稳定的地址。
作者并没有给出explicit,但是提到可以用 CVE-2015-1528 exp的思路,后续会继续学习下这个思路,有成果了就补上。小弟刚开始学习漏洞这块,有错误恳请大佬们指正。
漏洞作者给的文档以及poc: https://github.com/flankerhqd/mediacodecoob
adb+gdb : https://wladimir-tm4pda.github.io/porting/debugging_gdb.html
binder学习:https://blog.csdn.net/universus/article/details/6211589
jemalloc: https://blog.csdn.net/koozxcv/article/details/50973217
shadow jemalloc 调试: https://blog.csdn.net/hl09083253cy/article/details/79147625
shadow 安装:https://github.com/CENSUS/shadow
heap fengshui: https://bbs.pediy.com/thread-55879.htm
ASLR:http://drops.xmd5.com/static/drops/papers-14181.html
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2018-5-11 21:45
被glider菜鸟编辑
,原因: