ホームページ > 記事 > ウェブフロントエンド > ファジング シリーズをゼロから学ぶ: nduja を率いて Grinder の障壁を突破する_html/css_WEB-ITnose
記事著者: walkerfuz
巨人の肩の上に立って、さらに見ることでのみ、さらに先を見ることができます。これは開発プロジェクトにも当てはまります。
4 年前のオープンソースの Grinder プロジェクトとその上で実行される nduja は、実際に一般の人々の家庭にブラウザの脆弱性マイニングをもたらしました。しかし、時の試練とともに、Grinder は人々の好き嫌いを分ける厄介な問題にも遭遇しました。クラッシュは明らかに起こりましたが、それを再現することはできません。グラインダーと初めて会ったときの興奮から、別れたときの寂しさまで、夢を抱いた世代のディガーたちの足跡を目の当たりにしてきた、私と同じような人がどれほどいるでしょうか。
既存のプロジェクトをベースにして、それらを改善することは一種の進歩であるため、Morph プロジェクトが登場しました。
Morph の本来の位置づけは、サンプルが安定して再現できないという Grinder アーキテクチャに存在する本質的な問題を解決することでした。そのため、以前の「ブラウザ マイニング フレームワーク Morph の誕生」で説明した「静的ランダム配列」の試みは、 」が作成されましたが、それに伴う複雑さは、サンプルを合理化するのが難しいことです。
この記事は、この問題を解決するために、Grinder ログの静的方法を採用し、DLL インジェクションを使用して Grinder でログステートメントをインターセプトする独自の方法を、静的な簡略化されたサンプルを事前に生成する方法に改良しました。サンプルを安定して再現できないことと効率化が難しいという 2 つの大きな問題を根本的に解決し、ブラウザの脆弱性マイニングに新しいアイデアを提供します。
以前、「ブラウザ マイニング フレームワーク Morph の誕生」で、Grinder ログ レコードに安定して再現できないサンプルが発生しやすいという問題を解決するために、「静的ランダム配列」手法が導入されました。 Morph v0.2.5 の開発後、著者は大規模な展開テストを実施し、安定して再現できるクラッシュ結果をいくつか得ましたが、さらに分析するためにサンプルを入手したときに困難に遭遇しました。取得したクラッシュ サンプルは、次の形式の 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 ステートメントを特定するには、静的配列を順番に読み取り、デバッグして実行する必要があります。 buildElementsTree と tweakattributes のステップバイステップ 関数内の for ループが逆アセンブルされ、関連する js ステートメントが取得されます。さらに、このステートメントは前のループ内のステートメントに関連している可能性があります。完全な POC サンプルを取得するには、両方または複数のステートメントを見つける必要があります。
明らかに、このような分析は非常に面倒です。これらのサンプルを 1 つの文で説明します: 食べても味がないので、捨てるのは残念です。
それでは、どのような形式のサンプルが必要なのでしょうか?ブラウザーの脆弱性分析を行ったことがある人は、通常分析される 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>
クラッシュの原因は、少しの追跡とデバッグで特定できます。さらに、Grinder でクラッシュ サンプルを再現することに成功した子供たちは、取得した 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>
上記のサンプルでは、二分法を使用して、どのステートメントがブラウザにクラッシュを引き起こしたかを判断できます。崩壊する。つまり、ループのない サンプルと、上記の 2 つのような「最後まで一本道」形式のサンプルが として期待されるものです。
nduja は本質的にファジング戦略であり、DOM 要素を作成、変更、削除するための一連のルールを定式化します。 。乱数を生成することにより、ブラウザが異常を発生するかどうかをテストするためのさまざまなサンプルが生成されます。 Grinder は、ブラウザ プロセスの開始と監視、異常なサンプルを開いたり記録したりするなどの機能を提供することで、nduja をホストするファジング プラットフォームです。
Grinder ログの本質は、Dll インジェクションを通じて時間内に順次実行される js ステートメントを記録できることですが、EventListener 関数の呼び出しが含まれるため、testcase.py スクリプトの自動再現は行われません。いつかは実現可能。
上記の js ステートメントを動的に記録する Grinder ログ方法は信頼性が低いため、ログ記録モードに従い、同じロジックを使用して事前に静的サンプルを生成し、それを読み込みテストのためにブラウザー プロセスに渡すと、困難なサンプル削減の問題を解決するにはどうすればよいでしょうか?
このアイデアは、Grinder ログの静的化と呼ばれます。簡単に言うと、ログ ステートメントを追加する前と追加後、グラインダー プラットフォームで使用された nduja サンプルは次のようになります。
<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>
動的実行プロセス中のみ、その後に実行される js ステートメントは次のように処理されます。 logger.log ステートメントを記録し、最後に 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 ログの静的化には、プログラミング言語 ( 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 ログを静的にするための鍵は、プログラミング言語を使用して 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的逻辑,然后在现有流程的基础上,加上自己的改进,相信必定有所收获。
目前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”即可。
この記事では主に、ブラウザのファジング処理中にサンプルが単純化されにくい問題を解決する方法について説明します。おそらく読者は突然最終的な解決策を目にするでしょうが、この問題を解決する著者のプロセスは非常に困難でした。この記事を通じて、私が問題を解決してきた過程を皆さんと共有できれば幸いです。
nduja が使用するファジング戦略についてもここで説明します。ファジング戦略に関する研究は、現在のブラウザーの脆弱性マイニング作業に非常に必要であると言えます。次に、より多くの人が議論に参加することを期待して、著者が使用したいくつかのブラウザ ファジング戦略について説明します。
*原著者: walkerfuz、この記事はFreeBuf独自のリワードプログラム記事に属しており、許可なく転載することはできません