Wednesday, April 20, 2016

CVE-2015-3864 libstagefright攻击学习


        CVE-2015-3864libstagefright.so里的一个整形溢出漏洞,在Google ProjectZero的博客里已经写的非常详细了。现在接着这个机会学习一下Arm架构以及安卓的系统,然后把这个利用移植到Samsung I9023手机上(Android版本4.1.2)。这篇博客就记录下我自己走过的坑,写点google的博客里没提到的。顺便推荐一个网站,www.androidxref.com,有各个版本的Android源码,不用自己去下了,非常方便。

        先简单介绍下背景。具体漏洞位置是在解析mp4文件的MPEG4Extractor::ParseChunk()函数里,处理tx3g标签时的一个整形溢出,进一步导致堆溢出。在4.1.2的安卓版本里,这个地方的代码还不太一样,少了一个判断语句,应该算是更早之前的一个什么洞。不过问题也不是很大,利用过程都大同小异,没太大差别。



        然后利用方法就是Google博客里写的,先堆喷,再挖空,然后把可控数据结构填进去,再触发漏洞,形成越界写,覆盖下一结构的虚表,进而控制PC。大体思路是这样的。但是4.1.2版本的libstagefright的代码不太一样。似乎是太老的缘故,对很多标签的解析都没有,所以还要重新去找。

堆喷


        Google博客上说的堆喷用的是对‘pssh’标签解析,但在4.1.2上没有实现pssh标签的解析。经过一番查找,我找到另外两个适合做堆喷的标签,一个是‘mdat’,还有个是‘covr’


        在解析mdat标签的代码里,有一个parseDrmSINF()函数,里面有个可控的循环可以不停地分配可控大小的堆块。乍一看感觉非常理想。结果后来调试的时候发现一直跑不到那个函数。一看才发现是前面的一个mIsDrm标志是空的。再仔细往下看,发现这个是和DRM(数字版权管理)有关系。具体细节后面再讲。总之最后得出结论是用不了。



        然后是covr标签,有个内存泄漏。New了之后没有做delete。可以做堆喷。



关于DRM


        要想置位mIsDrm标记,似乎唯一的机会就是在MediaExtractor::Create()函数里。



        在MediaExtractor::Create()函数里,首先会调用一系列注册好了的sniff函数。Sniff函数的作用是检查这个文件到底是什么格式的,每过一个sniff函数就会更新一个confidence值,confidence值最高的那个就是识别出来的文件格式。然后MediaExtractor::Create()函数会根据不同的文件格式,分发到不同的Extractor函数里。


        所以,关键是在SniffDRM函数里要让这个文件通过。根据代码,要通过SniffDRM关键似乎是在一开始的source->DrmInitialization()里面,然后会进一步地调用到ChromiumHTTPDataSource::DrmInitialization()。然后这个函数里会对mDrmManagerClient->openDecryptSession()函数的返回值进行判断。而,这个函数,经过一系列调用,会走到DrmEngineBase::openDecryptSession(),最后调用 onOpenDecryptSession()openDecryptSession() 定义成虚函数,经过一番查找,最后发现所有的openDecryptSession()定义的地方都是直接返回DRM_ERROR_CANNOT_HANDLE,也就是说Android framework里面默认是不对DRM进行处理的。Framework里就定义了这样个接口,开发者可以去继承这个类,实现这个接口。后来又看到了一篇博客,于是便释然了。

        结论就是,在原生的Android framework上不能用DRM,有mIsDrm标志的条件跳转一定是Null的。不过,可能有些别的厂商实现了DRM的功能,就可以用了。

关于Heap Grooming


        在Google的利用代码里还用到了‘hvcC’标签,这个在4.1.2里也是没有的。不过替代的方案很多,有很多标签用setCString()设置字符串的都可以用,不过在我的代码里用的是和hvcCavcC比较类似的esds标签。他们都是用setData()函数的。

        关于setData()函数,内部是一个对KeyedVector的操作,一个key对应一个value。当setData()key值已经存在时,setData()会先把key对于的value的内存地址free掉,然后再malloc一块新的value大小的内存,然后再memcpy把数据拷过去。setCString()函数内部就是一个重新封装了的setData()

还有关于堆喷的


        在covr标签的代码里,最后也会有一个setData()函数,不过只要每次堆喷的大小一样就不影响布局了。

        攻击过程还是通过浏览器实现的,所以堆喷的时候还不能喷地太多,否则程序会提前中止。NorthBitMetaphor里提到用XMLHTTPRequest来请求,可以绕过这个限制。


        不过,我最后还是手工地选取堆喷地址,最后也能得到一个稳定的PC control

关于Info Leak


        还是NorthBitMetaphor,里面提到4.4以下的版本没办法Info Leak。我又自己去看了下源码,确实是这样,原因还是在那个DRMflag。然后我看了下那个接口的onTransact代码,似乎是没有别的办法做Info Leak了。

最后


        ROP阶段我用的是ROPGadget,找gadget非常方便。

        写shellcode参考的是promised_lu的文章http://bbs.pediy.com/showthread.php?t=155774

        最后,因为没办法Info Leak,所以还是用Google的方法,以1/256的概率撞libc地址。测下来大概跑700次能成1次。

        我写的攻击源码在这里https://github.com/HighW4y2H3ll/libstagefrightExploit


No comments:

Post a Comment