0vercl0k/wtf

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

fish3rman opened this issue · 4 comments

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
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <strsafe.h>

#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 <text_file_name>\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<LPTSTR>(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<LPTSTR>(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
// 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 <fmt/format.h>

#include <iostream>
#include <string>
#include <codecvt>
#include <locale>

namespace Reader {

constexpr bool LoggingOn = true;

template <typename... Args_t>
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<std::codecvt_utf8_utf16<char16_t>, 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);

}
  • Dump
kd> !process 0 0 reader.exe
PROCESS ffffd88a3219f080
    SessionId: 2  Cid: 1214    Peb: 00340000  ParentCid: 19fc
    DirBase: 13746000  ObjectTable: ffffbf0d16027d00  HandleCount:  50.
    Image: reader.exe

kd> .process /i /p ffffd88a3219f080
You need to continue execution (press 'g' <enter>) 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:
fffff804`53bff050 cc              int     3
kd> .reload /user
Loading User Symbols
.....
kd> lmsmu 
start             end                 module name
00007ff9`b0ff0000 00007ff9`b11e5000   ntdll      (deferred)             
00000000`008e0000 00000000`008e7000   reader     (deferred)             
00007ff9`b0e00000 00007ff9`b0e59000   wow64      (deferred)             
00000000`77d70000 00000000`77d7a000   wow64cpu   (deferred)             
00007ff9`af900000 00007ff9`af983000   wow64win   (deferred)             
kd> u 00000000`008e0000 + 11d9
*** WARNING: Unable to verify checksum for reader.exe
reader!wmain+0x79 [C:\wtf\targets\reader\harness\reader\main.cpp @ 50]:
00000000`008e11d9 83c408          add     esp,8
00000000`008e11dc 6a00            push    0
00000000`008e11de 6880000040      push    40000080h
00000000`008e11e3 6a03            push    3
00000000`008e11e5 6a00            push    0
00000000`008e11e7 6a01            push    1
00000000`008e11e9 6800000080      push    0FFFFFFFF80000000h
00000000`008e11ee ff7604          push    qword ptr [rsi+4]
kd> bp 00000000`008e0000 + 11d9
kd> g
The context is partially valid. Only x86 user-mode context is available.
Breakpoint 0 hit
reader!wmain+0x79:
00000000`008e11d9 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
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
  • Symbolizer
ntoskrnl.exe+0x00404cc0
ntoskrnl.exe+0x00404cc1
ntoskrnl.exe+0x00404cc8
ntoskrnl.exe+0x00404cd0
ntoskrnl.exe+0x00404cd4
ntoskrnl.exe+0x00404cd8
ntoskrnl.exe+0x00404cdc
ntoskrnl.exe+0x00404ce0
ntoskrnl.exe+0x00404ce4
ntoskrnl.exe+0x00404ce8
ntoskrnl.exe+0x00404cec
ntoskrnl.exe+0x00404cf0
ntoskrnl.exe+0x00404cf7
ntoskrnl.exe+0x00404d26
ntoskrnl.exe+0x00404d2d
ntoskrnl.exe+0x00404d2f
...

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

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 0vercl0k/snapshot#8.

Cheers

yes the problem was the old version of snapshot.

thanks 👍

Awesome, sorry for the bug!

Cheers