安卓Stagefright高危漏洞分析学习总结【转】

安卓Stagefright高危漏洞分析学习总结
前言
对于这个漏洞各路高手好像已经分析过很多次了,但都是简单扼要的介绍一下重点,对于我等刚入门的新手来说实在是摸不着门路。由于想向着这个方向发展,所以就萌生了写一写的想法,也当是记个笔记,对各位新手应该还是有点用处的。
在我心中漏洞挖掘一直是个高大上的领域,以前虽然也读过这方面的书籍,也了解过这方面的知识,但一直不敢动手尝试。直到最近看中了家乡的一家企业,而其他们招的就是安全研究,所以投了一份简历,但没有得到回应。细想应该是自己没有这方面的工作经历,遂下定决心要好好学习下这方面的东西,古人说三十而立,自己也应该选定以后的发展方向了,而漏洞研究所需要的对底层实现的刨根问底,对安全问题的敏锐直觉,对碰到难题的锲而不舍正是自己所具有的。更关键的一点是我对此非常感兴趣,尤其是最近这段时间的亲身实践,更坚定了我以后迈上漏洞挖掘的步伐。
简介
安卓Stagefright漏洞指的是google媒体服务中存在的一系列漏洞,主要存在于libstagefright.so中,此漏洞的危害还是比较严重的。一旦攻陷,攻击者便可访问照相机、蓝牙、以及一些敏感数据,而且这系列的漏洞影响非常广,据说覆盖95%的安卓设备。所以就萌生了一探究竟的想法,当然我不可能把所有的漏洞都介绍一遍,这里仅以CVE-2015-1538 #1为目标进行调试分析,但看cve描述其它的漏洞应该也差不多,都是由于整数溢出。
 成因
位置:media / libstagefright /SampleTable.cpp
下面是修补此漏洞添加的代码:
可以看出溢出的原因应该就是:
mNumSampleToChunkOffsets*sizeof(SampleToChunkEntry)
超出了32位数所能表示的的最大范围,导致高位被丢弃,从而导致后面分配的空间是一个错误的大小。而后面接下来的代码是:
他会循环mNumSampleToChunkOffsets次,每次从文件中读出12个字节,然后存入对应的mSampleToChunkEntries中,从这里就可以看出问题的严重性了,由于分配的空间远远小于预期的空间,所以会导致堆溢出,从而破坏堆中的数据。但这里有个问题,由于SampleToChunkEntry的大小是12字节,要让整数溢出
mNumSampleToChunkOffsets>0xffffffff/0x0xc=0x15555555
难道我要一直循环0x15555555次,那得要多大的数据量来覆盖啊。还好
中间数据读取失败也会退出循环,所以我们可以自由控制堆溢出的数据量。
利用
由于这是一个典型的堆溢出,溢出的数据可以由外部输入控制,所以基本的思路就是使用堆喷技术在内存中产生大片的目标代码,然后我们预测一个能命中目标代码概率比较大的地址用来作为溢出的数据。具体可以看看漏洞作者提供的利用代码:
https://blog.zimperium.com/the-latest-on-stagefright-cve-2015-1538-exploit-is-now-available-for-testing-purposes/
接下来我就重点讲下我调试此漏洞的过程,由于作者的代码是工作在4.0的系统中,而我要在其它系统中实验,所以中间过程真是一波三折啊,不过学到的也很多,感触就是与其文章看一百遍,不如自己动手调试一遍,只有在调试的过程中才能感受到那些微妙的变化,比如栈迁移,比如通过一些gadget控制pc的移动,比如那些数据的精心布置,都只有在调试的过程中才能感受到它的巧妙。
第一次接触
平台:nexus 5
系统:android5.0.1(lrx22c)
首先运行作者提供的python脚本产生一个MP4文件:
然后把mp4文件放到手机的sd卡上,先打开logcat监视日志输出,然后打开ncat监听3523端口:
最后使用视频软件打开mp4文件,ncat没有监听到连接进入,但logcat监视到mediaserver的崩溃日志:
从日志中看不到预料中的内存破坏,而是一个异常中断的错误,从堆栈也可以看出在new中调用了abort函数终止了线程,虽然也引起了崩溃,但这个崩溃完全是一个还算正常的崩溃,看到这里我就很疑惑了,我是用作者提供的软件测试了,确定这个版本的android是存在此漏洞的,为什么现在是这个结果呢?为了找到原因只得打开ida动态调试
关键原因就是箭头指向接下来的三条指令,如果
mNumSampleToChunkOffsets>0xAA00000
那么接下来的
会执行,根据动态调试的结果,执行后R0=0xFFFFFFFF,然后调用new的时候会被正常终止,也就阻止了后面的堆溢出,从而使得整个漏洞不可被利用。
刚开始看到这点我是百思不得其解,翻阅5.0.1的源码也没有看到任何相关的代码,后来在作者的官网上看到这么一句话,终于恍然大悟
原来是编译器搞的鬼,就不知道那个0xAA00000是怎么得出来的,是固定写死的,还是根据sizeof(SampleToChunkEntry)计算出来的。总之第一次亲密接触就这么结束了,也恭喜自己踩了一个大坑。
第二次尝试
平台:荣耀6联通版(H60-L02)
系统:android4.4.2
前面的步骤一样,用视频软件打开产生的mp4文件后,ncat没有监听到连接进入,logcat监视到了崩溃日志:
这次终于看到了一个漏洞该有的崩溃了,是一个内存破坏异常,更关键的一点是出错的地址:
这不正是溢出后用来覆盖堆的数据么,也就是
接下来看堆栈
出错的地方仍然是在SampleTable::setSampleToChunkParams函数中,这次虽然没有了上面5.0.1的new中abort的问题了,但还有个问题,原作者的利用代码是针对崩溃点在
而我的测试却崩在了这个地方,所以利用肯定是不能正常工作的了,而且我多次测试全是在这个地方崩溃,看来也不是偶然问题了。难道这又是要踩坑的节奏?不管怎样还是先打开ida看看再说,定位到崩溃那行:
根据代码流程,首先是从R8+8处获得R0的值,而此时R0的值是sp_addr,表示R8+8处的数据已经被溢出覆盖,接下来从R0这个地址取值,根据经验可以知道R0肯定是个对象的地址,这句执行完后R1里面是R0这个对象的虚表地址,后面的两句也验证了我的猜想,
先是从虚表中获取一个函数指针,然后调用它。看到这里我心里不禁一喜,数据可以控制,也有执行的机会,这不正是一个利用点么。虽然和作者的利用点不一样,但本质是一样的,都是获取对象的虚表中的某个函数然后调用之。只是利用脚本中堆喷数据的内存布局需要做相应的调整,另外还有几个其它的地址也需要相应的调整,不同的机型或系统可能值会不一样,下面是本机的一些值:
如果想做测试的话,具体的值要根据调试确定,那个sp_addr的确定需要多次调试,选取一个命中概率比较高的地址,这样成功的机会就高。还有一个注意的地方是记得关闭ASLR:
echo 0 >/proc/sys/kernel/randomize_va_space
做好这些调整后产生一个新的mp4放入手机然后打开,如果运气的好的话,就会出现下面的画面:
由于这是笔者第一次分析漏洞,当看到这个画面的时候心中也是抑制不住的狂喜啊,仅凭一个mp4文件就获得了目标的控制权,想想就是一件令人非常兴奋的事情。
进一步分析
俗话说知其然还得知其所以然,虽然通过调试完成了漏洞的利用,但这里还有几个值得深思的地方,也许对于高手来说,这些早已了然于心,但对于我来说还是让我迷惑了好一阵子,最终的结论也不知道正不正确,如果有不正确的地方还望指正:
1.     为什么作者的利用点在_ZNK7android7RefBase9decStrongEPKv而我的在setSampleToChunkParams
2.     利用为什么有时成功,有时不成功
3.     为什么那个sp_addr是写死的,而且也必须写死,但最终刚好能命中我们通过堆喷布的局,作者的sp_addr=0x41d00010而我的是0xB361A010,尤其是地址的最后一个字节,简直是神预测,哪怕堆喷时分配的起始地址错位一个字节,那么后面所有的页都会相应的错位,由于我们的利用点不是blxsp_addr(如果是这个还可以通过nop填充,即使错点位也没事),而是ldr经过几级寻址才能最终调用,所以对于布好局的页容不得半点错位,依我以前的理解,内存分配的起始地址应该是随机的,但通过调试我发现确实如作者预测的那样,每次分配的地址的最后一个字节都是08。
对于第一点这主要涉及到几个对象在堆中的布局问题,这几个对象是(前面是对象名,后面是类型):
mSampleToChunkEntries:SampleToChunkEntry[]
mDataSource:DataSource
sampleTable:SampleTable
mLastTrack:Track
我的崩溃点对应的源码位置在media/libstagefright/SampleTable.cpp
对照着汇编代码不难得出R8=sampleTable,R0=[R8+8]=mDataSource,
R1=readAt由于setSampleToChunkParams是SampleTable成员函数,所以进入setSampleToChunkParams时R0=sampleTable,堆开始溢出的起点是mSampleToChunkEntries。如果mSampleToChunkEntries<sampleTable,并且sampleTable在溢出的范围之内,那么sampleTable中的数据被溢出数据覆盖,所以就出现我这里的这种崩溃情况。如果mDataSource和sampleTable都不在溢出的覆盖范围之内,而mLastTrack在溢出覆盖范围之内那么就会出现作者的那种情况,在对象析构的时候崩溃。如果mDataSource被覆盖,sampleTable没有被覆盖,那么崩溃会发生在LDRR1, [R1,#0x1C]这一句,由于我的利用布局是针对sampleTable被覆盖的情况,所以在这种情况下利用就会失败,这也是利用有时成功有时不成功的一种原因。
对于第二点我在前面已经说了一种原因,还有一种原因就是sp_addr没有命中堆喷的地址范围,导致异常崩溃了,在我的调试中好像大部分失败的原因都是这个。
对于第三点我得出的结论是:
由于堆喷的大小是((2*1024*1024) / 4096) – 20=492页,所以分配时由于优化方面的考虑,分配的起始地址应该是页边界对齐的,也就是xxx000这种样子,但由于分配的内存还需要管理,所以头部的前8个字节被用作管理方面的用途,所以最终分配出来的起始地址是xxx008这种样子,正是这个原因所以我们的利用才能得以成功。
后记
经过了几个早上终于把这篇文章写完了,由于平时很少写东西,而且这也是自己第一次动手分析漏洞,所以错误和疏漏在所难免,内容的深度估计也有些欠缺,还请高手们不要笑话。
I like you, but you.
纵然万劫不复,纵然相思入骨
   我也待你眉眼如初,岁月入故
此条目发表在Android, 未分类, 漏洞攻击分类目录。将固定链接加入收藏夹。