Home  >  Article  >  Web Front-end  >  从零开始学Fuzzing系列:带领nduja突破Grinder的壁垒_html/css_WEB-ITnose

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

WBOY
WBOYOriginal
2016-06-21 08:47:181093browse

文章作者: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文档:

<html><head><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></head><body onload="morph_fuzz()"></body></html>

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

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

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

<html><head><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> </head><body onload="exploit()"><form id="a"></form></body></html>

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

<html><body></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></html>

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

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语句后是这样的:

<html><head><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></head><body  onload="morph_fuzz()"></body></html>

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

<html><head><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></head></html>

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 = "<title>nduja_fuzzer</title>\n"        body = self.gen_tags("body", script)        return head + body

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

<title>nduja_fuzzer</title><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></body>

将 上述 生成的精简样本传递给浏览器进程,让其加载测试即可。可以看出, 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 = "<title>nduja_fuzzer</title>\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原创奖励计划文章,未经许可禁止转载

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn