0vercl0k / wtf

wtf is a distributed, code-coverage guided, customizable, cross-platform snapshot-based fuzzer designed for attacking user and / or kernel-mode targets running on Microsoft Windows and Linux user-mode (experimental!).
MIT License
1.46k stars 130 forks source link

It does not run successfully in a 32-bit environment and is recorded as a crash. #209

Closed fish3rman closed 1 month ago

fish3rman commented 1 month ago

Hi! I made test binary referenced by #106 . It works properly in a 64-bit binary, but something wrong with 32-bit. The strange thing is that when tracing, the address of reader.exe is not recorded. Instead of this, the start function in input.trace is nt!KiGeneralProtectionFault

Code ```c++ #define _CRT_SECURE_NO_WARNINGS #include #include #include #include #define BUFFERSIZE 5 DWORD g_BytesTransferred = 0; void DisplayError(LPTSTR lpszFunction); VOID CALLBACK FileIOCompletionRoutine( __in DWORD dwErrorCode, __in DWORD dwNumberOfBytesTransfered, __in LPOVERLAPPED lpOverlapped ); VOID CALLBACK FileIOCompletionRoutine( __in DWORD dwErrorCode, __in DWORD dwNumberOfBytesTransfered, __in LPOVERLAPPED lpOverlapped) { _tprintf(TEXT("Error code:\t%x\n"), dwErrorCode); _tprintf(TEXT("Number of bytes:\t%x\n"), dwNumberOfBytesTransfered); g_BytesTransferred = dwNumberOfBytesTransfered; } // // Note: this simplified sample assumes the file to read is an ANSI text file // only for the purposes of output to the screen. CreateFile and ReadFile // do not use parameters to differentiate between text and binary file types. // int __cdecl _tmain(int argc, TCHAR* argv[]) { HANDLE hFile; DWORD dwBytesRead = 0; char ReadBuffer[BUFFERSIZE] = { 0 }; OVERLAPPED ol = { 0 }; printf("\n"); if (argc != 2) { printf("Usage Error: Incorrect number of arguments\n\n"); _tprintf(TEXT("Usage:\n\t%s \n"), argv[0]); return 0; } int tmp; scanf("%d", &tmp); hFile = CreateFile(argv[1], // file to open GENERIC_READ, // open for reading FILE_SHARE_READ, // share for reading NULL, // default security OPEN_EXISTING, // existing file only FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, // normal file NULL); // no attr. template if (hFile == INVALID_HANDLE_VALUE) { DisplayError(const_cast(TEXT("CreateFile"))); _tprintf(TEXT("Terminal failure: unable to open file \"%s\" for read.\n"), argv[1]); return 0; } // Read one character less than the buffer size to save room for // the terminating NULL character. if (FALSE == ReadFileEx(hFile, ReadBuffer, BUFFERSIZE - 1, &ol, FileIOCompletionRoutine)) { DisplayError(const_cast(TEXT("ReadFile"))); printf("Terminal failure: Unable to read from file.\n GetLastError=%08x\n", GetLastError()); CloseHandle(hFile); return 0; } SleepEx(5000, TRUE); dwBytesRead = g_BytesTransferred; // This is the section of code that assumes the file is ANSI text. // Modify this block for other data types if needed. if (dwBytesRead > 0 && dwBytesRead <= BUFFERSIZE - 1) { ReadBuffer[dwBytesRead] = '\0'; // NULL character _tprintf(TEXT("Data read from %s (%d bytes): \n"), argv[1], dwBytesRead); printf("%s\n", ReadBuffer); } else if (dwBytesRead == 0) { _tprintf(TEXT("No data read from file %s\n"), argv[1]); } else { printf("\n ** Unexpected value for dwBytesRead ** \n"); } // It is always good practice to close the open file handles even though // the app will exit here and clean up open handles anyway. CloseHandle(hFile); } void DisplayError(LPTSTR lpszFunction) // Routine Description: // Retrieve and output the system error message for the last-error code { LPVOID lpMsgBuf; LPVOID lpDisplayBuf; DWORD dw = GetLastError(); FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL); lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, (lstrlen((LPCTSTR)lpMsgBuf) + lstrlen((LPCTSTR)lpszFunction) + 40) // account for format string * sizeof(TCHAR)); if (FAILED(StringCchPrintf((LPTSTR)lpDisplayBuf, LocalSize(lpDisplayBuf) / sizeof(TCHAR), TEXT("%s failed with error code %d as follows:\n%s"), lpszFunction, dw, lpMsgBuf))) { printf("FATAL ERROR: Unable to output error code.\n"); } _tprintf(TEXT("ERROR: %s\n"), (LPCTSTR)lpDisplayBuf); LocalFree(lpMsgBuf); LocalFree(lpDisplayBuf); } ```
harness ```c++ // Disable pagefiles https://www.tomshardware.com/reviews/ssd-performance-tweak,2911-4.html #include "backend.h" #include "crash_detection_umode.h" #include "fshandle_table.h" #include "fshooks.h" #include "targets.h" #include #include #include #include #include namespace Reader { constexpr bool LoggingOn = true; template void DebugPrint(const char *Format, const Args_t &...args) { if constexpr (LoggingOn) { fmt::print("reader: "); fmt::print(fmt::runtime(Format), args...); } } std::string u16stringToString(const std::u16string& u16str) { std::wstring_convert, char16_t> convert; return convert.to_bytes(u16str); } bool InsertTestcase(const uint8_t *Buffer, const size_t BufferSize) { g_FsHandleTable.MapExistingGuestFile( uR"(\??\C:\bin\readme.txt)", Buffer, BufferSize); return true; } bool Init(const Options_t &Opts, const CpuState_t &State) { const Gva_t Rip = Gva_t(g_Backend->Rip()); const Gva_t AfterCalls = Rip + Gva_t(0x3); if (!g_Backend->SetBreakpoint(AfterCalls, [](Backend_t *Backend) { DebugPrint("Reached function end\n"); Backend->PrintRegisters(); Backend->Stop(Ok_t()); })) { DebugPrint("Failed to SetBreakpoint AfterCalls\n"); return false; } if (!g_Backend->SetBreakpoint("reader!wprintf", [](Backend_t *Backend) { const Gva_t FormatPtr = Backend->GetArgGva(0); const std::u16string &WFormat = Backend->VirtReadWideString(FormatPtr); const std::string &Format = u16stringToString(WFormat); DebugPrint("wprintf: {}", Format); Backend->SimulateReturnFromFunction(0); })) { DebugPrint("Failed to SetBreakpoint on printf\n"); return false; } if (!g_Backend->SetBreakpoint("reader!printf", [](Backend_t *Backend) { const Gva_t FormatPtr = Backend->GetArgGva(0); const std::string &Format = Backend->VirtReadString(FormatPtr); DebugPrint("printf: {}", Format); Backend->SimulateReturnFromFunction(0); })) { fmt::print("Failed to SetBreakpoint on printf\n"); return false; } if (!SetupFilesystemHooks()) { DebugPrint("Failed to SetupFilesystemHooks\n"); return false; } if (!SetupUsermodeCrashDetectionHooks()) { fmt::print("Failed to SetupUsermodeCrashDetectionHooks\n"); return false; } return true; } Target_t Reader("reader", Init, InsertTestcase); } ```

kd> .process /i /p ffffd88a3219f080 You need to continue execution (press 'g' ) for the context to be switched. When the debugger breaks in again, you will be in the new process context. kd> g Break instruction exception - code 80000003 (first chance) nt!DbgBreakPointWithStatus: fffff80453bff050 cc int 3 kd> .reload /user Loading User Symbols ..... kd> lmsmu start end module name 00007ff9b0ff0000 00007ff9b11e5000 ntdll (deferred) 00000000008e0000 00000000008e7000 reader (deferred) 00007ff9b0e00000 00007ff9b0e59000 wow64 (deferred) 0000000077d70000 0000000077d7a000 wow64cpu (deferred) 00007ff9af900000 00007ff9af983000 wow64win (deferred) kd> u 00000000008e0000 + 11d9 ** WARNING: Unable to verify checksum for reader.exe reader!wmain+0x79 [C:\wtf\targets\reader\harness\reader\main.cpp @ 50]: 00000000008e11d9 83c408 add esp,8 00000000008e11dc 6a00 push 0 00000000008e11de 6880000040 push 40000080h 00000000008e11e3 6a03 push 3 00000000008e11e5 6a00 push 0 00000000008e11e7 6a01 push 1 00000000008e11e9 6800000080 push 0FFFFFFFF80000000h 00000000008e11ee ff7604 push qword ptr [rsi+4] kd> bp 00000000008e0000 + 11d9 kd> g The context is partially valid. Only x86 user-mode context is available. Breakpoint 0 hit reader!wmain+0x79: 00000000008e11d9 83c408 add esp,8 32.kd:x86> bc 32.kd:x86> !wow64exts.sw Switched to Host mode 32.kd> !snapshot c:\dump [dbgeng-rs] Dumping the CPU state into c:\dump\state.19041.1.amd64fre.vb_release.191206-1406.20240807_0540\regs.json.. [dbgeng-rs] Dumping the memory state into c:\dump\state.19041.1.amd64fre.vb_release.191206-1406.20240807_0540\mem.dmp.. Creating c:\dump\state.19041.1.amd64fre.vb_release.191206-1406.20240807_0540\mem.dmp - Full memory range dump 0% written. 5% written. 40 sec remaining. ValidateSequenceNumber: Sequence number too far ahead for validation. 10% written. 35 sec remaining. 15% written. 30 sec remaining. 20% written. 30 sec remaining. 25% written. 27 sec remaining. 30% written. 26 sec remaining. 35% written. 24 sec remaining. 40% written. 19 sec remaining. 45% written. 17 sec remaining. 50% written. 16 sec remaining. 55% written. 14 sec remaining. 60% written. 12 sec remaining. 65% written. 11 sec remaining. 70% written. 9 sec remaining. 75% written. 8 sec remaining. 80% written. 7 sec remaining. 85% written. 5 sec remaining. 90% written. 3 sec remaining. 95% written. 1 sec remaining. Wrote 4.0 GB in 35 sec. The average transfer rate was 117.0 MB/s. Dump successfully written [dbgeng-rs] Done!


- Result
```powershell
C:\wtf\targets\reader>..\..\src\build\wtf.exe run --name reader --state state --backend=bochscpu --input .\inputs\input --trace-type 1
Initializing the debugger instance.. (this takes a bit of time)
Setting debug register status to zero.
Setting debug register status to zero.
Trace file C:\wtf\targets\reader\input.trace
Running .\inputs\input
Mapping already existing guest file \??\C:\bin\readme.txt with filestream(21)
--------------------------------------------------
Run stats:
Instructions executed: 4.1k (2.0k unique)
          Dirty pages: 36.0kb
      Memory accesses: 19.3kb
       Edges executed: 0.0 (0.0 unique)
#1 cov: 1988 exec/s: 1.0 lastcov: 0.0s crash: 1 timeout: 0 cr3: 0 uptime: 1.0s
0vercl0k commented 1 month ago

Thanks for the detailed report πŸ™πŸ½

There were some issues recently around Wow64 that might / might not be related:

So basically, you should make sure you updated snapshot to the latest version to grab your dump file and if you want to use symbolizer-rs you should checkout the fbl_libify branch and build it yourself with (cargo build --release).

I'll check the code / your results more closely this week though.

Cheers

0vercl0k commented 1 month ago

Okay I looked closer at your regs.json and I can see that the segment limit is 0xfffff instead of 0xffffffff so I think you updating snapshot to >= 0.2.2 will resolve your issue; I believe you are hitting https://github.com/0vercl0k/snapshot/issues/8.

Cheers

fish3rman commented 1 month ago

yes the problem was the old version of snapshot.

thanks πŸ‘

0vercl0k commented 1 month ago

Awesome, sorry for the bug!

Cheers