就实践卡片笔记法来说,浮墨是一个比较理想的工具,「少即是多,多则惑」,去掉一切不需要的功能,重点才能突出。Logseq 的野心挺大,不想只做一个双链笔记本,还想做得更多,这就要求我们想清楚使用其的主要目的是什么。现实中并不存在一个 all in one 的理想工具,什么工具合适就可以使用什么工具,各位施主不能 「着相」了。
-i dir - input directory with test cases -o dir - output directory for fuzzer findings -t msec - timeout for each run
Instrumentation type:
-D dir - directory with DynamoRIO binaries (drrun, drconfig) -w winafl - Path to winafl.dll -P - use Intel PT tracing mode -Y - enable the static instrumentation mode
Execution control settings:
-f file - location read by the fuzzed program (stdin) -m limit - memory limit for the target process -p - persist DynamoRIO cache across target process restarts -c cpu - the CPU to run the fuzzed program
Fuzzing behavior settings:
-d - quick & dirty mode (skips deterministic steps) -n - fuzz without instrumentation (dumb mode) -x dir - optional fuzzer dictionary (see README)
Other stuff:
-I msec - timeout for process initialization and first run -T text - text banner to show on the screen -M \ -S id - distributed mode (see parallel_fuzzing.txt) -C - crash exploration mode (the peruvian rabbit thing) -e - expert mode to run WinAFL as a DynamoRIO tool -l path - a path to user-defined DLL for custom test cases processing -V - show version number and exit
Attach:
-A module - attach to the process that loaded the provided module
For additional tips, please consult afl_docs\README.
-covtype - the type of coverage being recorded. Supported options are bb (basic block, default) or edge.
-coverage_module - module for which to record coverage. Multiple module flags are supported.
-target_module - module which contains the target function to be fuzzed. Either -target_method or -target_offset need to be specified together with this option.
-target_method - name of the method to fuzz in persistent mode. For this to work either the method needs to be exported or the symbols for target_module need to be available. Otherwise use -target_offset instead.
-target_offset - offset of the method to fuzz from the start of the module.
-fuzz_iterations - Maximum number of iterations for the target function to run before restarting the target process.
-nargs - Number of arguments the fuzzed method takes. This is used to save/restore the arguments between runs.
-call_convention - The default calling convention is cdecl on 32-bit x86 platforms and Microsoft x64 for Visual Studio 64-bit applications. Possible values: * fastcall: fastcall * ms64: Microsoft x64 (Visual Studio) * stdcall: cdecl or stdcall * thiscall: thiscall
-debug - Debug mode. Does not try to connect to the server. Outputs a log file containing loaded modules, opened files and coverage information.
-logdir - specifies in which directory the log file will be written (only to be used with -debug).
-thread_coverage - If set, WinAFL will only collect coverage from a thread that executed the target function
Hypervisor was known as hard target to fuzz over several years. Even though, lots of prior pioneers( Peter Hlavaty, Chaitin Tech, StarLabs, Peleg Hadar and Ophir Harpaz and many others ) doing amazing work to overcome this limit and found interesting bugs.
But it is not an easy topic for some fresh newbie like me to starts from the bottom. I think manual code( or binary ) auditing and static analysis could be only considerable options if I start my research a few years ago without What The Fuzz.
What The Fuzz( a.k.a WTF ) is a snapshot-based fuzzer targeting Windows Ring-3 and Ring-0 component. WTF’s snapshot is based on memory dump both kernel and user-land. Because of that, WTF can not emulate any functionality which requires anything not within in-memory and can not create new process or thread because memory dump limited on single process. But WTF support breakpoint Handler which you can set breakpoint on any address within memory dump and if fuzz execution reaches that address, pre-defined breakpoint handler will be executed. Based on this, we can trying to overcome some limitation on WTF such as file access.
I really love WTF’s flexibility and its potential, and I am going to show one of example usage of WTF on targeting Virtualbox to prove how awesome it is.
SVGA is something like “JavaScript of hypervisors“. Because of its complex nature, many hypervisor bugs are oriented by SVGA. And that’s the reason why I choose it as a first target.
VirtualBox have a function called vmsvgaR3FifoLoop. It waits until guest submit new command data through GPA. So this is a good spot( or should I call it source? ) to take a snapshot.
I set a breakpoint on vmsvgaR3FifoLoop+4E2 to take snapshot. It is same position as here, start of switch-case routine for SVGA and SVGA3D command.
After creating snapshot, I had to decide make it run multiple times in single execution or not. Because SVGA is consist of various commands like define, update, delete something…, I thought fuzz campaign must handle multiple SVGA commands during single round.
First, I had to define structure to contains multiple testcases. Luckily, 0vercl0k already write nice example to doing that. So I did same thing as below.
Next question was, how can I insert data into snapshot? I had to find a insertion point and proper memory address. Luckily( again ), VirtualBox using a function called vmsvgaR3FifoGetCmdPayload to receive command data from guest to host. I define a breakpoint handler in Init() callback function as below.
As I said above, I create a snapshot on vmsvgaR3FifoLoop+4E2. So if I restore register context, next execution flow starts from there. Because of that, I had to parse new testcase using breakpoint Handler.
GlobalState.SvgaCmds.pop_front(); })) { fmt::print("Failed to Setbreakpoint on SvgaLoopStart\n"); returnfalse; }
After some investigation, I found CreateDeviceEx call in vmsvga3dContextDefine keep causing CR3 context switching and I didn’t found any way to handling it using breakpoint handler. So I just blacklisting it( SVGA_3D_CMD_CONTEXT_DEFINE = 1045 ).
I disclose almost every part of fuzz module except mutation part. I just use libfuzzer mutator to mutate body data of SVGA command and pick one of command in array randomly.
Aaaaannnd, this is everything you need to fuzz! I skip some formal code for brevity, but I think you can easily find what you need to define fully working fuzz module.
…..Ooooh wait, I forgot something. After some hours of struggle, I define a blacklist which cause CR3 context switching. I just put it on the end of Init() function.
boolSetupVBoxBlacklistHooks(){ if(!g_Backend->SetBreakpoint("VBoxRT!RTLogLoggerEx", [](Backend_t *Backend) { Backend->SimulateReturnFromFunction(0); })) { fmt::print("Failed to SetBreakpoint on VBoxRT!RTLogLoggerEx\n"); std::abort(); }
if(!g_Backend->SetBreakpoint("VBoxVMM!PDMCritSectLeave", [](Backend_t *Backend) { Backend->SimulateReturnFromFunction(0); })) { fmt::print("Failed to SetBreakpoint on VBoxVMM!PDMCritSectLeave\n"); std::abort(); }
if(!g_Backend->SetBreakpoint("VBoxC!util::AutoLockBase::release", [](Backend_t *Backend) { Backend->SimulateReturnFromFunction(0); })) { fmt::print("Failed to SetBreakpoint on VBoxC!util::AutoLockBase::release\n"); std::abort(); }
if(!g_Backend->SetBreakpoint("VBoxC!util::AutoLockBase::acquire", [](Backend_t *Backend) { Backend->SimulateReturnFromFunction(0); })) { fmt::print("Failed to SetBreakpoint on VBoxC!util::AutoLockBase::acquire\n"); std::abort(); }
if(!g_Backend->SetBreakpoint("VirtualBoxVM!UIFrameBufferPrivate::NotifyChange", [](Backend_t *Backend) { Backend->SimulateReturnFromFunction(0); })) { fmt::print("Failed to SetBreakpoint on VirtualBoxVM!UIFrameBufferPrivate::NotifyChange\n"); std::abort(); }
if(!g_Backend->SetBreakpoint("VBoxRT!SUPSemEventWaitNoResume", [](Backend_t *Backend) { Backend->SimulateReturnFromFunction(0); })) { fmt::print("Failed to SetBreakpoint on VBoxRT!SUPSemEventWaitNoResume\n"); std::abort(); }
if(!g_Backend->SetBreakpoint("d3d9!CBaseDevice::Release", [](Backend_t *Backend) { Backend->SimulateReturnFromFunction(0); })) { fmt::print("Failed to SetBreakpoint on d3d9!CBaseDevice::Release\n"); } if(!g_Backend->SetBreakpoint("d3d9!CD3DBase::Clear", [](Backend_t *Backend) { Backend->SimulateReturnFromFunction(0); })) { fmt::print("Failed to SetBreakpoint on d3d9!CD3DBase::Clear\n"); } if(!g_Backend->SetBreakpoint("d3d9!CMipMap::SetAutoGenFilterType", [](Backend_t *Backend) { Backend->SimulateReturnFromFunction(0); })) { fmt::print("Failed to SetBreakpoint on d3d9!CMipMap::SetAutoGenFilterType\n"); } if(!g_Backend->SetBreakpoint("d3d9!CMipMap::GenerateMipSubLevels", [](Backend_t *Backend) { Backend->SimulateReturnFromFunction(0); })) { fmt::print("Failed to SetBreakpoint on d3d9!CMipMap::GenerateMipSubLevels\n"); }
if(!g_Backend->SetBreakpoint("USER32!GetDC", [](Backend_t *Backend) { Backend->SimulateReturnFromFunction(0x1337); })) { fmt::print("Failed to SetBreakpoint on USER32!GetDC\n"); }
returntrue; }
Yep. That’s really everything. I use this fuzz module several hours and it founds interesting crash.
3. Vulnerability( TL;DR )
Crash occurred in vmsvga3dSurfaceCopy function( PageHeap needed ).
This function trying to copy surface data from one to another using surface id and there’s no boundary check between surface, so it become exploitable wildcopy vulnerability in heap memory.
This vulnerability patched in 6.1.36 release at July, 2022.
4. Conclusion
I think importance of snapshot fuzzing is, it makes researcher to focus on target itself.
Unlike other fuzzers based on runtime and DBI are often create (very) unreasonable side effect or need lots of time to create working harness. The concept of snapshot fuzzing makes it possible to reduce this waste of time.
xorg-x11-drv-nvidia.x86_64 : NVIDIA's proprietary display driver for NVIDIA graphic cards =========================================================================== 名称 和 概况 匹配:xorg-x11-drv-nvidia xorg-x11-drv-nvidia-340xx-cuda.x86_64 : CUDA libraries for xorg-x11-drv-nvidia-340xx xorg-x11-drv-nvidia-340xx-devel.i686 : Development files for xorg-x11-drv-nvidia-340xx xorg-x11-drv-nvidia-340xx-devel.x86_64 : Development files for xorg-x11-drv-nvidia-340xx xorg-x11-drv-nvidia-340xx-kmodsrc.x86_64 : xorg-x11-drv-nvidia-340xx kernel module source code xorg-x11-drv-nvidia-340xx-libs.i686 : Libraries for xorg-x11-drv-nvidia-340xx xorg-x11-drv-nvidia-340xx-libs.x86_64 : Libraries for xorg-x11-drv-nvidia-340xx xorg-x11-drv-nvidia-390xx-cuda.x86_64 : CUDA driver for xorg-x11-drv-nvidia-390xx xorg-x11-drv-nvidia-390xx-cuda-libs.i686 : CUDA libraries for xorg-x11-drv-nvidia-390xx xorg-x11-drv-nvidia-390xx-cuda-libs.x86_64 : CUDA libraries for xorg-x11-drv-nvidia-390xx xorg-x11-drv-nvidia-390xx-devel.i686 : Development files for xorg-x11-drv-nvidia-390xx xorg-x11-drv-nvidia-390xx-devel.x86_64 : Development files for xorg-x11-drv-nvidia-390xx xorg-x11-drv-nvidia-390xx-kmodsrc.x86_64 : xorg-x11-drv-nvidia-390xx kernel module source code xorg-x11-drv-nvidia-390xx-libs.i686 : Libraries for xorg-x11-drv-nvidia-390xx xorg-x11-drv-nvidia-390xx-libs.x86_64 : Libraries for xorg-x11-drv-nvidia-390xx xorg-x11-drv-nvidia-470xx-cuda.x86_64 : CUDA driver for xorg-x11-drv-nvidia-470xx xorg-x11-drv-nvidia-470xx-cuda-libs.i686 : CUDA libraries for xorg-x11-drv-nvidia-470xx xorg-x11-drv-nvidia-470xx-cuda-libs.x86_64 : CUDA libraries for xorg-x11-drv-nvidia-470xx xorg-x11-drv-nvidia-470xx-devel.i686 : Development files for xorg-x11-drv-nvidia-470xx xorg-x11-drv-nvidia-470xx-devel.x86_64 : Development files for xorg-x11-drv-nvidia-470xx xorg-x11-drv-nvidia-470xx-kmodsrc.x86_64 : xorg-x11-drv-nvidia-470xx kernel module source code xorg-x11-drv-nvidia-470xx-libs.i686 : Libraries for xorg-x11-drv-nvidia-470xx xorg-x11-drv-nvidia-470xx-libs.x86_64 : Libraries for xorg-x11-drv-nvidia-470xx
可是仔细一琢磨, Fedora 35 Nvidia 驱动不是工作正常吗,可以上去看看用的是哪个版本的驱动,执行命令 nvidia-settings,在 System Information 中发现使用的就是最新版本的驱动 510.68.02,看样子最新版本的驱动也没有问题,一下陷入了困境。😰
Protocol buffers are Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.
这段是 Google 官方网站给出的介绍,protobuf 可以自动化生成代码,用于读入或者写入结构化数据。一个简单的 protobuf 文件可以是这样的:
1 2 3 4 5
message Person { requiredstring name = 1; requiredint32 id = 2; optionalstring email = 3; }
#define DEFINE_TEST_ONE_PROTO_INPUT_IMPL(use_binary, Proto) \ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { \ using protobuf_mutator::libfuzzer::LoadProtoInput; \ Proto input; \ if (LoadProtoInput(use_binary, data, size, &input)) \ TestOneProtoInput(input); \ return 0; \ } // Defines custom mutator, crossover and test functions using default // serialization format. Default is text. #define DEFINE_PROTO_FUZZER(arg) DEFINE_TEXT_PROTO_FUZZER(arg) // Defines custom mutator, crossover and test functions using text // serialization. This format is more convenient to read. #define DEFINE_TEXT_PROTO_FUZZER(arg) DEFINE_PROTO_FUZZER_IMPL(false, arg) // Defines custom mutator, crossover and test functions using binary // serialization. This makes mutations faster. However often test function is // significantly slower than mutator, so fuzzing rate may stay unchanged. #define DEFINE_BINARY_PROTO_FUZZER(arg) DEFINE_PROTO_FUZZER_IMPL(true, arg)
执行 make 后报错 ./test.pb.h:17:2: error: This file was generated by an older version of protoc which is,因为我们在编译的时候使用了 -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON 下载了新版本的 protobuf,所以出了这个错误。只好使用下载的 protoc 重新生成 test.pb.cc 和 test.pb.h
std::string res = all.str(); if (bb.size() != 0 && res.size() != 0) { // set PROTO_FUZZER_DUMP_PATH env to dump the serialized protobuf if (constchar *dump_path = getenv("PROTO_FUZZER_DUMP_PATH")) { std::ofstream of(dump_path); of.write(res.data(), res.size()); } } return res; }
extern"C"intFuzzTEST(constuint8_t* data, size_t size); // our customized fuzzing function
DEFINE_PROTO_FUZZER(const TEST &test_proto) { auto s = ProtoToData(test_proto); // convert protobuf to raw data FuzzTEST((constuint8_t*)s.data(), s.size()); // fuzz the function }
# for testing libprotobuf + libfuzzer # compile harness first # then link lpm_libfuzz with harness.o & static libraries harness.o: harness.cc $(CXX)$(CXXFLAGS) -c $(DFUZZ)$<
$(TARGET): harness.o $(TARGET).cc $(CXX)$(CXXFLAGS) -o $@$^$(PB_SRC)$(LPM_LIB)$(PROTOBUF_LIB)$(INC)# $(LPM_LIB) must be placed before $(PROTOBUF_LIB)
.PHONY: clean clean: rm $(TARGET) *.o
make 后报错,找不到头文件
1 2 3 4
/home/henices/code/AFL+/external/libprotobuf-mutator/include/libprotobuf-mutator/src/libfuzzer/libfuzzer_macro.h:24:10: fatal error: 'port/protobuf.h' file not found #include "port/protobuf.h" ^~~~~~~~~~~~~~~~~ 1 error generated.
# for testing libprotobuf + libfuzzer # compile harness first # then link lpm_libfuzz with harness.o & static libraries harness.o: harness.cc $(CXX)$(CXXFLAGS) -c $(DFUZZ)$<
$(TARGET): harness.o $(TARGET).cc $(CXX)$(CXXFLAGS) -o $@$^$(PB_SRC)$(LPM_LIB)$(PROTOBUF_LIB)$(INC)# $(LPM_LIB) must be placed before $(PROTOBUF_LIB)
extern"C"size_tafl_custom_fuzz(MyMutator *mutator, // return value from afl_custom_init uint8_t *buf, size_t buf_size, // input data to be mutated uint8_t **out_buf, // output buffer uint8_t *add_buf, size_t add_buf_size, // add_buf can be NULL size_t max_size){ // This function can be named either "afl_custom_fuzz" or "afl_custom_mutator" // A simple test shows that "buf" will be the content of the current test case // "add_buf" will be the next test case ( from AFL++'s input queue ) TEST input; // parse input data to TEST // Notice that input data should be a serialized protobuf data // Check ./in/ii and test_protobuf_serializer for more detail bool parse_ok = input.ParseFromArray(buf, buf_size); if(!parse_ok) { // Invalid serialize protobuf data. Don't mutate. // Return a dummy buffer. Also mutated_size = 0 staticuint8_t *dummy = newuint8_t[10]; // dummy buffer with no data *out_buf = dummy; return0; } // mutate the protobuf mutator->Mutate(&input, max_size); // Convert protobuf to raw data const TEST *p = &input; std::string s = ProtoToData(*p); // Copy to a new buffer ( mutated_out ) size_t mutated_size = s.size() <= max_size ? s.size() : max_size; // check if raw data's size is larger than max_size uint8_t *mutated_out = newuint8_t[mutated_size+1]; memcpy(mutated_out, s.c_str(), mutated_size); // copy the mutated data // Assign the mutated data and return mutated_size *out_buf = mutated_out; return mutated_size; }