本文原创作者:MarcusAurelius
该利用过程分为5个复杂的Stage,本文首先分析了第1个Stage,即Stage0。 攻击代码的Stage0阶段通过一个0Day地址泄露漏洞,为后续数据填充操作完成定位地址定位的任务。此漏洞位于android-src/external/libxslt模块中。
该漏洞属于未经正确性检查,就直接错误地进行强制类型转换,从而导致一个用于内存读(实际用于字符串比较)的指针可以被操控。漏洞利用的思路是:通过在JS分配的大片内存空间中,填充字符串:http://www/w3.org/1999/XSL/Transform,程序的后续逻辑会将该字符串会与那个可控内存指针所指向的字符串进行比较,一旦字符串对比不成功,则javascript的一个对象会记录下“Error”,由于指针所指向的地址是可控制的,一旦发现 “Error”,就重新调整地址,直到没有发生Error,这样就得到了javascript开辟的可控空间的内存地址信息。
图1展示了hacking team所使用的触发漏洞代码:
图1 漏洞触发代码与其内存结构
在浏览器中,我们可以使用javascript引用如图1所示的xsl文件,通过此文件,即可引发stage0所使用的漏洞。图1中的xsl语句,在libxml2解析后,会形成如图1中右图所示结构。其中,xsl描述的cdent节点,即为触发漏洞的ENTITY类型节点。
此漏洞发生在浏览器引擎对xsl文件的解析过程中。因此,我们先简述一下图1中代码,与右图节点的对应关系。右图的myDoc节点为虚的根节点,在左图xsl描述中并没有对应。xsl描述的最外层adoc和ata节点,同为根节点的下的一级子节点,由于adoc是第一个子节点,被myDoc的数据结构中得children指针指向,ata则被adoc的next指针指向。同理,x节点,cdent节点和y节点,同为adoc的子节点一级,形成如上图所示结构。
程序出现异常的根本原因是由于libxslt在对图1中的cdent(xmlEntity类型)节点进行操作时,并未对此节点的类型进行比较,导致对cdent节点进行了不正确的类型转换(转换为xmlNode类型),并对转换后的节点进行了非法操作。libxslt(android浏览器在对xsl进行解析时所使用的模块)在对xsl操作的过程中,会对大部分节点的namespace进行比较,判断节点是否为一个合法的xsl节点。
但当节点类型为xmlEntity类型时,由于xmlEntity没有ns(用来比较namespace信息的数据结构)字段,所以不对xmlEntity类型进行比较namespace操作(xmlEntity类型、xmlNode类型的详细信息请看图5)。而libxslt在判断节点类型是否为xmlEntity时,libxslt的判断并不完全,最终导致xmlEntity可被转换为xmlNode进行操作。从而引发程序崩溃。
图2 未对所有节点的类型进行判断
触发崩溃的第一步,是由于对节点类型的比较不完全导致的。libxslt在成功构建节点树(如图2中右图)后,会循环对myDoc的子节点进行namespace比较操作,图2中左侧代码,是在循环中当对当前节点的所有操作完成时,将循环操作中的节点指针指向下一个需要操作的节点的代码。根据图2中第4967行的代码,我们可以发现,libxslt是希望当目标节点子节点或next节点的类型不为xmlEntity类型时,将目标节点子节点或next节点赋值给cur指针,再由cur指针进入下一次循环。但是,libxslt只对cur指针的第一个子节点进行了数据类型的比较操作,并没有对cur指针的next节点进行数据类型的比较。而在我们构建的节点结构中,cdent(xmlEntity类型)节点,正是被x的next指针指向的,导致cdent节点不正确的进入了循环。
图3 对namespace进行比较的函数
触发崩溃的第二步,是xmlEntity节点被转换为xmlNode节点。图3为第一步中所说的循环的开始位置(图3代码的4893行),我们可以清晰的看到第一步所用的cur指针的数据类型为xmlNode类型。而第一步的xmlEntity类型被赋值给了xmlNode类型的指针,这就导致了类型转换的错误。
图4 IS_XSLT_ELEM宏的定义
触发崩溃的第三步,是对不正确的数据类型进行了非法操作。图4为图3中IS_XSLT_ELEM宏的展开。IS_XSLT_ELEM宏的操作是比较节点的namespace,判断xmlNode结构体中的ns字段(此字段指向一个xmlNs的数据类型)中的href字段所指向的地址是否存储了字符串“http://www.w3.org/1999/XSL/Transform”。
图5 相互转换的两种数据类型
当IS_XSLT_ELEM宏的输入参数为一个xmlEntity类型时,宏就会将orig字符串当作xmlNs类型的数据结构来操作。由于程序中的orig字符串存储的是xmlEntity节点内的字符串信息,所以在触发漏洞的代码这个例子中,此处存储的字符串为:
'<html>XX'; toascii(addr); // ns->href toascii(addr); // ns->prefix 'XXXX' // ns->_private 'XXXX' // ns->context '<xsl:message xmlns:xsl=/'http://www.w3.org/1999/XSL/Transform/' terminate=/'yes/'/></html>
而在宏中,会把这串字符串当作xmlNs类型来操作。
图6 xmlNs的数据结构
因此可以通过此漏洞达到地址泄漏的目的。可以注意到,当宏将字符串当作xmlNs节点来处理时,代码中转换为ascii码的addr就可以被当作比较的字符串指针(href)来处理(详细信息见图4)所以我们可以将任何可以转化为ascii码的地址写入href指针,从而让这些指针所指向的字符串与“http://www.w3.org/1999/XSL/Transform”进行比较。
如果比较结果为真,则继续后续操作,否则,libxslt将返回一个可被javascript接收的error信息。攻击者通过接受的error信息,就可判定输入的地址是否有指定的字符串,再结合大量的数据填充,就可以打到地址泄漏的目的。(详见“三、利用方式”)
调试过程在nexus 7+android4.3上进行。
由于在webkit解析xsl文件时,如果标签为<!ENTITY时,会调用xmlParseEntityDecl函数来对xmlEntity节点进行解析。
图7 程序在xmlParseEntityDecl停下时的状态
myDoc节点:0x400d9768最终程序崩溃时,调用栈中会显示是由于处理这个对象而导致崩溃。
Input:0x59988c18通过查看input信息,可知对xsl对象所解析到那个节点。
图8 Input中的信息
图9 程序的解析状态
如图9显示,正在解析!ENTITY节点。(base表示所解析的字符串,cur表示当前解析到的位置。)
函数entityDecl会创建引发错误的ENTITY对象:
图10 xmlEntity节点的创建(此时节点的数据类型为xmlNode)
如图10中所示,新生成的next节点,为通过entityDecl函数所得到的entity节点(type=0×11,0×11表示为entity节点,为图2中XML_ENTITY_DECL的实际值)。此时此节点的compression字段(在xmlEntity中此字段为orig字段)为0。
函数xmlParseEntityValue会将节点中的字符串存储于orig字符串指针中。
图11 获取xmlEntity节点中的字符串信息
如图11所示,程序将从!ENTITY节点中解析出的字符串传给了orig。(input中的cur(当前值)已向后移动,而之前的部分存入了orig中)
之后,函数会将orig赋值给xmlEntity对象的orig。
图12 将字符串存入xmlEntity的orig字段中
函数通过cur对目标xmlEntity节点的orig字段进行赋值。
图13 将之前出问题的节点转换为xmlEntity类型
如图13所示,被赋值的cur正是之前创建的xmlEntity节点。
而当程序运行到cur->orig=orig时,目标位置指针被重置:
图14 字符串的传入
如图14所示,此时的compission字段被置为orig指针,存储着可控的对象。而当对此节点进行namespace比较时,节点会类型转换为xmlNode类型,而xmlEntity->orig字符串字段则会被当成xmlNs类型来解释,最终达到任意地址比较的操作。
图15 程序崩溃时的状态
如图15所示,程序最终在字符串比较时发生段错误,此时的栈中xsltParseTemplateContext的第二个参数,正是为我们在xmlParseEntityDecl停下时,myDoc所指向的地址。
1.向内存中填充330页的写有namespace信息的页。通过大量的数据填充,在触发漏洞时就有更大的可能匹配成功,只有匹配成功,才能进一步的地址泄漏。如果触发漏洞时的addr值指向未被分配的页,则会出现段错误导致程序崩溃。
图16 内存填充
图17 填充namespace信息
2.通过漏洞,可以在xsl对象生成时,修改href指针的值(详见图6),将地址改为一个在填充过程中,有很大几率会被填充到的地址。
图18 漏洞利用代码1
图19 漏洞利用代码2
3.函数内部会自动对xsl对象的namespace指针进行匹配,以确定xsl对象是否为一个合法的xsl元素。如果为非法元素,则会被javascript捕获到异常。
图20 javascript可获取的异常状态
4.若进入if,则字符串匹配成功,之后进入二分法查找具体页的过程,addr为触发漏洞时所写入的地址。若进入else,将猜测的地址addr进行变化,再次进行比较过程(此函数名即为find_spray_addr进入else后,则会迭代查找地址)。
5.二分法查找:
a.将填充的330页数据的前一半(165页)数据进行清空。 b.再次调用漏洞,判断是否发生异常,若异常,则addr所指向的地址在前一半填充的页中。否则,addr所指向的地址在后一半填充的页中。 c.继续对前一半或后一半进行a、b两步,直到仅剩一页时,则可判定addr指向的是此页中的地址。
图21 二分法查找addr所在页的代码1
图22 二分法查找addr所在页的代码2
6.通过获取页信息,即可得到一个泄露的内存地址addr以及周围4mb的可控内存,达到本阶段的漏洞利用目的。
本文由:3020337181@qq.com和3352444864@qq.com合作完成