LibAFL icon indicating copy to clipboard operation
LibAFL copied to clipboard

Capture __fastfail exceptions on windows

Open tokatoka opened this issue 3 years ago • 12 comments

#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.

tokatoka avatar Nov 30 '21 23:11 tokatoka

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.

expend20 avatar Sep 21 '22 20:09 expend20

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

expend20 avatar Sep 25 '22 20:09 expend20

nice!

tokatoka avatar Sep 25 '22 20:09 tokatoka

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

expend20 avatar Sep 27 '22 20:09 expend20

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%)

expend20 avatar Sep 28 '22 20:09 expend20

This is not done yet... For frida, it's ok. But for inprocess-fuzzing we can't use frida APIs.

tokatoka avatar Oct 06 '22 16:10 tokatoka

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

tokatoka avatar Oct 06 '22 16:10 tokatoka

Requires nightly though, doesn't work on aarch, and is not maintained anymore/archived

domenukk avatar Oct 06 '22 16:10 domenukk

There's a pr for newer (nightly) versions though

domenukk avatar Oct 06 '22 16:10 domenukk

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

tokatoka avatar Oct 06 '22 16:10 tokatoka

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?

expend20 avatar Oct 12 '22 19:10 expend20

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.

domenukk avatar Oct 12 '22 20:10 domenukk