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.