0x00: 前言 之前学习过peach的使用,在willj师傅的指导下尝试去fuzz了某播放器,只是个尝试,并没有更深入去搞(本来的计划是结合winafl的),虽然没啥产出,但是fuzz过程还算清晰。过程中实现了自动化的fuzz脚本,有完整的功能,就是效率堪忧-。-
0x01: 关于peach peach 是一款优秀的文件格式fuzz工具。 peach是基于模板变异工作的,而且开源,文档虽然不是那么多,但是自己多摸索、学习还是可以学会一些基本的用法的。我在看使用peach做文件fuzz相关的资料的时候,最开始看的《0day2》里作者给的例子,从编写pit file到fuzz跑起来;之后看了一份国际友人写的Fuzzing with Peach – Part 1 « Flinkd! ,他这篇文章写的非常好,几乎涵盖了peach 90%的语法,仔细阅读,自己动手实践,会很快入门pit file的编写。
0x02: 文件fuzz的思路 fuzz嘛,简单的来看就是
构造输入
传给目标程序
程序状态检测(是否crash)
做log
之后根据你的log,把有用的样本拿出来在分析。 我的想法也很简单,就是利用peach基于我给的一个小文件,生成很多样本,然后写自动化的脚本去fuzz,并且做好异常检测的工作。 我的目标是adobe flash player sa版本,刚开始尝试就做一点简单的,选择flv文件作为fuzz的点。下面的问题就是:
flv文件格式
根据文件格式编写pit file
如何加载我的fuzz.flv文件?
异常检测怎么做
下面慢慢来分析。
flv文件格式flv
文件主要分为header
和body
两个部分。
header部分1 2 3 4 第1-3字节:文件标志,FLV的文件标志为固定的“FLV",字节(0x46, 0x4C,0x56),见上面的字节序和字符序两行; 第4字节:当前文件版本,固定为1(0x01) 第5字节:此字节当前用到的只有第6,8两个bit位,分别标志当前文件是否存在音频,视频。参见上面bit序,即是第5字节的内容; 第6-9字节:此4字节共同组成一个无符号32位整数(使用大头序),表示文件从FLV Header开始到Flv Body的字节数,当前版本固定为9(0x00,0x00,0x00,0x09)
body部分 这部分其实就是很多的tag的组合。 不过tag的种类有三种,分别是script、Audio、Video。每种tag的tag data又各不相同,详细的可以看一些文档了解。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ------------------------- | Previous Tag Size | ------------------------- | Tag | ------------------------- | Previous Tag Size | ------------------------- | Tag | ------------------------- | Previous Tag Size | ------------------------- | Tag | ------------------------- | Previous Tag Size | -------------------------
一些参考的文档flv文件格式详解 ,以及是官方的flv格式相关的文档都可以。
根据文件格式编写pit file 了解了文件格式之后就是编写pit file了,困难的地方可能就在于tag结构,因为数目不确定,而且相互之间有联系,比如某个bit为1或者0,影响着后面的某个结构的有无。 我的做法是,我使用类似Switch case这样的结构,让peach自己去判断选择对应的结构,即我写三种tag,然后限定最大的出现次数,因为样本很小,几百个最多了,然后peach根据模板文件的tag的标志,找到我pit file里对应的tag的结构,然后根据pit file里的结构进行变异,然后生成新的样本。 这里可以集合010 editor的二进制模板功能,可以对比你生成的样本是否正确,方便调试。
如何加载我的fuzz.flv文件? 有了样本,下面的问题就是怎么加载样本然后播放了。flash并不直接打开flv文件,而是使用swf来加载,所以我需要用as语言来编写一个swf来加载。这时候就有又一个问题:swf要编译的,即我的文件名会写死,这时候就麻烦了。 不过这个也好解决,我可以在后续的fuzz脚本中,每次单独复制一个样本到工作目录,然后重命名为swf要加载的文件的名字,然后起flash,加载swf,然后做后续的工作;完成之后,循环这个工作。这样就可以很好的解决这个问题了。
异常检测怎么做 我觉得最简单的办法就是调试器了,如果你进程崩了,你的just in time debugger会启动,问你要不要调试,你检测进程就可以了,然后做log,杀了所有进程,继续下一轮fuzz就好了。缺点很明显,这方法贼搓,而且效率低的要死,刚开始搞嘛,凑合用咯。
0x03: 编写pit file 首先是针对flv header的部分的编写
1 2 3 4 5 6 7 8 9 10 11 <DataModel name ="flvHeader" > <String name ="flv_Signature" value ="464C5601" valueType ="hex" token ="true" mutable ="false" /> <Flags name ="HeadFlags" size ="8" > <Flag name ="dummy" position ="3" size ="5" /> <Flag name ="audio" position ="2" size ="1" /> <Flag name ="dummy2" position ="1" size ="1" /> <Flag name ="video" position ="0" size ="1" /> </Flags > <Number name ="dataoffset" value ="9" size ="32" /> <Number name ="zero" size ="32" /> </DataModel >
这部分是script tag部分的编写
1 2 3 4 5 6 7 8 9 10 <Block name ="script" > <Number name ="type" size ="8" signed ="false" endian ="big" value ="18" token ="true" mutable ="false" /> <Number name ="datasize" size ="24" endian ="big" signed ="false" /> <Number name ="timestamp" size ="24" endian ="big" signed ="false" /> <Number name ="timestampi" size ="8" endian ="big" signed ="false" /> <Number name ="streamid" size ="24" endian ="big" signed ="false" /> <Number name ="firstbyte" size ="8" endian ="big" signed ="false" /> <Blob name ="data2" lengthType ="calc" length ="int(self.find('datasize').getInternalValue())-1" /> <Number name ="lastsize" size ="32" endian ="big" signed ="false" /> </Block >
这部分是audio tag部分的编写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <Block name ="audio" > <Number name ="type" size ="8" signed ="false" endian ="big" value ="8" token ="true" mutable ="false" /> <Number name ="datasize1" size ="24" endian ="big" signed ="false" /> <Number name ="timestamp" size ="24" endian ="big" signed ="false" /> <Number name ="timestampi" size ="8" endian ="big" signed ="false" /> <Number name ="streamid" size ="24" endian ="big" signed ="false" /> <Flags name ="Flag3" size ="8" > <Flag name ="fmt" position ="0" size ="4" /> <Flag name ="sr" position ="4" size ="2" /> <Flag name ="bits" position ="6" size ="1" /> <Flag name ="channels" position ="7" size ="1" /> </Flags > <Block > <Relation type ="when" when ="int(self.find('Flag3.fmt').getInternalValue()) == 10" /> <Blob name ="frmtype" length ="1" /> <Blob name ="data1" lengthType ="calc" length ="int(self.find('datasize1').getInternalValue())-2" /> </Block > <Block > <Relation type ="when" when ="int(self.find('Flag3.fmt').getInternalValue()) != 10" /> <Blob name ="data1" lengthType ="calc" length ="int(self.find('datasize1').getInternalValue())-1" /> </Block > <Number name ="lastsize" size ="32" endian ="big" signed ="false" /> </Block >
这部分是video tag的部分的编写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <Block name ="video" > <Number name ="type" size ="8" signed ="false" endian ="big" value ="9" token ="true" mutable ="false" /> <Number name ="datasize2" size ="24" endian ="big" signed ="false" /> <Number name ="timestamp" size ="24" endian ="big" signed ="false" /> <Number name ="timestampi" size ="8" endian ="big" signed ="false" /> <Number name ="streamid" size ="24" endian ="big" signed ="false" /> <Flags name ="Flag2" size ="8" > <Flag name ="frmtype" position ="0" size ="4" /> <Flag name ="codecid" position ="4" size ="4" /> </Flags > <Block > <Relation type ="when" when ="int(self.find('Flag2.codecid').getInternalValue()) == 7" /> <Blob name ="pkttype" length ="1" /> <Blob name ="compotime" length ="3" /> <Blob name ="data" lengthType ="calc" length ="int(self.find('datasize2').getInternalValue())-5" /> </Block > <Block > <Relation type ="when" when ="int(self.find('Flag2.codecid').getInternalValue()) != 7" /> <Blob name ="data" lengthType ="calc" length ="int(self.find('datasize2').getInternalValue())-1" /> </Block > <Number name ="lastsize" size ="32" endian ="big" signed ="false" /> </Block >
0x04: swf加载样本 利用as语言编写的代码,编译后得到swf文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 package { import flash.display.Sprite; import flash.net.*; import flash.media.*; import flash.utils.*; import flash.display.* import flash.events.*; import flash.system.fscommand; import flash.display3D.textures.VideoTexture; public class Main extends Sprite { public function Main () :void { var video:Video; var netCon:NetConnection; var stream:NetStream; function loadVideo (url:String) :Video { video = new Video(); netCon = new NetConnection(); netCon.connect(null ); stream = new NetStream(netCon); stream.play(url); var client:Object = new Object(); client.onMetaData = onMetaEvent; stream.client = client; stream.addEventListener(NetStatusEvent.NET_STATUS, netStatus); video.attachNetStream(stream); return video; } function onMetaEvent (e:Object) :void { } function netStatus (e:NetStatusEvent) :void { video.width = stage.stageWidth; video.height = stage.stageHeight; } stage.addChild(loadVideo("fuzz.flv" )); } } }
0x05: 自动化fuzz脚本 核心部分的代码如下。
1 2 3 4 5 6 7 def run (fileID ): copyFile(fileID) subprocess.Popen(runCmd) checkCrash() clean()
首先会拷贝一个样本文件到工作目录
1 2 def copyFile (fileID ): shutil.copyfile(fileDict.get(fileID),workDir+"fuzz.flv" )
然后开始一轮的fuzz
1 2 3 4 fuzzFilename = "fuzz.swf" programName = "flashplayer_22_sa_debug.exe" runCmd = programName +" " + fuzzFilename subprocess.Popen(runCmd)
然后是异常检测(贼搓的方法…TAT)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def checkCrash (): winDbg = "windbg.exe" try : processList = psutil.process_iter() except Exception as e: print e for p in processList: if (p.name == winDbg): print "[#]Crash Found! Writing to log now ..." log(fileID) sleep(1 ) p.kill() else : pass
最后就是收尾的工作了
1 2 3 4 5 def clean (): subprocess.Popen(killProgram) sleep(1 ) if (os.path.exists(workDir+"fuzz.flv" )): os.remove(workDir+"fuzz.flv" )
0x06: 结束语 我这个东西只能叫toy吧,效率低下,简单粗暴。但是过程中是学习到不少东西,之后的打算是多看一些论文,多学习一些漏洞挖掘的方法,之前尝试了结合winafl来搞,不过问题很多,有待解决…慢慢来吧。 所有的东西我都丢github了,有啥错误欢迎各位师傅留言/email指导我 传送门在这里:fuzz with peach
0x07: 参考 flv文件格式详解 peach 文档 Fuzzing with Peach – Part 1 « Flinkd!