从零开始学Fuzzing系列:带领nduja突破Grinder的壁垒_html/css_WEB-ITnose

文章作者:walkerfuz

0×00 写在前面

站在巨人的肩膀上,才能看的更远,开发项目亦是如此。

四年前开源的Grinder项目,和借助于它运行的nduja,着实让浏览器漏洞挖掘飞入了寻常百姓家。但随着时间的考验,Grinder也遇到了让人爱恨交加的尴尬:明明产生了Crash,可就是无法重现。有多少人和我一样,从初识Grinder的激动,到分离时的落寞,也见证了一代怀揣梦想的挖洞人的足迹。

在现有项目的基础上,对其进行改进,乃是一种进步,于是出现了Morph项目。

Morph起初的定位就是解决Grinder架构中存在的本质问题:样本无法稳定重现,所以才有了前面《 浏览器挖掘框架Morph诞生记 》中讲述的“静态随机数组”的尝试,但随之带来的并发症却是:样本难以精简。

本文正是为解决这个问题而产生的,笔者采用一种Grinder log静态化的方式,将原本Grinder中采用DLL注入截获log语句的方式改进为提前生成静态精简样本的方式,从根本上解决样本无法稳定重现和难以精简两大难题,为浏览器漏洞挖掘工作提供新的思路。

0×01 我们需要什么格式的样本?

前面《 浏览器挖掘框架Morph诞生记 》中介绍过一种“静态随机数组”方法,用来解决Grinder log记录容易出现样本无法稳定重现的问题。笔者在开发出Morph v0.2.5之后进行了大规模部署测试,也得到了一些可以稳定重现的Crash结果,但拿到样本想进一步分析时,却遇到了难题。得到的Crash样本是如下形式的HTML文档:

<script type='text/javascript'>var mor_array = [675, 142, 861, 226, 112, 157, 667, ...... 147, 368, 10, 1];//元素个数有可能上万个var mor_index = 0 ;// Pick a random number between 0 and Xfunction rand( x ){  index = (mor_index++) % (mor_array.length);  return mor_array[index] % x;}function R(mod) {  return rand(10000) % mod;}......function tweakattributes(elem,i){  for( var p in elem){//这里的循环要依次调用上面的mor_array数组中的元素    try {        r=rand_item(interesting_vals);        elem.setAttribute(p,r);    }    catch (exception) {}  }}......function buildElementsTree(){    elementTree=[];    for (k=0;k<200;k++){//这里的循环要依次调用上面的mor_array数组中的元素      r=rand_item( elements );      elementTree[k]=document.createElement(r);      elementTree[k].id="el"+k;      rb=R(document.all.length);      document.all[rb].appendChild(elementTree[k]);      tweakattributes(elementTree[k],k);    }  }}function morph_fuzz(){  buildElementsTree();  ......  }</script><body onl oad="morph_fuzz()">

要想在上述样本中定位到是哪个js语句最终导致crash,必须依次读取静态数组,一步步调试执行buildElementsTree和tweakattributes函数中的for循环,拆解得到相关js语句。而且该语句 有可能 与之前循环的某个语句还有联系,必须将两者或更多的语句都定位出来才能得到完整的POC样本。

显而易见,如此分析起来,是极其繁琐的。用一句话形容这些样本: 食之无味,弃之可惜

那我们到底需要什么格式的样本呢?搞过浏览器漏洞分析的人员都知道,平常分析的POC都是这样的:

<script> function exploit(){ var e0 = null; var e1 = null; var e2 = null;  try {  e0 = document.getElementById("a");  e1 = document.createElement("div");  e2 = document.createElement("q");  e1.applyElement(e2);  e1.appendChild(document.createElement('button'));  e1.applyElement(e0);  e2.innerHTML = "";  e2.appendChild(document.createElement('body')); }catch(e){ } CollectGarbage(); } </script> <body onl oad="exploit()">

稍微跟踪调试即可确定crash产生的原因。另外,用Grinder成功重现出Crash样本的童鞋也知道,得到的POC样本通常都是这种格式:

<body><script>var createdObjects={}var c = document.createElement("CANVAS")c.width = 1000c.height = 1000document.body.appendChild(c)var img = new Image()img.src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAANlBMVEX///+6v8a2u8PKztPAxcu7wMf4+fm0ucH =="try{ ctx = c.getContext("2d")} catch(e){}try{ HTML0= document.createElement("MENU")} catch(e){}try{ createdObjects["HTML0"]=HTML0} catch(e){}try{ document.body.appendChild(HTML0)} catch(e){}try{ P0= new Path2D()} catch(e){}try{ createdObjects["P0"]=P0} catch(e){}try{ ctx.height="36191.05884594913180334528604"} catch(e){}try{ delete createdObjects['HTML0']} catch(e){}try{ ctx.translate(-0.9872812044341117,0)} catch(e){}try{ ctx.getLineDash()} catch(e){}try{ CollectGarbage()} catch(e){}try{ ctx.scale(-1435178373,-58)} catch(e){}try{window.location.reload(true);}catch(e){}</script>

上述样本采用二分法即可确定是哪些语句造成了浏览器崩溃。总之, 像上述两种没有循环,“一条大路走到底”格式的样本,才是我们期望得到的

0×02 Grinder log静态化的尝试

nduja本质上是一种Fuzzing策略,它制定了一系列新建、修改、删除DOM元素的规则。通过生成随机数的方式,产生不同的样本来测试浏览器是否产生异常。而Grinder则是提供了启动并监控浏览器进程、打开或记录异常样本等功能,将nduja承载起来的Fuzzing平台。

Grinder log的精髓在于,它能够通过Dll注入的方式,将在时间上顺序执行的js语句记录下来,但由于涉及到EventListener函数的调用,因此testcase.py脚本的自动化重现,在某些时候是不可行的。

既然上述Grinder log动态化记录js语句的方式是不可靠的,那如果按照log记录的模式,以同样的逻辑,提前生成静态样本再传递给浏览器进程加载测试,是否会解决样本难以精简的问题呢?

这种思路,我们称之为Grinder log静态化。简单来说,之前在grinder平台中使用的nduja样本,增加log语句后是这样的:

<script type='text/javascript'>......function tweakattributes(elem,i){  for( var p in elem){     try {	  r=rand_item(interesting_vals);	  logger.log("elementTree["+i+"]."+p+"="+r+";", "ndujaL", 1);          elem.setAttribute(p,r);    }catch(exception) {}  }}......function buildElementsTree(){    elementTree=[];    for (k=0;k<200;k++){       r=rand_item( elements );      logger.log("elementTree["+k+"]=document.createElement('"+r+"');","ndujaL",1);      elementTree[k]=document.createElement(r);      logger.log("elementTree["+k+"].id='el"+k+"';","ndujaL",1);      elementTree[k].id="el"+k;      rb=R(document.all.length);      logger.log( "document.all["+rb+"].appendChild(elementTree["+k+"]);", "ndujaL", 1 );      document.all[rb].appendChild(elementTree[k]);      tweakattributes(elementTree[k],k);    }  }}function morph_fuzz(){  buildElementsTree();  ......  }</script><body  onl oad="morph_fuzz()">

只有在动态执行过程中,才能通过logger.log语句将后续执行的js语句记录下来,最后通过testcase.py将其恢复的html样本(补充完整后)类似于以下形式:

<script type='text/javascript'>elementTree=[];......try{elementTree[3]=document.createElement("button");}catch(exception) {}try{ elementTree[3].id="el"+"3"; }catch(exception) {}try{ document.all[5].appendChild(elementTree[k]); }catch(exception) {}......elementTree[3].setAttribute ("edition","first");elementTree[3].setAttribute("title", 0x41414141414141);......</script>

Grinder log静态化就是,提前采用编程语言(Python)静态生成上述逻辑的样本:

class JsGenCls():    # adjust    def trys(self, case):        return "try{%s}catch(e){}\n" % case    # Random    def randb(self):        return r.choice(["true", "false"])		    def create_element_append_child(self):        ret = ""        ret += self.trys("%s = document.createElement('%s');" % (self.newElem(), self.randTag()))        ret += self.trys("%s.id = '%s';" % (self.newElem(), self.newElem()))        ret += self.trys("%s.appendChild(%s);" % (self.randDoc(),  self.newElem()))        self.elements.append(self.newElem())        return ret		    def tweak_attributes(self, element):        ret = ""        for attribute in g.HTMLAttributes:            ret += self.trys("%s.setAttribute('%s',%s);" % (element, attribute, self.randInteresting()))        return ret		    def fuzz_nduja(self):        ret = ""        # 1. build element treee for nduja        # create element and append child        for i in range(g.MAX_ELEM):            ret += self.create_element_append_child()            # tweak attributes            ret += self.tweak_attributes(self.lastElem())        # boom	return ret		    def generate(self):        script = self.fuzz_nduja()        script += self.window_reload()        script = self.gen_tags("script", script)        head = "nduja_fuzzer\n"        body = self.gen_tags("body", script)        return head + body

只要调用JsGenCls.generate函数即可生成一段Html文档的字符串,生成最终效果如下:

nduja_fuzzer<body><script>try{Element0 = document.createElement('body');}catch(e){}try{Element0.id = 'Element0';}catch(e){}try{document.all[2].appendChild(Element0);}catch(e){}try{Element0.addEventListener('chargingchange', func0, false);}catch(e){}try{Element0.setAttribute('accesskey',true);}catch(e){}try{Element0.setAttribute('action','no');}catch(e){}try{Element0.setAttribute('aria-checked','controls');}catch(e){}try{Element0.setAttribute('aria-colcount',-7e6);}catch(e){}try{Element0.setAttribute('aria-colspan',0x80000000);}catch(e){}try{Element0.setAttribute('aria-flowto','ab');}catch(e){}try{Element0.ownerDocument();}catch(e){}try{Element0.document='ltr';}catch(e){}try{Element0.cloneNode='controls';}catch(e){}try{Element0.open=null;}catch(e){}try{Element0.close(-7e6);}catch(e){}</script>

将 上述 生成的精简样本传递给浏览器进程,让其加载测试即可。可以看出, Grinder log静态化的关键在于,将nduja的逻辑采用编程语言静态生成出来 。那nduja的逻辑是什么样的呢?

0×03 nduja的逻辑

nduja的重点放在对Html文档中DOM元素的新建、修改与删除和对其属性、样式的随机修改上,主要执行逻辑如下:

其中BuildElementTree函数主要逻辑是:

随机创建一系列DOM元素

随机将这些元素添加到文档树中的某个子节点位置

随机为某个元素的某些动作创建监听事件

随机修改元素的属性和样式等

执行逻辑如下:

之后的Initialize函数逻辑最为简单,只涉及到随机为某些元素的某些动作添加监听事件:

最后Boom函数主要是从之前BuildElementTree函数生成的DOM元素树中,选择具有某些特征的元素组成的对象集合,下图中的NodeIterator对象、TreeWalker对象、TagAggregation、ElemRange对象、TxtRange对象都是采用不同的策略组成的DOM元素集合,然后通过调用AlterRange、MoveIterator、MoveTreeWalker、TagCrawler等方法随机修改、删除集合中的某些元素,以测试浏览器的解析情况:

上图中的Spray函数实现了数据的内存填充:

function spray(){  for(S="\u4545",k=[],y=0;y++<65;)    y<20?S+=S:k[y]=[S.substr(22)+"\u4545\u4545"].join("");	}

在nduja逻辑前两个函数中,AddEventListener监听事件指向了一个ModifyDOM自定义函数,它的逻辑主要是在某些Event事件信号产生时,随机创建DOM元素集合,然后随机修改、删除或添加子树:

从上面整个nduja的逻辑流程可以发现,它主要针对DOM元素,随机进行创建、修改和删除操作,所以能够发现很多释放后重用漏洞也就不足为奇了。仔细想想,这类漏洞在2010年左右逐渐兴起,而nduja的作者是在2012年前后开发的这款工具。不难猜测,nduja的作者肯定是当年在分析释放后重用漏洞时,发现了这样一种浏览器释放后重用漏洞的测试逻辑,所以才有了nduja的诞生。

《白帽子讲浏览器安全》中有一段关于nduja现状的描述:

这个框架(nduja)默认的Fuzz效果可能已经不明显了,虽然可以产生显著多的崩溃,但是其中几乎没有可利用的。笔者曾经进行了测试,结果表明,使用默认代码运行七天过程中,产生了数万个崩溃,经程序分类筛选后发现没有可以利用的,在修改框架之后即发现了可用漏洞,所以在现有框架上进行修改甚至于手动定义是很有必要的。

只要详细了解上述nduja的逻辑,然后在现有流程的基础上,加上自己的改进,相信必定有所收获。

0×04 Morph的架构与使用

目前Morph工具已经开发至v0.3.*版本,将nduja等Fuzzing逻辑作为modules模块的方式添加到工程当中。项目Github地址:

https://github.com/walkerfuz/morph

该工具的架构已经演变成morph.py、web.py和server.py三个松耦合模块:

morph.py:负责启动WEB服务器web.py、通过PyDbgEng3启动并监控浏览器进程、上传经过二次确认的异常样本等

web.py:结合modules模块负责生成静态样本并提供给浏览器进程

server.py:保存morph.py上传的样本结果

主要设计逻辑如下:

关于该框架的使用

假设存储漏洞结果的服务器为192.168.1.10,运行Morph漏洞挖掘任务的客户端为192.168.1.20。

1、首先将server目录拷贝至 192.168.1.10 服务器上,启动:

server -p 8080

浏览器访问[ http://192.168.1.10:8080/upload ]展示收集的漏洞样本结果列表:

2、然后将node目录拷贝至 192.168.1.20 客户端,运行Morph:

morph -b IE -m nduja_try -p 7890 -s 192.168.1.10:8080

当然客户端和服务端也可以同为一台机器,得到的结果存储在server下的upload目录。

关于modules的开发

目前可用的modules包括 nduja_rand、nduja_try、WebAPIs等。 自定义Fuzzing逻辑只需编写 对外提供可以生成静态样本的gen函数接口 的Python脚本即可。格式如下:

#! /user/bin/python# coding:UTF-8class JSTemplater():    def generate(self):        script = self.fuzz_nduja()        script += self.window_reload()        script = self.gen_tags("script", script)        head = "nduja_fuzzer\n"        body = self.gen_tags("body", script)        return head + bodydef gen():    js = JSTemplater()    return js.generate()

关于PyDbgEng3进程监控 器

这是一款专门针对Fuzzing测试优化的进程监控器, 项目 Github 地址:

https://github.com/walkerfuz/PyDbgEng3

主要特点包括:

可以得到目标进程异常时的crash详细信息

内置 !exploitable插件,能够判断漏洞是否可以利用

使用方法:

from PyDbgEng3 import Debuggerproc_args = b"C:/Program Files/Internet Explorer/iexplore.exe"crashInfo = Debugger.Run(proc_args, minorHash=True, mode=“M”/"S", trace=None)

该工具还针对某些多进程浏览器进行了优化设计,比如 Chrome浏览器。当多进程浏览器的某个子标签进程出现异常时,PyDbgEng3能够准确记录Crash现场的详细信息,并正确结束整个进程树。只需要将 参数 mode设置为“M”即可。

0×05 总结

本文主要讲述了如何解决浏览器Fuzzing过程中遇到的样本难以精简这一问题。可能读者看到最终的解决方法会豁然开朗,但笔者解决这个问题的过程却是十分坎坷的。希望能通过本文,和大家分享我解决问题的心路历程。

这里也讲述了nduja使用的Fuzzing策略,可以说,Fuzzing策略的研究对当前浏览器漏洞挖掘工作的开展十分必要。接下来准备和大家专门探讨一些笔者所用的浏览器Fuzzing策略,希望能抛砖引玉,吸引更多的人加入到讨论中来。

*原创作者:walkerfuz,本文属FreeBuf原创奖励计划文章,未经许可禁止转载

郑重声明:本文版权包含图片归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们(delete@yzlfxy.com)修改或删除,多谢。

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。

留言与评论(共有 0 条评论)
昵称:
匿名发表
   
验证码: