peach源码阅读
前言
接触peach这个框架也有一年多时间了,平时只是停留在使用层面,最近有一个论文思路需要在peach的源码上进行修改来验证,借此机会好好的看了两天peach源码。
网上关于peach源码介绍的资料基本没有,现做一个记录,权当抛砖引玉,希望能多多交流。
PS:此次peach源码版本为3.1.124
一. vs2010调试环境搭建
peach官方提供了源码编译方法(http://community.peachfuzzer.com/v3/Installation.html) 但是不能通过调试器进行调试,因此首先搭建了vs2010的调试环境
- 生成vs2010能打开的sln格式文件
waf.bat configure waf.bat msvs2010
- 使用vs2010打开sln文件,解决方案下会存在多个项目,需要重点需要关注两个项目:
- peach:整个框架的入口,调用peach.core.dll中的相关功能模块
- peach.core:实现peach核心功能。以dll的形式编译输出,因此不能直接调试该项目,需要通过peach项目对peach.core.dll的调用,来进行调试跟踪
-
在解决方案中设置启动项目顺序为“Single startup project”,并将最先启动项目名称设置为——peach
-
设置peach项目的命令行参数1为pit文件路径
- 完成以上设置,就可以下断点进行调试peach源码了,列一下vs2010调试的一些快捷键
- F9: 下断点
- F10:单步步过,执行一条指令
- F11:单步步入,执行一条指令
- F5: 执行到下一个断点
二. 处理流程分析
- peach – program.cs,此处是整个peach框架执行的起始位置,在此处通过调用,开始执行peach.core工程中的核心代码
40 public class Program { static int Main(string[] args) { Peach.Core.AssertWriter.Register(); return new Peach.Core.Runtime.Program(args).exitCode; } }
- 进入到peach.core —— Runtime —— program.cs,该模块会进行一些初始化操作,包括屏幕输出开始信息,检查用户提供的命令行参数是否合法,用户是否提供了额外的参数设置(包括agent、monitor、test,如果没有提供则从pit文件中进行读取)。
接下来会做两件事:一是初始化Engine(Engine是peach框架的一个引擎,控制着整个fuzz过程) ;二是对pit文件进行解析
284 Engine e = new Engine(GetUIWatcher()); dom = GetParser(e).asParser(parserArgs, extra[0]); config.pitFile = extra[0];
对pit文件的解析结果保存在一个叫dom的变量中,其中包括了pit文件中的dataModels、stateModels和tests的解析,dom变量在后续的过程将会被多次使用 相关初始化工作完成后,通过以下语句进入到Engine模块
298 e.startFuzzing(dom, config);
- 进入到peach.core —— Runtime —— Engine.cs,此模块做的工作如下:
- 检查dom,config,test等变量是否初始化完毕;
- 初始化变异策略;
- 初始化context类变量:context顾名思义是’‘上下文’‘的意思,该类变量中记录了与此次fuzz相关的各种信息,包括pit的解析信息、迭代信息、崩溃信息、控制信息等,是整个fuzz过程的“神经中枢”
将context作为参数,传入OnTestStarting函数,开始整个fuzz过程
341 OnTestStarting(context);
之前说了,Engine.cs控制着整个fuzz过程的进行,那么肯定是要实现循环和迭代的。在此通过一个大的while循环,在while循环中实现迭代
358 while ((firstRun || iterationCount <= iterationStop) && context.continueFuzzing) { ... 413 test.stateModel.Run(context); }
- 上图中test.stateModel.Run函数会继续调用StateModel模块,遂进入到peach.core —— Dom —— StateModel.cs。StateModel模块中会确定当前的State;通过updateToOriginalDataModel函数,使用datamodel对种子文件进行解析,从而完成对datamodel的初始化
171 foreach (State state in states) { state.UpdateToOriginalDataModel(); }
updateToOriginalDataModel函数调用peach.core —— Cracker —— DataCracker.cs,最终通过调用peach.core —— Data.cs 中的函数完成解析功能
64 try { DataCracker cracker = new DataCracker(); cracker.CrackData(model, new BitStream(File.OpenRead(FileName))); }
此时函数参数正是我们的datamodel和种子文件
完成对种子文件解析后,datamodel初始化便已经完成;接下来通过currentState.Run函数继续调用State模块对当前的State进行操作
182 try
{
currentState.Run(context);
break;
}
- 进入到peach.core —— Dom —— State.cs。State的构造函数会解析得到State中的action列表,保存在actions变量中
60 public State() { actions = new NamedCollection<Action>(); }
接着通过for循环,分别对于每一个action进行相应的操作。由上图所示,第二个action为output,即输出变异后的测试文件,其中会包含变异操作,所以重点关注第二次循环
201 foreach (Action action in actions) action.Run(context);
- 进入到peach.core —— Dom —— Action.cs,以下代码就是变异生成新的种子文件的全过程
345 OnStarting(); //对种子文件按照pit的规定进行变异 logger.Debug("ActionType.{0}", GetType().Name.ToString()); RunScript(onStart); foreach (var item in outputData) // 输出新的变异内容,并保存为文件 parent.parent.SaveData(item.outputName, item.dataModel.Value);
- 接下来我们继续跟踪如何对种子文件进行变异的。当执行上图Onstarting函数时,通过c#中的“委托机制”,实际上是调用了多个模块中的函数。了解这一机制在peach源码阅读过程中非常重要,在此以Onstarting函数为例进行简要说明
Action.cs : Onstarting函数会调用starting事件
203 protected virtual void OnStarting() { if (Starting != null) Starting(this); }
Watch.cs: 给Starting这一委托事件绑定了处理函数——Action_Starting
49 public void Initialize(Engine engine, RunContext context)
{
...
70 Core.Dom.Action.Starting += new
ActionStartingEventHandler(Action_Starting);
...
}
因此当执行Starting时,就会执行peach.core —— Dom —— file.cs和peach.core —— MutationStrategy —— RandomStrategy.cs模块中的Action_Starting函数
- 首先会进入peach.core —— Dom —— file.cs,在Action_Starting函数中会初始化变量rec
320 protected override void Action_Starting(Core.Dom.Action action) { var rec = new Fault.Action() { name = action.name, type = action.type, models = new List<Fault.Model>() }; foreach (var data in action.allData) { rec.models.Add(new Fault.Model() { name = data.dataModel.name, parameter = data.name ?? "", dataSet = data.selectedData != null ? data.selectedData.name : "", mutations = new List<Fault.Mutation>(), }); }
rec记录了此次变异依据的种子文件和参照的datamodel
- 随后进入到peach.core —— MutationStrategy —— RandomStrategy.cs,同样是调用名为Action_Starting的函数,其中存在一个if判断,如果_context.controlIteration变量为false则开始调用MutateDateaModel函数进行变异
247 else if (!_context.controlIteration) { MutateDataModel(action); }
此时参数action变量如下图所示 执行MutateDateModel函数,会调用ApplyMutation函数。其中outputData是一个迭代类型,通过for循环从中取数据进行ApplyMutation
407 foreach (var item in action.outputData) { ApplyMutation(item); }
此时item变量如下图所示: ApplyMutation函数会继续调用OnDataMutating函数
387 if (elem != null && elem.MutatedValue == null) { Mutator mutator = Random.Choice(item.Mutators); OnDataMutating(data, elem, mutator); logger.Debug("Action_Starting: Fuzzing: " + item.ElementName); logger.Debug("Action_Starting: Mutator: " + mutator.name); mutator.randomMutation(elem); }
OnDataMutating函数有3个参数:data,elem和mutator,指定了使用哪一个变异器对原始文件的哪一个模块进行变异
- OnDataMutating会通过委托机制,首先进入 peach.core —— Dom —— file.cs对第8步中提到的变量rec进行数据读取等操作
359 protected override void MutationStrategy_DataMutating(ActionData data, DataElement element, Mutator mutator) { var rec = states.Last().actions.Last(); var tgtName = data.dataModel.name; var tgtParam = data.name ?? ""; var tgtDataSet = data.selectedData != null ? data.selectedData.name : ""; var model = rec.models.Where(m => m.name == tgtName && m.parameter == tgtParam && m.dataSet == tgtDataSet).FirstOrDefault(); System.Diagnostics.Debug.Assert(model != null); model.mutations.Add(new Fault.Mutation() { element = element.fullName, mutator = mutator.name }); }
然后会根据mutator的选取,调用相应的变异模块。在此是进入到 peach.core —— Mutators —— ArrayReverseOrderMutator.cs
83 public override void randomMutation(DataElement obj) { performMutation(obj); obj.mutationFlags = MutateOverride.Default; }
obj的内容非常丰富,如下图所示
3. 后记
- 此时重点阅读的源码只是peach源码中初始化和变异的部分,对于agent、monitor、通信交互等模块均没有涉及
- 原本计划通过理解源码,将peach变异的模块分解出来方便日后其他框架的移植,但由于参数的数据结构太复杂,最终也没有能够成功,这一部分作为以后的工作来持续跟进
- 以上对于peach源码的理解还远远不够,由于能力有限,有说明有误的地方还请多多指正;也欢迎各位对peach源码有一定理解的朋友一起讨论,共同进步