对FuzzILLI源码的REPRL.swift的粗略阅读
REPRL
1 | // A script runner to execute JavaScript code in an instrumented JS engine. |
这是makefuzzer函数的第一句,创建了一个runner,这个是拿来执行插桩过的js引擎
相关文件都位于Sources\Fuzzilli\Execution
Execution.swift and ScriptRunner.swift
这两个文件比较简单,Execution.swift定义了一个枚举ExecutionOutcome,表示程序运行可能出现的结果,crashed、failed、succeed还有一个timeout,同时也定义了Execution这个protocol,Execution用来表示一个program的执行结果
ScriptRunner.swift就定义了一个ScriptRunner的protocol,带有run函数和setEnvironmentVariable函数,这两个方法估计是要被后面的类重写
REPRL.swift
REPRL类继承ScriptRunner,先来看看它的init函数:
1 | public init(executable: String, processArguments: [String], processEnvironment: [String: String]) { |
init函数初始化它的processArguments和env成员,从makefuzzer函数中看出,processArguments和processEnvironment都是从profile来的:
1 | let runner = REPRL(executable: jsShellPath, processArguments: profile.processArguments, processEnvironment: profile.processEnv) |
顺路瞅一瞅v8的profile:
1 | let v8Profile = Profile( |
在来看看initialize函数:
1 | override func initialize() { |
接下来最重要的run函数:
1 | public func run(_ script: String, withTimeout timeout: UInt32) -> Execution { |
从上面的函数中来看,其实最主要的操作还是由libreprl进行的,所以我们还需要跟进去看看;其中对libreprl库函数的调用顺序为:reprl_create_context->reprl_initialize_context->reprl_execute->reprl_destroy_context
libreprl
libreprl位于Sources\libreprl,只有一个头文件和两个平台的具体实现文件,我们一般都在linux上搞fuzz,所以还是只看libreprl.h和libreprl-posix.c就好
头文件定义了reprl_create_context,reprl_initialize_context,reprl_destroy_context,reprl_execute这几个比较重要的函数;
其中他们都会用到的一个关键结构体reprl_context:
1 |
|
reprl_create_context
1 | // Well-known file descriptor numbers for reprl <-> child communication, child |
先把和子进程通信要用到的fd保留住,在分配所需空间
reprl_initialize_context
1 | int reprl_initialize_context(struct reprl_context* ctx, const char** argv, const char** envp, int capture_stdout, int capture_stderr) |
初始化reprl_context,其中比较关键的就是它调用了reprl_create_data_channel函数来初始化data_channel:
1 | static struct data_channel* reprl_create_data_channel(struct reprl_context* ctx) |
该函数先用memfd_create函数(memfd_create函数可以看man命令的解释)创建一个匿名文件,成功的话会返回这个文件的fd,在用ftruncate将其大小改为REPRL_MAX_DATA_SIZE,接着用mmap来申请内存,将fd和mmap申请的内存赋值给data_channel结构并返回。
capture_stdout和capture_stderr只有在用户设置了diagnostics标志才启用
reprl_execute
1 | int reprl_execute(struct reprl_context* ctx, const char* script, uint64_t script_length, uint64_t timeout, uint64_t* execution_time, int fresh_instance) |
其中一个reprl_spawn_child函数是用于启动子进程的,这个是关键函数,看看reprl是如何启动子进程的:
1 | static int reprl_spawn_child(struct reprl_context* ctx) |
总体下来就是调用pipe来启动和子进程间的通信(处理各种fd),接着父进程调用read在等待子进程的HELO message,在回应给子进程一个HELO message
V8_FUZZILLI
这是该回到子进程这边了,我这里选择的是v8,让我们来看看v8对fuzzilli的处理,在src/d8/d8.cc中:
1 |
|
在Shell::Initialize函数中:
1 | void Shell::Initialize(Isolate* isolate, D8Console* console, |
在这里会和父进程先进行HELO message的传递,SourceGroup::Execute函数会读取父进程的指令并开始执行js代码:
1 | bool SourceGroup::Execute(Isolate* isolate) { |
上面的[1],[2],[3]都对应了reprl_execute函数里的:
1 | // Copy the script to the data channel. |
最后v8会把代码执行的result通知给父进程:
1 | // Shell::Main |
在多看看v8在这里的逻辑:
1 | int Shell::Main(int argc, char* argv[]){ |
fuzzilli_reprl在宏V8_FUZZILLI定义了之后会被设置为true,所以这里就不停的接受fuzzer的”exec”指令,然后开始执行js代码,把result通知给父进程,在进行下一轮的等待
reprl_destroy_context
该函数就是释放前面申请的资源
参考链接
https://github.com/googleprojectzero/fuzzilli/blob/main/Docs/HowFuzzilliWorks.md