LibAFL
LibAFL copied to clipboard
Capture __fastfail exceptions on windows
#258
When software call __fastfail, the exception can't be caught https://github.com/googleprojectzero/winafl/blob/ea5f6b85572980bb2cf636910f622f36906940aa/winafl.c#L728 winafl wraps two functions in kernelbase.dll
for frida fuzzers, I think this can be done in a similar way as winafl, but for sancov backend fuzzers, we would have to look at what libfuzzer is doing.
For the record, the problem indeed exist, here is the walkthrough
How to repro
#include <string.h>
extern "C"
{
__declspec(dllexport)
void fuzz_function(const char *src, size_t len)
{
char buf[512];
if (len < 4)
return;
if (src[0] != 'B')
return;
if (src[1] != 'O')
return;
if (src[2] != 'O')
return;
if (src[3] != 'M')
return;
memset(buf, 0, sizeof(buf)*2);
return;
}
}
compile string: cl /Zi boom.cpp /link /DEBUG:FULL /DLL /OUT:boom.dll
fuzz run: frida_gdiplus.exe -H boom.dll -F fuzz_function
You'll see the debugger popped up, if you have postmortem one installed
debug
security_check_cookie -> __report_gsfailure
void __fastcall __noreturn _report_gsfailure(unsigned __int64 stack_cookie)
{
void *retaddr; // [rsp+38h] [rbp+0h]
unsigned __int64 stack_cookiea; // [rsp+40h] [rbp+8h] BYREF
stack_cookiea = stack_cookie;
if ( IsProcessorFeaturePresent(0x17u) )
__fastfail(2u);
capture_previous_context(&GS_ContextRecord);
GS_ContextRecord.Rip = (unsigned __int64)retaddr;
GS_ContextRecord.Rsp = (unsigned __int64)&stack_cookiea;
GS_ExceptionRecord.ExceptionAddress = retaddr;
GS_ContextRecord.Rcx = stack_cookiea;
GS_ExceptionRecord.ExceptionCode = 0xC0000409;
GS_ExceptionRecord.ExceptionFlags = 1;
GS_ExceptionRecord.NumberParameters = 1;
GS_ExceptionRecord.ExceptionInformation[0] = 2i64;
j___raise_securityfailure(&GS_ExceptionPointers);
}
...
void __fastcall _raise_securityfailure(_EXCEPTION_POINTERS *const exception_pointers)
{
HANDLE CurrentProcess; // rax
SetUnhandledExceptionFilter(0i64);
UnhandledExceptionFilter(exception_pointers);
CurrentProcess = GetCurrentProcess();
TerminateProcess(CurrentProcess, 0xC0000409);
}
__fastfail(2)
is mov ecx,2 && int 29h
And IsProcessorFeaturePresent just checks KUSER_SHARED_DATA.ProcessorFeatures:
char __fastcall RtlIsProcessorFeaturePresent(unsigned int a1)
{
if ( a1 >= 0x40 )
return 0;
else
return *(_BYTE *)(a1 + 0x7FFE0274i64);
}
the solution
WinAFL's solution is partially right, to catch the exception we need to hook IsProcessorFeaturePresent
and return 0 for PF_FASTFAIL_AVAILABLE. But then we need to just handle C0000409 exception, cause UnhandledExceptionFilter() will lead to VEH handler anyway (presumably, need to verify).
the implementation
So far stuck on hook part. @tokatoka have any link how to hook win api with Frida? I found so far only this and libafl_frida\src\asan\hook_funcs.rs which is somewhat hard to grasp.
But then we need to just handle C0000409 exception, cause UnhandledExceptionFilter() will lead to VEH handler anyway (presumably, need to verify).
Confirmed, VEH handler is called if IsProcessorFeaturePresent(23)
return false
nice!
Confirmed, VEH handler is called if IsProcessorFeaturePresent(23) return false
Actually I was wrong, it's not the case. I confused it with exit process code 😆 Anyway I'll try to use hook
Somehow the PR works:
...
[Stats #1] (GLOBAL) run time: 0h-0m-0s, clients: 2, corpus: 4, objectives: 0, executions: 231, exec/sec: 0
(CLIENT) corpus: 4, objectives: 0, executions: 231, exec/sec: 0, edges: 17/65536 (0%)
[Testcase #1] (GLOBAL) run time: 0h-0m-0s, clients: 2, corpus: 5, objectives: 0, executions: 4544, exec/sec: 0
(CLIENT) corpus: 5, objectives: 0, executions: 4544, exec/sec: 0, edges: 17/65536 (0%)
[Stats #1] (GLOBAL) run time: 0h-0m-0s, clients: 2, corpus: 5, objectives: 0, executions: 4544, exec/sec: 0
(CLIENT) corpus: 5, objectives: 0, executions: 4544, exec/sec: 0, edges: 19/65536 (0%)
[Testcase #1] (GLOBAL) run time: 0h-0m-0s, clients: 2, corpus: 6, objectives: 0, executions: 7798, exec/sec: 0
(CLIENT) corpus: 6, objectives: 0, executions: 7798, exec/sec: 0, edges: 19/65536 (0%)
IsProcessorFeaturePresent(23) returning false
Calling handle_exception
Received STATUS_STACK_BUFFER_OVERRUN
Crashed with STATUS_STACK_BUFFER_OVERRUN
Child crashed!
Child crashed!
Waiting for broker...
Bye!
Spawning next client (id 1)
[Objective #1] (GLOBAL) run time: 0h-0m-2s, clients: 2, corpus: 6, objectives: 1, executions: 7798, exec/sec: 3427
(CLIENT) corpus: 6, objectives: 1, executions: 7798, exec/sec: 3427, edges: 19/65536 (0%)
This is not done yet... For frida, it's ok. But for inprocess-fuzzing we can't use frida APIs.
maybe we can use something like this (although, it looks really old) https://github.com/Jascha-N/minhook-rs
and then in the constructor for windows executor, hook IsProcessFeaturePresent
Requires nightly though, doesn't work on aarch, and is not maintained anymore/archived
There's a pr for newer (nightly) versions though
yeah I suppose we can do this hooking manually, shouldn't be difficult (like, in linux, just a LD_PRELOAD, and it's good) but I'm not familiar with windows internals.
here's a good blogpost about how to do this manually http://kylehalladay.com/blog/2020/11/13/Hooking-By-Example.html
somehow missed the latest conversation, @tokatoka what is inprocess-fuzzing in general, and how is that separated from Frida? I thought currently Frida is the only option for windows binary targets. If you have compiler instrumentation, perhaps you don't need this hook?
You can build inprocess-fuzzers with clang from source, and those will run into the same exception issues. We'll need a solution for these, too. Also, there is a branch with tinyinst that isn't done, however.