- 项目地址:https://github.com/googleprojectzero/fuzzilli (Samuel Groß)
- pdf参考 https://saelo.github.io/presentations/offensivecon_19_fuzzilli.pdf
PPT 资料
- Motivation
- 发现js引擎中的漏洞,比如JIT
- Requirements
- 提供的样本算法有效
语法正确性
- 基于语法的fuzz很容易实现,比如domato
- 基本思想:将js当作上下文无关文法语言,然后应用生成规则变异
- 如下例子就是基于语法生成的fuzzer生成的样本
...;
var v4 = new Array(42, v3, “foobar”);
for (var v5 = 0; v5 < 1000; v5++) {
v4 = v5 * 7;
var v6 = v4.slice(v1, v1, v2);
}
...;
- 但是以上例子可能出现异常 Exception: TypeError: v4.slice is not a function.,导致后边的语句永远不会被执行。
- 加入 try-catch? 但是会导致在JIT执行代码的时候和原有代码有很大出路
高度语义正确性
- 相对语法正确来说更难
- 多个解决方案
- 生成代码阶段执行精确的类型检查
- 一步一步生成,每一步都检测有效性
- 使用变异方法,只保留语料库中语义有效的样本
- 定义js代码的合理变异
变异程序
- 变异可能是在不同的level上进行的:源代码、ast、bytecode
- 观测 程序的数据流和控制流变化的情况
FuzzIL
- 定义一种IR语言
- 观测程序的控制流和数据流
- 定义基于IL的变异
- 将IL转换为Js
变异:
- 操作变异
- 插入变异(生成新的代码)
- 输入变异
- 拼接变异(插入已有代码)
最小化样本
- 上述变异只会增大样本大小,在加入语料库前执行minim操作
- 最简单的做法就是一句一句删除,看删除到那一句会对最终结果造成影响
guided fuzzing
- feedback system
- 基于边覆盖
Architecture
- Fuzzer
- Corpus
- Opt module
- Mutator
- Lifter
- Minimizer
- FeedBack
对于每一个target对应一个fuzzer实例,通过IPC或者network同步,程序可以从另一个实例导入,并与本地语料库进行比较
- 同步
- Master----(IPC、network、task queue)--- worker、worker
A fuzzer instance (implemented in Fuzzer.swift) is made up of the following central components:
- FuzzerCore: produces new programs from existing ones by applying mutations. Afterwards executes the produced samples and evaluates them.
- ScriptRunner: executes programs of the target language.
- Corpus: stores interesting samples and supplies them to the core fuzzer.
- Environment: has knowledge of the runtime environment, e.g. the available builtins, property names, and methods.
- Minimizer: minimizes crashing and interesting programs.
- Evaluator: evaluates whether a sample is interesting according to some metric, e.g. code coverage.
- Lifter: translates a FuzzIL program to the target language (JavaScript).
Furthermore, a number of modules are optionally available:
- Statistics: gathers various pieces of statistical information.
- NetworkWorker/NetworkMaster: synchronize multiple instances over the network.
- ThreadWorker/ThreadMaster: synchronize multiple instances within the same process.
- Storage: stores crashing programs to disk.
The fuzzer is event-driven, with most of the interactions between different classes happening through events. Events are dispatched e.g. as a result of a crash or an interesting program being found, a new program being executed, a log message being generated and so on. See Events.swift for the full list of events. The event mechanism effectively decouples the various components of the fuzzer and makes it easy to implement additional modules.
A FuzzIL program can be built up using a ProgramBuilder instance. A ProgramBuilder provides methods to create and append new instructions, append instructions from another program, retrieve existing variables, query the execution context at the current position (e.g. whether it is inside a loop), and more.
Scalability
There is one fuzzer instance per target process. This enables synchronous execution of programs and thereby simplifies the implementation of various algorithms such as consecutive mutations and minimization. Moreover, it avoids the need to implement thread-safe access to internal state, e.g. the corpus. Each fuzzer instance has its own dedicated OperationQueue, conceptually corresponding to a single thread. Every interaction with a fuzzer instance must then happen on the instance’s queue. This guarantees thread-safety as the queue is serial. For more details see the docs.
To scale, fuzzer instances can become workers, in which case they report newly found interesting samples and crashes to a master instance. In turn, the master instances also synchronize their corpus with the workers. Communication between masters and workers can happen in different ways, each implemented as a module:
- Inter-thread communication: synchronize instances in the same process by enqueuing tasks to the other fuzzer’s DispatchQueue.
- Inter-process communication (TODO): synchronize instances over an IPC channel.
- Inter-machine communication: synchronize instances over a simple TCP-based protocol.
This design allows the fuzzer to scale to many cores on a single machine as well as to many different machines. As one master instance can quickly become overloaded if too many workers send programs to it, it is also possible to configure multiple tiers of master instances, e.g. one master instance, 16 intermediate masters connected to the master, and 256 workers connected to the intermediate masters.
安装和运行
这里以V8为例,根据官方repo的readme依次执行即可,checkout到特定的版本,根据以下步骤
The basic steps to use this fuzzer are:
- Download the source code for one of the supported JavaScript engines (currently JavaScriptCore, Spidermonkey, and v8).
- Apply the corresponding patch from the Targets/ directory. Also see the README.md in that directory.
- Compile the engine with coverage instrumentation (requires clang >= 4.0) as described in the README.
- Compile the fuzzer:
swift build [-c release]
. - Run the fuzzer:
swift run [-c release] FuzzilliCli --profile=<profile> [other cli options] /path/to/jsshell
. See alsoswift run FuzzilliCli --help
.
最后跑起来的效果就是:
nevv@ubuntu:~/Browser/fuzz/fuzzilli-master$ .build/x86_64-unknown-linux/release/FuzzilliCli --profile=v8 ../../pwn/34c3_v9/v8/v8/v8/out/fuzzbuild/d8
[Cli] No filesystem storage configured, found crashes will be discarded!
[Coverage] Initialized, 560233 edges
[JavaScriptEnvironment] initialized static JS environment model
[Fuzzer] Initialized
[Fuzzer] Recommended timeout: at least 120ms. Current timeout: 250ms
[Fuzzer] Startup tests finished successfully
[Fuzzer] Let's go!
Fuzzer Statistics
-----------------
Total Samples: 430
Interesting Samples Found: 142
Valid Samples Found: 318
Corpus Size: 143
Success Rate: 73.95%
Timeout Rate: 0.47%
Crashes Found: 0
Timeouts Hit: 2
Coverage: 4.17%
Avg. program size: 73.97
Connected workers: 0
Execs / Second: 114.18
Total Execs: 7343
源码概览
目录结构
nevv@ubuntu:~/Browser/fuzz$ tree fuzzilli-master
fuzzilli-master
├── CONTRIBUTING.md
├── Docs
│ └── ProcessingModel.md
├── LICENSE
├── Misc
│ ├── enum_properties.js
│ ├── Forkserver
│ │ ├── server.c
│ │ └── tester.c
│ └── REPRL
│ └── tester.c
├── Package.swift
├── README.md
├── Sources
│ ├── Fuzzilli
│ │ ├── Configuration.swift
│ │ ├── Core
│ │ │ ├── CodeGenerators.swift
│ │ │ ├── Component.swift
│ │ │ ├── Corpus.swift
│ │ │ ├── Environment.swift
│ │ │ ├── Events.swift
│ │ │ ├── FuzzerCore.swift
│ │ │ ├── JavaScriptEnvironment.swift
│ │ │ ├── Logging.swift
│ │ │ ├── ProgramBuilder.swift
│ │ │ └── Timers.swift
│ │ ├── Evaluation
│ │ │ ├── ProgramAspects.swift
│ │ │ ├── ProgramCoverageEvaluator.swift
│ │ │ └── ProgramEvaluator.swift
│ │ ├── Execution
│ │ │ ├── Execution.swift
│ │ │ ├── Forkserver.swift
│ │ │ ├── REPRL.swift
│ │ │ └── ScriptRunner.swift
│ │ ├── Fuzzer.swift
│ │ ├── FuzzIL
│ │ │ ├── AbstractInterpreter.swift
│ │ │ ├── Analyzer.swift
│ │ │ ├── Blocks.swift
│ │ │ ├── Instruction.swift
│ │ │ ├── Operations.swift
│ │ │ ├── Program.swift
│ │ │ ├── TypeSystem.swift
│ │ │ └── Variable.swift
│ │ ├── Lifting
│ │ │ ├── Expression.swift
│ │ │ ├── InliningPolicy.swift
│ │ │ ├── JavaScriptLifter.swift
│ │ │ ├── JSExpressions.swift
│ │ │ ├── Lifter.swift
│ │ │ └── ScriptWriter.swift
│ │ ├── Minimization
│ │ │ ├── BlockReducer.swift
│ │ │ ├── CallArgumentReducer.swift
│ │ │ ├── GenericInstructionReducer.swift
│ │ │ ├── InliningReducer.swift
│ │ │ ├── Minimizer.swift
│ │ │ ├── Reducer.swift
│ │ │ └── ReplaceReducer.swift
│ │ ├── Modules
│ │ │ ├── Module.swift
│ │ │ ├── NetworkSync.swift
│ │ │ ├── Statistics.swift
│ │ │ ├── Storage.swift
│ │ │ └── ThreadSync.swift
│ │ ├── Mutators
│ │ │ ├── BaseInstructionMutator.swift
│ │ │ ├── CombineMutator.swift
│ │ │ ├── ConcatMutator.swift
│ │ │ ├── GrowMutator.swift
│ │ │ ├── InputMutator.swift
│ │ │ ├── InsertionMutator.swift
│ │ │ ├── JITStressMutator.swift
│ │ │ ├── Mutator.swift
│ │ │ ├── OperationMutator.swift
│ │ │ └── SpliceMutator.swift
│ │ └── Util
│ │ ├── CInterop.swift
│ │ ├── Error.swift
│ │ ├── Misc.swift
│ │ ├── MovingAverage.swift
│ │ ├── OperationSource.swift
│ │ ├── Random.swift
│ │ ├── RingBuffer.swift
│ │ ├── VariableMap.swift
│ │ ├── VariableSet.swift
│ │ └── WeightedList.swift
│ ├── FuzzilliCli
│ │ ├── Arguments.swift
│ │ ├── main.swift
│ │ ├── Profiles
│ │ │ ├── JSCProfile.swift
│ │ │ ├── Profile.swift
│ │ │ ├── SpidermonkeyProfile.swift
│ │ │ └── V8Profile.swift
│ │ ├── Settings.swift
│ │ └── TerminalUI.swift
│ ├── libcoverage
│ │ ├── coverage.c
│ │ └── include
│ │ └── libcoverage.h
│ ├── libforkserver
│ │ ├── forkserver.c
│ │ └── include
│ │ └── libforkserver.h
│ ├── libreprl
│ │ ├── include
│ │ │ └── libreprl.h
│ │ └── libreprl.c
│ └── libsocket
│ ├── include
│ │ └── libsocket.h
│ └── socket.c
├── Targets
│ ├── JavaScriptCore
│ │ ├── fuzzbuild.sh
│ │ ├── README.md
│ │ └── webkit.patch
│ ├── Spidermonkey
│ │ ├── firefox.patch
│ │ ├── fuzzbuild.sh
│ │ └── README.md
│ └── V8
│ ├── fuzzbuild.sh
│ ├── README.md
│ └── v8.patch
└── Tests
├── FuzzilliTests
│ ├── CodeGenerationTest.swift
│ ├── InliningTest.swift
│ ├── InterpreterTest.swift
│ ├── MockFuzzer.swift
│ ├── ProgramSerializationTest.swift
│ ├── RingBufferTest.swift
│ ├── TestUtils.swift
│ ├── TypeSystemTest.swift
│ ├── VariableMapTest.swift
│ ├── VariableSetTest.swift
│ └── XCTestManifests.swift
└── LinuxMain.swift
31 directories, 111 files
FuzzilliCli
- Fuzzilli的命令行工具,主要功能是:
- 解析命令行参数并配置fuzz
- 设置终端的UI (TerminalUI.swift)
- 设置不同js引擎的profile(JSC、spidermonkey、V8)
│ ├── FuzzilliCli
│ │ ├── Arguments.swift
│ │ ├── main.swift
│ │ ├── Profiles
│ │ │ ├── JSCProfile.swift
│ │ │ ├── Profile.swift
│ │ │ ├── SpidermonkeyProfile.swift
│ │ │ └── V8Profile.swift
│ │ ├── Settings.swift
│ │ └── TerminalUI.swift
profiles
在profile文件中会定义 processArguments 和 codePrefix 、codeSuffix,比如v8:
let v8Profile = Profile(
processArguments: ["--debug-code",
"--expose-gc",
"--single-threaded",
"--predictable",
"--allow-natives-syntax",
"--interrupt-budget=1024",
"--fuzzing",
"--reprl"],
processEnv: [:],
codePrefix: """
function main() {
""",
codeSuffix: """
}
%NeverOptimizeFunction(main);
main();
""",
crashTests: ["fuzzilli('FUZZILLI_CRASH', 0)", "fuzzilli('FUZZILLI_CRASH', 1)", "fuzzilli('FUZZILLI_CRASH', 2)"],
additionalCodeGenerators: WeightedList<CodeGenerator>([
(ForceV8TurbofanGenerator, 10),
]),
additionalBuiltins: [
"gc" : .function([] => .undefined),
]
)
main
- 初始化 REPRL
- 初始化 Mutator,这里多插入了InsertionMutator,因为它更易产生无效样本
- 初始化 FuzzerCore
- 初始化 codeGenerators 负责代码生成 (defaultCodeGenerators + profile.additionalCodeGenerators,在V8中是ForceV8TurbofanGenerator)
- 初始化 ProgramCoverageEvaluator 计算覆盖率
- 初始化 JavaScriptEnvironment,能够加载可用的builtins函数
- 初始化 JavaScriptLifter ,能够将FuzzIL转换成js代码
- 初始化 Corpus,语料库,设置样本最大/最小值
- 初始化 Minimizer ,minimize crashes and interesting programs.
- 构造 Fuzzer 实例
- 初始化 TerminalUI
Settings
- defaultCodeGenerators 不同code生成器所占的权重
- 权重计算? 优化?
(IntegerLiteralGenerator, 2),
(FloatLiteralGenerator, 1),
(StringLiteralGenerator, 1),
(BooleanLiteralGenerator, 1),
(UndefinedValueGenerator, 1),
(NullValueGenerator, 1),
(BuiltinGenerator, 5),
(BuiltinGenerator, 5),
(ObjectLiteralGenerator, 10),
(ArrayLiteralGenerator, 10),
(ObjectLiteralWithSpreadGenerator, 5),
(ArrayLiteralWithSpreadGenerator, 5),
(FunctionDefinitionGenerator, 15),
(FunctionReturnGenerator, 3),
Fuzzilli
Fuzzer
- 定义了Fuzzer的类,其中包括
- Fuzzer的设置
- 能够处理的event
- FuzzerCore执行fuzz
- 语料库corpus
- 最小化执行用例的Minimizer等等
- start
// runFor是函数标签 提高代码可读性。_ 参数前缀代表调用该函数的时候可以不加该参数的名字
public func start(runFor maxIterations: Int) {
precondition(isInitialized)
assert(OperationQueue.current == queue)
self.maxIterations = maxIterations
self.runStartupTests()
// 测试是不是都正确的配置
logger.info("Let's go!")
// Start fuzzing
queue.addOperation {
self.fuzzOne()
}
}
- fuzzOne
/// Performs one round of fuzzing.
private func fuzzOne() {
guard maxIterations == -1 || iterations < maxIterations else {
return
}
iterations += 1
core.fuzzOne()
// Fuzz another one.
// We only want do actual fuzzing if there is nothing else to do. For that
// reason we enqueue the corresponding operations with low priority.
let op = BlockOperation(block: self.fuzzOne)
op.queuePriority = .veryLow
queue.addOperation(op)
}
- execute
- 提升FuzzIL,并通过runner来执行
/// Executes a program.
///
/// This will first lift the given FuzzIL program to the target language, then use the configured script runner to execute it.
///
/// - Parameters:
/// - program: The FuzzIL program to execute.
/// - timeout: The timeout after which to abort execution. If nil, the default timeout of this fuzzer will be used.
/// - Returns: An Execution structure representing the execution outcome.
public func execute(_ program: Program, withTimeout timeout: UInt32? = nil) -> Execution {
assert(runner.isInitialized)
assert(OperationQueue.current == queue)
events.PreExecute.dispatch(with: program)
let script: String
if config.speedTestMode {
script = lifter.lift(makeComplexProgram())
} else {
script = lifter.lift(program)
}
let execution = runner.run(script, withTimeout: timeout ?? config.timeout)
events.PostExecute.dispatch(with: execution)
return execution
}
FuzzerCore
Generating and executing programs.
programPrefixGenerators 生成一些基本的变量类型
fuzzOne:变异后调用fuzzer的execute执行样本。
JavaScriptEnvironment
- 定义了一些比较特殊的数字用来变异:比如smi的最大值、最大负数等
- 定义了一些特殊的jsTypeNames以及字符串
- 定义了js的不同数组类型比如jsTypedArrays、Uint16Array等
- 定义了一些buildin比如Object、ArrayBuffer、Boolean、DataView
ProgramBuilder
- 构造和附加程序中不同类型操作的随机实例。
- 记录了属性名和当前程序中出现的整数值。
- 用于当前程序的各种分析程序scopeAnalyzer和contextAnalyzer
Events
- Events模版类,可以在fuzzer中调度的所有事件的列表。
- observe 为当前event注册listener
- dispatchAsync 异步运行所有注册的listener
- dispatch 同步运行所有注册的listener
Evalueation
- ProgramCoverageEvaluator
- 计算当前边覆盖的百分比
- 将共享内存地址设置到环境变量中,每次执行前clear共享内存
Execution
- ExecutionOutcome 可能的结果
- Execution 类保存执行一个程序后的结果
- pid
- outcome
- termsig
- output
- execTime
Forkserver
- init 初始化执行时的环境变量,执行的参数、outfile路径
- run 获取执行后的结果
Fuzz 流程
- fuzzer 的 start() 函数开始执行fuzz
/// Starts the fuzzer and runs for the specified number of iterations.
///
/// This must be called after initializing the fuzzer.
/// Use -1 for maxIterations to run indefinitely.
public func start(runFor maxIterations: Int) {
precondition(isInitialized)
assert(OperationQueue.current == queue)
self.maxIterations = maxIterations
self.runStartupTests()
logger.info("Let's go!")
// Start fuzzing
queue.addOperation {
self.fuzzOne()
}
}
- 调用fuzzOne()
/// Performs one round of fuzzing.
private func fuzzOne() {
guard maxIterations == -1 || iterations < maxIterations else {
return
}
iterations += 1
core.fuzzOne()
// Fuzz another one.
// We only want do actual fuzzing if there is nothing else to do. For that
// reason we enqueue the corresponding operations with low priority.
let op = BlockOperation(block: self.fuzzOne)
op.queuePriority = .veryLow
queue.addOperation(op)
}
-
实际调用的是 Fuzzercore 下的 fuzzOne()
只要没有runtime异常,就会多次变异
/// Perform one round of fuzzing.
///
/// High-level fuzzing algorithm:
///
/// let parent = pickSampleFromCorpus()
/// repeat N times:
/// let current = mutate(parent)
/// execute(current)
/// if current produced crashed:
/// output current
/// elif current resulted in a runtime exception or a time out:
/// // do nothing
/// elif current produced new, interesting behaviour:
/// minimize and add to corpus
/// else
/// parent = current
///
///
/// This ensures that samples will be mutated multiple times as long
/// as the intermediate results do not cause a runtime exception.
- 首先会根据numConsecutiveMutations(运行时指定,默认是5),对样本连续变异
- 随后通知fuzzer program生成完毕
- fuzzer 调用 execute 执行,根据返回结果判断成功、crash、超时或者失败
Mutate
- 对于单一指令的类都继承自 BaseInstructionMutator,这个类中有两个mutate方法,一个是针对整个程序的,另外一个是变异单一statement
public func mutate(_ program: Program, for fuzzer: Fuzzer) -> Program? {
beginMutation(of: program)
var candidates = [Int]()
for instr in program {
if canMutate(instr) {
candidates.append(instr.index)
}
}
guard candidates.count > 0 else {
return nil
}
var toMutate = Set<Int>()
for _ in 0..<Int.random(in: 1...maxSimultaneousMutations) {
toMutate.insert(chooseUniform(from: candidates))
}
let b = fuzzer.makeBuilder()
b.adopting(from: program) {
for instr in program {
if toMutate.contains(instr.index) {
mutate(instr, b)
} else {
b.adopt(instr)
}
}
}
return b.finish()
}
具体来说,主要有以下几类Mutator:
-
CombineMutator
- A mutator that inserts a program in full into another one.
-
ConcatMutator
- A mutator that concatenates two programs together.
-
GrowMutator
- A mutator that inserts new instructions at the end of the program.
-
InputMutator
- A mutator that changes the input variables of instructions in a program.
-
InsertionMutator
- A mutator that inserts new code at random positions in a program.
-
JITStressMutator
- A mutator designed to call already JITed functions with different arguments or environment. In a way, this is a workaround for the fact that we don't have coverage feedback from JIT code.由于不能直接从JIT代码中获得覆盖率的反馈信息,因此使用不同参数和环境强制调用一些JIT后的函数。
-
OperationMutator
- A mutator that randomly mutates parameters of the Operations in the given program.
-
SpliceMutator
- A mutator that inserts a "slice" of a program at a random position in another program. A "slice" is defined as (not necessarily contiguous) sequence of instructions that define all used variables.