From b3c4ded32588eb1d744bc7705e94cd68363d1016 Mon Sep 17 00:00:00 2001 From: oopsmishap Date: Wed, 12 Oct 2022 22:30:47 +0100 Subject: [PATCH 01/17] Start of test framework --- src/dumpulator/ntsyscalls.py | 2 +- tests/DumpulatorTests/.gitignore | 5 + tests/DumpulatorTests/DumpulatorTests.sln | 43 +++++++ .../HarnessFull/HarnessFull.cpp | 23 ++++ .../HarnessFull/HarnessFull.vcxproj | 117 +++++++++++++++++ .../HarnessFull/HarnessFull.vcxproj.filters | 22 ++++ .../HarnessMinimal/HarnessMinimal.cpp | 11 ++ .../HarnessMinimal/HarnessMinimal.vcxproj | 118 ++++++++++++++++++ .../HarnessMinimal.vcxproj.filters | 22 ++++ .../HarnessMinimal/msvcrt_x86.def | 3 + .../HarnessMinimal/msvcrt_x86.lib | Bin 0 -> 1718 bytes tests/DumpulatorTests/Loader/Loader.cpp | 20 +++ tests/DumpulatorTests/Loader/Loader.vcxproj | 99 +++++++++++++++ .../Loader/Loader.vcxproj.filters | 22 ++++ tests/DumpulatorTests/README.md | 14 +++ tests/DumpulatorTests/Tests/ExceptionTest.cpp | 64 ++++++++++ tests/DumpulatorTests/Tests/HandleTest.cpp | 76 +++++++++++ tests/DumpulatorTests/Tests/Tests.vcxproj | 116 +++++++++++++++++ .../Tests/Tests.vcxproj.filters | 30 +++++ tests/DumpulatorTests/Tests/debug.h | 40 ++++++ .../Tests/exception_handler_x64.def | 3 + .../Tests/exception_handler_x64.lib | Bin 0 -> 1738 bytes .../Tests/exception_handler_x86.def | 5 + .../Tests/exception_handler_x86.lib | Bin 0 -> 2182 bytes tests/handle-test32.py | 22 ++-- tests/handle-test64.py | 22 ++-- tests/handle_dmp_test.py | 53 ++++++++ 27 files changed, 925 insertions(+), 27 deletions(-) create mode 100644 tests/DumpulatorTests/.gitignore create mode 100644 tests/DumpulatorTests/DumpulatorTests.sln create mode 100644 tests/DumpulatorTests/HarnessFull/HarnessFull.cpp create mode 100644 tests/DumpulatorTests/HarnessFull/HarnessFull.vcxproj create mode 100644 tests/DumpulatorTests/HarnessFull/HarnessFull.vcxproj.filters create mode 100644 tests/DumpulatorTests/HarnessMinimal/HarnessMinimal.cpp create mode 100644 tests/DumpulatorTests/HarnessMinimal/HarnessMinimal.vcxproj create mode 100644 tests/DumpulatorTests/HarnessMinimal/HarnessMinimal.vcxproj.filters create mode 100644 tests/DumpulatorTests/HarnessMinimal/msvcrt_x86.def create mode 100644 tests/DumpulatorTests/HarnessMinimal/msvcrt_x86.lib create mode 100644 tests/DumpulatorTests/Loader/Loader.cpp create mode 100644 tests/DumpulatorTests/Loader/Loader.vcxproj create mode 100644 tests/DumpulatorTests/Loader/Loader.vcxproj.filters create mode 100644 tests/DumpulatorTests/README.md create mode 100644 tests/DumpulatorTests/Tests/ExceptionTest.cpp create mode 100644 tests/DumpulatorTests/Tests/HandleTest.cpp create mode 100644 tests/DumpulatorTests/Tests/Tests.vcxproj create mode 100644 tests/DumpulatorTests/Tests/Tests.vcxproj.filters create mode 100644 tests/DumpulatorTests/Tests/debug.h create mode 100644 tests/DumpulatorTests/Tests/exception_handler_x64.def create mode 100644 tests/DumpulatorTests/Tests/exception_handler_x64.lib create mode 100644 tests/DumpulatorTests/Tests/exception_handler_x86.def create mode 100644 tests/DumpulatorTests/Tests/exception_handler_x86.lib create mode 100644 tests/handle_dmp_test.py diff --git a/src/dumpulator/ntsyscalls.py b/src/dumpulator/ntsyscalls.py index a3ae6fb..31e21c8 100644 --- a/src/dumpulator/ntsyscalls.py +++ b/src/dumpulator/ntsyscalls.py @@ -630,7 +630,7 @@ def ZwCancelTimer(dp: Dumpulator, TimerHandle: HANDLE, CurrentState: P(BOOLEAN) ): - raise NotImplementedError() + return STATUS_SUCCESS @syscall def ZwCancelTimer2(dp: Dumpulator, diff --git a/tests/DumpulatorTests/.gitignore b/tests/DumpulatorTests/.gitignore new file mode 100644 index 0000000..9e4582a --- /dev/null +++ b/tests/DumpulatorTests/.gitignore @@ -0,0 +1,5 @@ +bin/ +_obj/ +.vs/ +*.user +*.exp \ No newline at end of file diff --git a/tests/DumpulatorTests/DumpulatorTests.sln b/tests/DumpulatorTests/DumpulatorTests.sln new file mode 100644 index 0000000..21b26e6 --- /dev/null +++ b/tests/DumpulatorTests/DumpulatorTests.sln @@ -0,0 +1,43 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32922.545 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Loader", "Loader\Loader.vcxproj", "{8F7621EE-2179-4E66-B058-B11C5908CF3E}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Tests", "Tests\Tests.vcxproj", "{F33082EB-B49F-4630-B919-B9B18C86E358}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "HarnessMinimal", "HarnessMinimal\HarnessMinimal.vcxproj", "{CBD65637-B773-430B-AF11-A0AFEB699D8F}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "HarnessFull", "HarnessFull\HarnessFull.vcxproj", "{1FD2D0B5-53E2-4E9E-AA4B-2E822ABD2D49}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8F7621EE-2179-4E66-B058-B11C5908CF3E}.Release|Win32.ActiveCfg = Release|Win32 + {8F7621EE-2179-4E66-B058-B11C5908CF3E}.Release|Win32.Build.0 = Release|Win32 + {8F7621EE-2179-4E66-B058-B11C5908CF3E}.Release|x64.ActiveCfg = Release|x64 + {8F7621EE-2179-4E66-B058-B11C5908CF3E}.Release|x64.Build.0 = Release|x64 + {F33082EB-B49F-4630-B919-B9B18C86E358}.Release|Win32.ActiveCfg = Release|Win32 + {F33082EB-B49F-4630-B919-B9B18C86E358}.Release|Win32.Build.0 = Release|Win32 + {F33082EB-B49F-4630-B919-B9B18C86E358}.Release|x64.ActiveCfg = Release|x64 + {F33082EB-B49F-4630-B919-B9B18C86E358}.Release|x64.Build.0 = Release|x64 + {CBD65637-B773-430B-AF11-A0AFEB699D8F}.Release|Win32.ActiveCfg = Release|Win32 + {CBD65637-B773-430B-AF11-A0AFEB699D8F}.Release|Win32.Build.0 = Release|Win32 + {CBD65637-B773-430B-AF11-A0AFEB699D8F}.Release|x64.ActiveCfg = Release|x64 + {CBD65637-B773-430B-AF11-A0AFEB699D8F}.Release|x64.Build.0 = Release|x64 + {1FD2D0B5-53E2-4E9E-AA4B-2E822ABD2D49}.Release|Win32.ActiveCfg = Release|Win32 + {1FD2D0B5-53E2-4E9E-AA4B-2E822ABD2D49}.Release|Win32.Build.0 = Release|Win32 + {1FD2D0B5-53E2-4E9E-AA4B-2E822ABD2D49}.Release|x64.ActiveCfg = Release|x64 + {1FD2D0B5-53E2-4E9E-AA4B-2E822ABD2D49}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B2416A67-4257-41D9-AD43-263D0467D10D} + EndGlobalSection +EndGlobal diff --git a/tests/DumpulatorTests/HarnessFull/HarnessFull.cpp b/tests/DumpulatorTests/HarnessFull/HarnessFull.cpp new file mode 100644 index 0000000..0fcb9cc --- /dev/null +++ b/tests/DumpulatorTests/HarnessFull/HarnessFull.cpp @@ -0,0 +1,23 @@ + +#include +#include +#include +#include + +int EntryPoint(void* peb) +{ + WSADATA wsa; + WSAStartup(0, &wsa); + CoInitialize(0); + ShellExecuteW(0, L"open", L".", nullptr, nullptr, SW_SHOWNORMAL); + MessageBeep(MB_ICONERROR); + InitCommonControls(); + HKEY key; + RegOpenKeyW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", &key); + BCRYPT_ALG_HANDLE alg; + BCryptOpenAlgorithmProvider(&alg, BCRYPT_RC4_ALGORITHM, nullptr, 0); + HCRYPTPROV prov; + CryptAcquireContextW(&prov, nullptr, nullptr, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT); + __debugbreak(); // Dump here + return 0; +} \ No newline at end of file diff --git a/tests/DumpulatorTests/HarnessFull/HarnessFull.vcxproj b/tests/DumpulatorTests/HarnessFull/HarnessFull.vcxproj new file mode 100644 index 0000000..b5a2ea5 --- /dev/null +++ b/tests/DumpulatorTests/HarnessFull/HarnessFull.vcxproj @@ -0,0 +1,117 @@ + + + + + Release + Win32 + + + Release + x64 + + + + 16.0 + Win32Proj + {1fd2d0b5-53e2-4e9e-aa4b-2e822abd2d49} + HarnessFull + 10.0 + + + + Application + false + v142 + true + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + false + $(SolutionDir)bin\ + $(SolutionDir)_obj\$(Platform)\$(ProjectName)\ + $(ProjectName)_x86 + + + false + $(SolutionDir)bin\ + $(SolutionDir)_obj\$(Platform)\$(ProjectName)\ + $(ProjectName)_x64 + + + + Level3 + true + true + false + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Disabled + false + MultiThreaded + StdCall + + + Console + true + true + true + EntryPoint + false + false + bcrypt.lib;ws2_32.lib;Comctl32.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + true + 0x300000 + + + + + Level3 + true + true + false + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Disabled + false + MultiThreaded + StdCall + + + Console + true + true + true + true + EntryPoint + false + false + bcrypt.lib;ws2_32.lib;Comctl32.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + 0x130000000 + + + + + + + + + \ No newline at end of file diff --git a/tests/DumpulatorTests/HarnessFull/HarnessFull.vcxproj.filters b/tests/DumpulatorTests/HarnessFull/HarnessFull.vcxproj.filters new file mode 100644 index 0000000..4cc5472 --- /dev/null +++ b/tests/DumpulatorTests/HarnessFull/HarnessFull.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/tests/DumpulatorTests/HarnessMinimal/HarnessMinimal.cpp b/tests/DumpulatorTests/HarnessMinimal/HarnessMinimal.cpp new file mode 100644 index 0000000..c268551 --- /dev/null +++ b/tests/DumpulatorTests/HarnessMinimal/HarnessMinimal.cpp @@ -0,0 +1,11 @@ +#include + +//extern "C" uintptr_t __cdecl _threadhandle(void); + +int EntryPoint(void* peb) +{ +#ifndef _WIN64 + __threadhandle(); +#endif // _WIN64 + return 0; +} \ No newline at end of file diff --git a/tests/DumpulatorTests/HarnessMinimal/HarnessMinimal.vcxproj b/tests/DumpulatorTests/HarnessMinimal/HarnessMinimal.vcxproj new file mode 100644 index 0000000..b7eb595 --- /dev/null +++ b/tests/DumpulatorTests/HarnessMinimal/HarnessMinimal.vcxproj @@ -0,0 +1,118 @@ + + + + + Release + Win32 + + + Release + x64 + + + + 16.0 + Win32Proj + {cbd65637-b773-430b-af11-a0afeb699d8f} + HarnessMinimal + 10.0 + + + + Application + false + v142 + true + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + false + $(SolutionDir)bin\ + $(SolutionDir)_obj\$(Platform)\$(ProjectName)\ + $(ProjectName)_x86 + + + false + $(SolutionDir)bin\ + $(SolutionDir)_obj\$(Platform)\$(ProjectName)\ + $(ProjectName)_x64 + + + + Level3 + true + true + false + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + MultiThreaded + false + Sync + StdCall + Disabled + + + Console + true + true + true + EntryPoint + false + true + false + msvcrt_x86.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + 0x300000 + + + + + Level3 + true + true + false + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + MultiThreaded + false + Sync + StdCall + Disabled + + + Console + true + true + true + EntryPoint + false + true + false + 0x130000000 + + + + + + + + + \ No newline at end of file diff --git a/tests/DumpulatorTests/HarnessMinimal/HarnessMinimal.vcxproj.filters b/tests/DumpulatorTests/HarnessMinimal/HarnessMinimal.vcxproj.filters new file mode 100644 index 0000000..3a69434 --- /dev/null +++ b/tests/DumpulatorTests/HarnessMinimal/HarnessMinimal.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/tests/DumpulatorTests/HarnessMinimal/msvcrt_x86.def b/tests/DumpulatorTests/HarnessMinimal/msvcrt_x86.def new file mode 100644 index 0000000..c85412c --- /dev/null +++ b/tests/DumpulatorTests/HarnessMinimal/msvcrt_x86.def @@ -0,0 +1,3 @@ +LIBRARY msvcrt.dll +EXPORTS + __threadhandle \ No newline at end of file diff --git a/tests/DumpulatorTests/HarnessMinimal/msvcrt_x86.lib b/tests/DumpulatorTests/HarnessMinimal/msvcrt_x86.lib new file mode 100644 index 0000000000000000000000000000000000000000..4052aa3e6a1cce738dd1f02e0eb3bf01c557175c GIT binary patch literal 1718 zcmcIl%Wl&^6g^{yQY?hRV+R#RsswC8n>vq52&GUY(zt1qrfb)=1BsN7Doxn(1AIeQ zd<4s?>XHQuKA_*x1?ArHOl?C7DGC#LX6~8Cz2`nAkDmL(p!33dW~bY7u~;aV^3G}@ zjV~#2d8MpoyB2_JfH(xq6CiU2%x&=JdDYFvR&;HR=B=-_*64sY>I~a{*zw$Y917sX1kOm< zn3J?1U?4+3lXPC4 vw)-;Hku!2(uNN`D>!eS+4K``j=lc~pJAoGOr@E!tiv8NCtj6dW~ zHNG7vD~SgK4eI|Tmu8hERMi)?>k30=$rouE+c!B&Lb()oi7Z*sC}+uMEvS*RWbd!nMPpM=Cf=>Px# literal 0 HcmV?d00001 diff --git a/tests/DumpulatorTests/Loader/Loader.cpp b/tests/DumpulatorTests/Loader/Loader.cpp new file mode 100644 index 0000000..182a524 --- /dev/null +++ b/tests/DumpulatorTests/Loader/Loader.cpp @@ -0,0 +1,20 @@ +#include +#include + +int main(int argc, char** argv) +{ +#ifdef _WIN64 + auto dll = "Tests_x64.dll"; +#else + auto dll = "Tests_x86.dll"; +#endif // _WIN64 + auto hLib = LoadLibraryA(dll); + if (argc < 2) + { + // TODO: implement enumerating all exports and running them + puts("Usage: Loader TestFunction"); + return EXIT_FAILURE; + } + auto TestFunction = (int(*)())GetProcAddress(hLib, argv[1]); + return TestFunction() ? EXIT_SUCCESS : EXIT_FAILURE; +} \ No newline at end of file diff --git a/tests/DumpulatorTests/Loader/Loader.vcxproj b/tests/DumpulatorTests/Loader/Loader.vcxproj new file mode 100644 index 0000000..05203bc --- /dev/null +++ b/tests/DumpulatorTests/Loader/Loader.vcxproj @@ -0,0 +1,99 @@ + + + + + Release + Win32 + + + Release + x64 + + + + 16.0 + Win32Proj + {8F7621EE-2179-4E66-B058-B11C5908CF3E} + Loader + 10.0 + + + + Application + false + v142 + true + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + false + $(SolutionDir)bin\ + $(SolutionDir)_obj\$(Platform)\$(ProjectName)\ + $(ProjectName)_x86 + + + false + $(SolutionDir)bin\ + $(SolutionDir)_obj\$(Platform)\$(ProjectName)\ + $(ProjectName)_x64 + + + + Level3 + false + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + MultiThreaded + + + Console + true + true + true + false + false + + + + + Level3 + false + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + MultiThreaded + + + Console + true + true + true + false + false + + + + + + + + + \ No newline at end of file diff --git a/tests/DumpulatorTests/Loader/Loader.vcxproj.filters b/tests/DumpulatorTests/Loader/Loader.vcxproj.filters new file mode 100644 index 0000000..cad3232 --- /dev/null +++ b/tests/DumpulatorTests/Loader/Loader.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/tests/DumpulatorTests/README.md b/tests/DumpulatorTests/README.md new file mode 100644 index 0000000..c5ce86c --- /dev/null +++ b/tests/DumpulatorTests/README.md @@ -0,0 +1,14 @@ +# Dumpulator Test Harness + +### How to install the Template + +* Navigate to `C:\Users\{user}\Documents\{Visual Studio 20XX}\Templates\ProjectTemplates\Visual C++ Project` +* Copy `DumpulatorTemplate.zip` into this folder +* Restart Visual Studio + +### How to add a new project to the solution + +* Right click solution +* New->Project +* In the search bar search `DumpulatorTemplate` +* Complete wizard to add new test DLL \ No newline at end of file diff --git a/tests/DumpulatorTests/Tests/ExceptionTest.cpp b/tests/DumpulatorTests/Tests/ExceptionTest.cpp new file mode 100644 index 0000000..a7e3ad2 --- /dev/null +++ b/tests/DumpulatorTests/Tests/ExceptionTest.cpp @@ -0,0 +1,64 @@ +#include "debug.h" + +static LONG WINAPI VectoredHandler(struct _EXCEPTION_POINTERS* ExceptionInfo) +{ + DebugPrint(WIDEN(__FUNCTION__)); + return EXCEPTION_CONTINUE_SEARCH; +} + +static LONG WINAPI ContinueHandler(struct _EXCEPTION_POINTERS* ExceptionInfo) +{ + DebugPrint(WIDEN(__FUNCTION__)); + return EXCEPTION_CONTINUE_SEARCH; +} + +static LPTOP_LEVEL_EXCEPTION_FILTER previousFilter; + +static LONG WINAPI ExceptionFilter(struct _EXCEPTION_POINTERS* ExceptionInfo) +{ + DebugPrint(WIDEN(__FUNCTION__)); + if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT) + { +#ifdef _WIN64 + ExceptionInfo->ContextRecord->Rip++; +#else + ExceptionInfo->ContextRecord->Eip++; +#endif // _WIN64 + return EXCEPTION_CONTINUE_EXECUTION; + } + return previousFilter(ExceptionInfo); +} + +static int __try_filter(unsigned int code, struct _EXCEPTION_POINTERS* ExceptionInfo) +{ + DebugPrint(WIDEN(__FUNCTION__)); + const auto& er = *ExceptionInfo->ExceptionRecord; + if (er.ExceptionCode == EXCEPTION_ACCESS_VIOLATION && er.ExceptionInformation[1] == 0xDEADF00D) + { + return EXCEPTION_EXECUTE_HANDLER; + } + return EXCEPTION_CONTINUE_SEARCH; +} + +extern "C" __declspec(dllexport) bool ExceptionTest() +{ + DebugPrint(WIDEN(__FUNCTION__)); + DebugPrint(L"Test VEH, SEH, VCH"); + AddVectoredExceptionHandler(1, VectoredHandler); + AddVectoredContinueHandler(1, ContinueHandler); + + __try + { + *((size_t*)(uintptr_t)0xDEADF00D) = 0; + } + __except (__try_filter(GetExceptionCode(), GetExceptionInformation())) + { + DebugPrint(L"__except handler"); + } + + DebugPrint(L"Test SetUnhandledExceptionFilter"); + previousFilter = SetUnhandledExceptionFilter(ExceptionFilter); + __debugbreak(); + DebugPrint(L"Finished!"); + return true; +} diff --git a/tests/DumpulatorTests/Tests/HandleTest.cpp b/tests/DumpulatorTests/Tests/HandleTest.cpp new file mode 100644 index 0000000..e17e4cc --- /dev/null +++ b/tests/DumpulatorTests/Tests/HandleTest.cpp @@ -0,0 +1,76 @@ +#include "debug.h" + +extern "C" __declspec(dllexport) bool WriteAndCreateFileTest() +{ + DebugPrint(WIDEN(__FUNCTION__)); + + char data_buffer[] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; + DWORD data_buffer_len = sizeof(data_buffer); + DWORD bytes_written = 0; + BOOL ret_value = FALSE; + + HANDLE file_handle = CreateFile( + L"nonexistant_file.txt", + GENERIC_WRITE, + 0, + NULL, + CREATE_NEW, + FILE_ATTRIBUTE_NORMAL, + NULL + ); + + if (file_handle == INVALID_HANDLE_VALUE) + { + DebugPrint(L"Failed to create file"); + return ret_value; + } + + ret_value = WriteFile( + file_handle, + data_buffer, + data_buffer_len, + &bytes_written, + NULL + ); + + CloseHandle(file_handle); + + return ret_value; +} + +extern "C" __declspec(dllexport) bool ReadFileTest() +{ + DebugPrint(WIDEN(__FUNCTION__)); + + DWORD bytes_written = 0; + BOOL ret_value = FALSE; + char read_buffer[1000]; + + HANDLE file_handle = CreateFile( + L"test_file.txt", + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL + ); + + if (file_handle == INVALID_HANDLE_VALUE) + { + DebugPrint(L"Failed to open file"); + return ret_value; + } + + ret_value = ReadFile( + file_handle, + read_buffer, + sizeof(read_buffer), + &bytes_written, + FALSE + ); + + CloseHandle(file_handle); + + return ret_value; +} diff --git a/tests/DumpulatorTests/Tests/Tests.vcxproj b/tests/DumpulatorTests/Tests/Tests.vcxproj new file mode 100644 index 0000000..b5b2fe7 --- /dev/null +++ b/tests/DumpulatorTests/Tests/Tests.vcxproj @@ -0,0 +1,116 @@ + + + + + Release + Win32 + + + Release + x64 + + + + + + + + + + + 16.0 + Win32Proj + {f33082eb-b49f-4630-b919-b9b18c86e358} + ExceptionTest + 10.0 + + + + DynamicLibrary + false + v142 + true + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + + + + + + + + + + + + + false + $(SolutionDir)bin\ + $(SolutionDir)_obj\$(Platform)\$(ProjectName)\ + $(ProjectName)_x86 + + + false + $(SolutionDir)bin\ + $(SolutionDir)_obj\$(Platform)\$(ProjectName)\ + $(ProjectName)_x64 + + + + Level3 + false + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + false + NotSet + MultiThreaded + false + + + Console + true + true + true + false + /DYNAMICBASE:NO %(AdditionalOptions) + true + exception_handler_x86.lib;ntdll.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + true + false + Default + + + + + Level3 + false + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + false + NotSet + MultiThreaded + false + + + Console + true + true + true + false + /DYNAMICBASE:NO %(AdditionalOptions) + true + exception_handler_x64.lib;ntdll.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + true + Default + + + + + + \ No newline at end of file diff --git a/tests/DumpulatorTests/Tests/Tests.vcxproj.filters b/tests/DumpulatorTests/Tests/Tests.vcxproj.filters new file mode 100644 index 0000000..ddf6038 --- /dev/null +++ b/tests/DumpulatorTests/Tests/Tests.vcxproj.filters @@ -0,0 +1,30 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + + + Header Files + + + \ No newline at end of file diff --git a/tests/DumpulatorTests/Tests/debug.h b/tests/DumpulatorTests/Tests/debug.h new file mode 100644 index 0000000..f85172a --- /dev/null +++ b/tests/DumpulatorTests/Tests/debug.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" +#endif // __cplusplus +NTSYSCALLAPI +NTSTATUS +NTAPI +NtDisplayString( + PUNICODE_STRING String +); + +#define WIDEN_EXPAND(str) L ## str +#define WIDEN(str) WIDEN_EXPAND(str) + +#ifdef __c1plusplus +// Helper function to directly call NtDisplayString with a string +// This simplifies the trace output of Dumpulator +template +void DebugPrint(const wchar_t(&str)[Count]) +{ + UNICODE_STRING ustr{ (Count - 1) * 2, Count * 2, (PWSTR)str }; + NtDisplayString(&ustr); +} +#else +static void DebugPrint(const wchar_t* str) +{ + int len = 0; + while (str[len] != L'\0') + len++; + UNICODE_STRING ustr; + ustr.Length = len * 2; + ustr.MaximumLength = (len + 1) * 2; + ustr.Buffer = (PWSTR)str; + NtDisplayString(&ustr); +} +#endif // __cplusplus diff --git a/tests/DumpulatorTests/Tests/exception_handler_x64.def b/tests/DumpulatorTests/Tests/exception_handler_x64.def new file mode 100644 index 0000000..c8731df --- /dev/null +++ b/tests/DumpulatorTests/Tests/exception_handler_x64.def @@ -0,0 +1,3 @@ +LIBRARY ntdll.dll +EXPORTS + __C_specific_handler \ No newline at end of file diff --git a/tests/DumpulatorTests/Tests/exception_handler_x64.lib b/tests/DumpulatorTests/Tests/exception_handler_x64.lib new file mode 100644 index 0000000000000000000000000000000000000000..cc91f611671e1dd421fe998cf7376978eca63887 GIT binary patch literal 1738 zcmcIlJ8v365dIE`A1I1Yk}h(5kt`)?h=DsyVI}8Bq#!W1&bV>2!FM>Egbl(_r*oBm zk>0Ia%dHzrDO2Q^M9$3K?)gx_0g0@%kNIX`=bOjgtMx|DYrRjuGlxg6ShP!Pg?zyp z##fb?vx@5UISt?uU|aynTRNtIxkH{ply~-a-MZNRxWDC=_v*W@IPLrGHiW1hRI7ih z@m1KkOgCC<7{FE|c7(Q2If?Pia_;G`=?s-|XR-?Jh2abWhFOa6Hhu3howiQ58kNTEa<_V!9lH&=Ma6Nds6L`JOS?9QKzH#50)pD((^h4WYlGqKNlsI|H&`KF3 zEV=`Z*E#8upVtG!xVwwnlOZLTr>5%w($$tmB(d-oWn-}o{RokbeUC8yk{31j-+?tH z@s>c1*4Z^mIwCq@h2*VLtU?m9LN29a?B3`K@k3R##ww(sL9USRT2MPz$bZQEBpF>I z7sft1Q^%~7kHswPs-JX0nBLJbd!Vaq9VYHd?yC53;OMThG|qNEw4ipjn{jL>iR%lx fenxMVol`+I`HRA>>7Vm^wJaH?M!&jI>X7~eEYU@V literal 0 HcmV?d00001 diff --git a/tests/DumpulatorTests/Tests/exception_handler_x86.def b/tests/DumpulatorTests/Tests/exception_handler_x86.def new file mode 100644 index 0000000..50d47e0 --- /dev/null +++ b/tests/DumpulatorTests/Tests/exception_handler_x86.def @@ -0,0 +1,5 @@ +LIBRARY msvcrt.dll +EXPORTS + _except_handler2 + _except_handler3 + _except_handler4_common \ No newline at end of file diff --git a/tests/DumpulatorTests/Tests/exception_handler_x86.lib b/tests/DumpulatorTests/Tests/exception_handler_x86.lib new file mode 100644 index 0000000000000000000000000000000000000000..cb54688932fe2bb8af98d5cccba1c7f8d4a6f377 GIT binary patch literal 2182 zcmcIm&2AD=6#iy_wi06yUD}8ViMl};hG|KRq1q-Arj$^+p5lN)0)USsv6> z4Rg@%4^BcZxW`N9PV;iNM#(#qf%7PE9Rsv=U`ivoVI>t3WJD3enlnepndP%M8yROS z80*r}hb25Ly>7O`Dry%x)9Ur?eEtc6YEfYEIIc?(>}4xvTq2xNMv0_MtNm{BA$@3X zff6mgh>gQbNW9K;Tg`DZt%#$CwD?Bj$`M3}myo@1^a$BD-6i&u#+ALb<`PmDR7~9G zh9HW2K|RHT1l1M%Ii`Eq#N{Np-!(^r)4}_3>RJ7HN-tL{_Hl4#TYQl}KwW075;PNF zNah(KPYJR^a`kc|NjEpb9N%DK-(-l0OMvBVM@X#L*iJ-9RMsH@T%Cd^Vfka89OAD7 z+s=-ej2g{<}p zyDAjAAWZSECHJK+@q5Kp;}>i8HCFsRy>#_%vnI{4mj5Sf?jM1f*wPf4Jja^*L*RS1 FegQrL#}xno literal 0 HcmV?d00001 diff --git a/tests/handle-test32.py b/tests/handle-test32.py index 542b4d8..501c49c 100644 --- a/tests/handle-test32.py +++ b/tests/handle-test32.py @@ -1,24 +1,20 @@ from dumpulator import Dumpulator from dumpulator.native import * -test_funcs = { - "console_output_test": 0x4010C0, - "read_file_test": 0x4010E0, - "write_file_test": 0x401190, - "write_file_offset_test": 0x401270, - "create_file_test": 0x401350, -} - - def main(): - dp = Dumpulator("HandleTest_x86.dmp") + dp = Dumpulator("TestHarness_x86.dmp") dp.handles.create_file("test_file.txt", FILE_OPEN) dp.handles.create_file("nonexistant_file.txt", FILE_CREATE) - for name, addr in test_funcs.items(): - print(f"\n---- calling {name} ----\n") - dp.call(addr) + with open("TestHarness/bin/HandleTest_x86.dll", "rb") as dll: + dll_data = dll.read() + + dp.map_module(dll_data, "HandleTest_x86.dll") + + for export in dp.modules["HandleTest_x86.dll"].exports: + print(f"\n---- calling {export.name} ----\n") + dp.call(export.address) if __name__ == '__main__': diff --git a/tests/handle-test64.py b/tests/handle-test64.py index 89ca69d..49fc5c3 100644 --- a/tests/handle-test64.py +++ b/tests/handle-test64.py @@ -1,24 +1,20 @@ from dumpulator import Dumpulator from dumpulator.native import * -test_funcs = { - "console_output_test": 0x140001150, - "read_file_test": 0x140001170, - "write_file_test": 0x140001240, - "write_file_offset_test": 0x140001330, - "create_file_test": 0x140001420, -} - - def main(): - dp = Dumpulator("HandleTest_x64.dmp") + dp = Dumpulator("TestHarness_x64.dmp") dp.handles.create_file("test_file.txt", FILE_OPEN) dp.handles.create_file("nonexistant_file.txt", FILE_CREATE) - for name, addr in test_funcs.items(): - print(f"\n---- calling {name} ----\n") - dp.call(addr) + with open("TestHarness/bin/HandleTest_x64.dll", "rb") as dll: + dll_data = dll.read() + + dp.map_module(dll_data, "HandleTest_x64.dll") + + for export in dp.modules["HandleTest_x64.dll"].exports: + print(f"\n---- calling {export.name} ----\n") + dp.call(export.address) if __name__ == '__main__': diff --git a/tests/handle_dmp_test.py b/tests/handle_dmp_test.py new file mode 100644 index 0000000..cb493ab --- /dev/null +++ b/tests/handle_dmp_test.py @@ -0,0 +1,53 @@ +import unittest +from dumpulator import Dumpulator +from dumpulator.native import * + + +class TestHandleManagerx64(unittest.TestCase): + dp = None + + @classmethod + def setUp(cls): + cls.dp = Dumpulator("TestHarness_x64.dmp", quiet=True) + with open("TestHarness/bin/HandleTest_x64.dll", "rb") as dll: + dll_data = dll.read() + cls.dp.map_module(dll_data, "HandleTest_x64.dll") + + def test_write_create_file(self): + self.dp.handles.create_file("nonexistant_file.txt", FILE_CREATE) + test_func = self.dp.modules["HandleTest_x64.dll"].find_export("WriteAndCreateFileTest") + ret_val = self.dp.call(test_func.address) + self.assertEqual(ret_val, 1) + + def test_read_file(self): + self.dp.handles.create_file("test_file.txt", FILE_OPEN) + test_func = self.dp.modules["HandleTest_x64.dll"].find_export("ReadFileTest") + ret_val = self.dp.call(test_func.address) + self.assertEqual(ret_val, 1) + + +class TestHandleManagerx86(unittest.TestCase): + dp = None + + @classmethod + def setUp(cls): + cls.dp = Dumpulator("TestHarness_x86.dmp", quiet=True) + with open("TestHarness/bin/HandleTest_x86.dll", "rb") as dll: + dll_data = dll.read() + cls.dp.map_module(dll_data, "HandleTest_x86.dll") + + def test_write_create_file(self): + self.dp.handles.create_file("nonexistant_file.txt", FILE_CREATE) + test_func = self.dp.modules["HandleTest_x86.dll"].find_export("WriteAndCreateFileTest") + ret_val = self.dp.call(test_func.address) + self.assertEqual(ret_val, 1) + + def test_read_file(self): + self.dp.handles.create_file("test_file.txt", FILE_OPEN) + test_func = self.dp.modules["HandleTest_x86.dll"].find_export("ReadFileTest") + ret_val = self.dp.call(test_func.address) + self.assertEqual(ret_val, 1) + + +if __name__ == '__main__': + unittest.main() From d8c55754306e2d3ffd03e14264928d89b8c35a77 Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Tue, 18 Oct 2022 01:06:54 +0200 Subject: [PATCH 02/17] Implement a generic runner for the DumpulatorTests --- tests/DumpulatorTests/README.md | 30 ++-- tests/DumpulatorTests/Tests/ExceptionTest.cpp | 2 +- tests/DumpulatorTests/Tests/HandleTest.cpp | 6 +- tests/ExceptionTest/.gitignore | 6 - tests/ExceptionTest/ExceptionTest.sln | 31 ---- .../ExceptionTest/ExceptionTest.vcxproj | 162 ------------------ .../ExceptionTest.vcxproj.filters | 27 --- tests/ExceptionTest/ExceptionTest/main.cpp | 85 --------- tests/HandleTest/HandleTest.sln | 31 ---- .../HandleTest/HandleTest/HandleTest.vcxproj | 149 ---------------- .../HandleTest/HandleTest.vcxproj.filters | 22 --- tests/HandleTest/HandleTest/main.c | 139 --------------- tests/handle-test32.py | 21 --- tests/handle-test64.py | 21 --- tests/handle_dmp_test.py | 53 ------ tests/harness-tests.py | 69 ++++++++ 16 files changed, 93 insertions(+), 761 deletions(-) delete mode 100644 tests/ExceptionTest/.gitignore delete mode 100644 tests/ExceptionTest/ExceptionTest.sln delete mode 100644 tests/ExceptionTest/ExceptionTest/ExceptionTest.vcxproj delete mode 100644 tests/ExceptionTest/ExceptionTest/ExceptionTest.vcxproj.filters delete mode 100644 tests/ExceptionTest/ExceptionTest/main.cpp delete mode 100644 tests/HandleTest/HandleTest.sln delete mode 100644 tests/HandleTest/HandleTest/HandleTest.vcxproj delete mode 100644 tests/HandleTest/HandleTest/HandleTest.vcxproj.filters delete mode 100644 tests/HandleTest/HandleTest/main.c delete mode 100644 tests/handle-test32.py delete mode 100644 tests/handle-test64.py delete mode 100644 tests/handle_dmp_test.py create mode 100644 tests/harness-tests.py diff --git a/tests/DumpulatorTests/README.md b/tests/DumpulatorTests/README.md index c5ce86c..0594321 100644 --- a/tests/DumpulatorTests/README.md +++ b/tests/DumpulatorTests/README.md @@ -1,14 +1,24 @@ -# Dumpulator Test Harness +# DumpulatorTests -### How to install the Template +## Creating a dump -* Navigate to `C:\Users\{user}\Documents\{Visual Studio 20XX}\Templates\ProjectTemplates\Visual C++ Project` -* Copy `DumpulatorTemplate.zip` into this folder -* Restart Visual Studio +The harness dumps were created as follows: -### How to add a new project to the solution +- Start a clean Windows Sandbox install +- Put x64dbg in there +- Install the [DisableParallelLoader](https://github.com/mrexodia/DisableParallelLoader) plugin and enable it +- Load the harness executable +- Execute the `dbh` command to hide the debugger from the PEB +- Run until the entry point +- Use the `minidump` command to create the dump -* Right click solution -* New->Project -* In the search bar search `DumpulatorTemplate` -* Complete wizard to add new test DLL \ No newline at end of file +## Adding a new test + +Add a new `mytest.cpp` file to the `Tests` project. The tests are exported as `bool _Test();` and the result indicates whether the test was successful or not. If you need a custom environment add the following in `tests/harness-tests.py`: + +```python +class Environment(TestEnvironment): + def setup(self, dp: Dumpulator): + # TODO: use the dp class to initialize your environment + pass +``` \ No newline at end of file diff --git a/tests/DumpulatorTests/Tests/ExceptionTest.cpp b/tests/DumpulatorTests/Tests/ExceptionTest.cpp index a7e3ad2..27ba847 100644 --- a/tests/DumpulatorTests/Tests/ExceptionTest.cpp +++ b/tests/DumpulatorTests/Tests/ExceptionTest.cpp @@ -40,7 +40,7 @@ static int __try_filter(unsigned int code, struct _EXCEPTION_POINTERS* Exception return EXCEPTION_CONTINUE_SEARCH; } -extern "C" __declspec(dllexport) bool ExceptionTest() +extern "C" __declspec(dllexport) bool Exception_Test() { DebugPrint(WIDEN(__FUNCTION__)); DebugPrint(L"Test VEH, SEH, VCH"); diff --git a/tests/DumpulatorTests/Tests/HandleTest.cpp b/tests/DumpulatorTests/Tests/HandleTest.cpp index e17e4cc..97155d9 100644 --- a/tests/DumpulatorTests/Tests/HandleTest.cpp +++ b/tests/DumpulatorTests/Tests/HandleTest.cpp @@ -1,6 +1,6 @@ #include "debug.h" -extern "C" __declspec(dllexport) bool WriteAndCreateFileTest() +extern "C" __declspec(dllexport) bool Handle_WriteAndCreateFileTest() { DebugPrint(WIDEN(__FUNCTION__)); @@ -10,7 +10,7 @@ extern "C" __declspec(dllexport) bool WriteAndCreateFileTest() BOOL ret_value = FALSE; HANDLE file_handle = CreateFile( - L"nonexistant_file.txt", + L"nonexistent_file.txt", GENERIC_WRITE, 0, NULL, @@ -38,7 +38,7 @@ extern "C" __declspec(dllexport) bool WriteAndCreateFileTest() return ret_value; } -extern "C" __declspec(dllexport) bool ReadFileTest() +extern "C" __declspec(dllexport) bool Handle_ReadFileTest() { DebugPrint(WIDEN(__FUNCTION__)); diff --git a/tests/ExceptionTest/.gitignore b/tests/ExceptionTest/.gitignore deleted file mode 100644 index 991634b..0000000 --- a/tests/ExceptionTest/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -x64/ -Release/ -Debug/ -Win32/ -.vs/ -*.user \ No newline at end of file diff --git a/tests/ExceptionTest/ExceptionTest.sln b/tests/ExceptionTest/ExceptionTest.sln deleted file mode 100644 index 2eb1b34..0000000 --- a/tests/ExceptionTest/ExceptionTest.sln +++ /dev/null @@ -1,31 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31911.196 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ExceptionTest", "ExceptionTest\ExceptionTest.vcxproj", "{4DF88650-7AF7-450C-B867-534D2A5304E1}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {4DF88650-7AF7-450C-B867-534D2A5304E1}.Debug|x64.ActiveCfg = Debug|x64 - {4DF88650-7AF7-450C-B867-534D2A5304E1}.Debug|x64.Build.0 = Debug|x64 - {4DF88650-7AF7-450C-B867-534D2A5304E1}.Debug|x86.ActiveCfg = Debug|Win32 - {4DF88650-7AF7-450C-B867-534D2A5304E1}.Debug|x86.Build.0 = Debug|Win32 - {4DF88650-7AF7-450C-B867-534D2A5304E1}.Release|x64.ActiveCfg = Release|x64 - {4DF88650-7AF7-450C-B867-534D2A5304E1}.Release|x64.Build.0 = Release|x64 - {4DF88650-7AF7-450C-B867-534D2A5304E1}.Release|x86.ActiveCfg = Release|Win32 - {4DF88650-7AF7-450C-B867-534D2A5304E1}.Release|x86.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {C233290A-8A7F-4F3C-879D-90EB09B42BEC} - EndGlobalSection -EndGlobal diff --git a/tests/ExceptionTest/ExceptionTest/ExceptionTest.vcxproj b/tests/ExceptionTest/ExceptionTest/ExceptionTest.vcxproj deleted file mode 100644 index c97f2e5..0000000 --- a/tests/ExceptionTest/ExceptionTest/ExceptionTest.vcxproj +++ /dev/null @@ -1,162 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 16.0 - Win32Proj - {4df88650-7af7-450c-b867-534d2a5304e1} - ExceptionTest - 10.0 - - - - Application - true - v142 - Unicode - - - Application - false - v142 - true - Unicode - - - Application - true - v142 - Unicode - - - Application - false - v142 - true - Unicode - - - - - - - - - - - - - - - - - - - - - true - - - false - - - true - - - false - - - - Level3 - true - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp17 - - - Console - true - - - - - Level3 - true - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp17 - false - MultiThreaded - - - Console - true - true - true - false - Default - - - - - Level3 - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp17 - - - Console - true - - - - - Level3 - true - true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp17 - false - MultiThreaded - - - Console - true - true - true - false - Default - - - - - - - - - - - - \ No newline at end of file diff --git a/tests/ExceptionTest/ExceptionTest/ExceptionTest.vcxproj.filters b/tests/ExceptionTest/ExceptionTest/ExceptionTest.vcxproj.filters deleted file mode 100644 index afdc275..0000000 --- a/tests/ExceptionTest/ExceptionTest/ExceptionTest.vcxproj.filters +++ /dev/null @@ -1,27 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Header Files - - - - - Source Files - - - \ No newline at end of file diff --git a/tests/ExceptionTest/ExceptionTest/main.cpp b/tests/ExceptionTest/ExceptionTest/main.cpp deleted file mode 100644 index 0413d93..0000000 --- a/tests/ExceptionTest/ExceptionTest/main.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include -#include -#include - -#pragma comment(lib, "ntdll.lib") - -extern "C" -NTSYSCALLAPI -NTSTATUS -NTAPI -NtDisplayString( - PUNICODE_STRING String -); - -#define WIDEN_EXPAND(str) L ## str -#define WIDEN(str) WIDEN_EXPAND(str) - -// Helper function to directly call NtDisplayString with a string -// This simplifies the trace output of dumpulator -template -void debugPrint(const wchar_t(&str)[Count]) -{ - UNICODE_STRING ustr{ (Count - 1) * 2, Count * 2, (PWSTR)str }; - NtDisplayString(&ustr); -} - -static LONG WINAPI VectoredHandler(struct _EXCEPTION_POINTERS* ExceptionInfo) -{ - debugPrint(WIDEN(__FUNCTION__)); - return EXCEPTION_CONTINUE_SEARCH; -} - -static LONG WINAPI ContinueHandler(struct _EXCEPTION_POINTERS* ExceptionInfo) -{ - debugPrint(WIDEN(__FUNCTION__)); - return EXCEPTION_CONTINUE_SEARCH; -} - -static LPTOP_LEVEL_EXCEPTION_FILTER previousFilter; - -static LONG WINAPI ExceptionFilter(struct _EXCEPTION_POINTERS* ExceptionInfo) -{ - debugPrint(WIDEN(__FUNCTION__)); - if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT) - { -#ifdef _WIN64 - ExceptionInfo->ContextRecord->Rip++; -#else - ExceptionInfo->ContextRecord->Eip++; -#endif // _WIN64 - return EXCEPTION_CONTINUE_EXECUTION; - } - return previousFilter(ExceptionInfo); -} - -static int __try_filter(unsigned int code, struct _EXCEPTION_POINTERS* ExceptionInfo) -{ - debugPrint(WIDEN(__FUNCTION__)); - const auto& er = *ExceptionInfo->ExceptionRecord; - if (er.ExceptionCode == EXCEPTION_ACCESS_VIOLATION && er.ExceptionInformation[1] == 0xDEADF00D) - { - return EXCEPTION_EXECUTE_HANDLER; - } - return EXCEPTION_CONTINUE_SEARCH; -} - -int main() -{ - debugPrint(L"Test VEH, SEH, VCH"); - AddVectoredExceptionHandler(1, VectoredHandler); - AddVectoredContinueHandler(1, ContinueHandler); - - __try - { - *((size_t*)(uintptr_t)0xDEADF00D) = 0; - } - __except (__try_filter(GetExceptionCode(), GetExceptionInformation())) - { - debugPrint(L"__except handler"); - } - - debugPrint(L"Test SetUnhandledExceptionFilter"); - previousFilter = SetUnhandledExceptionFilter(ExceptionFilter); - __debugbreak(); -} \ No newline at end of file diff --git a/tests/HandleTest/HandleTest.sln b/tests/HandleTest/HandleTest.sln deleted file mode 100644 index f66a77a..0000000 --- a/tests/HandleTest/HandleTest.sln +++ /dev/null @@ -1,31 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.2.32505.173 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "HandleTest", "HandleTest\HandleTest.vcxproj", "{1E035170-B8A3-4C3C-A5A7-CD45FE2C141B}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {1E035170-B8A3-4C3C-A5A7-CD45FE2C141B}.Debug|x64.ActiveCfg = Debug|x64 - {1E035170-B8A3-4C3C-A5A7-CD45FE2C141B}.Debug|x64.Build.0 = Debug|x64 - {1E035170-B8A3-4C3C-A5A7-CD45FE2C141B}.Debug|x86.ActiveCfg = Debug|Win32 - {1E035170-B8A3-4C3C-A5A7-CD45FE2C141B}.Debug|x86.Build.0 = Debug|Win32 - {1E035170-B8A3-4C3C-A5A7-CD45FE2C141B}.Release|x64.ActiveCfg = Release|x64 - {1E035170-B8A3-4C3C-A5A7-CD45FE2C141B}.Release|x64.Build.0 = Release|x64 - {1E035170-B8A3-4C3C-A5A7-CD45FE2C141B}.Release|x86.ActiveCfg = Release|Win32 - {1E035170-B8A3-4C3C-A5A7-CD45FE2C141B}.Release|x86.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {9B7A3204-EA43-45C2-BE70-29FAD9A04785} - EndGlobalSection -EndGlobal diff --git a/tests/HandleTest/HandleTest/HandleTest.vcxproj b/tests/HandleTest/HandleTest/HandleTest.vcxproj deleted file mode 100644 index 3bf3dfd..0000000 --- a/tests/HandleTest/HandleTest/HandleTest.vcxproj +++ /dev/null @@ -1,149 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 16.0 - Win32Proj - {1e035170-b8a3-4c3c-a5a7-cd45fe2c141b} - HandleTest - 10.0 - - - - Application - true - v143 - Unicode - - - Application - false - v143 - true - Unicode - - - Application - true - v143 - Unicode - - - Application - false - v143 - true - Unicode - - - - - - - - - - - - - - - - - - - - - - Level3 - true - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpplatest - - - Console - true - - - - - Level3 - true - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpplatest - Disabled - NoExtensions - MultiThreaded - - - Console - true - true - true - /DYNAMICBASE:NO %(AdditionalOptions) - false - - - - - Level3 - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpplatest - - - Console - true - - - - - Level3 - true - true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpplatest - Disabled - NoExtensions - MultiThreaded - - - Console - true - true - true - /DYNAMICBASE:NO %(AdditionalOptions) - false - - - - - - - - - \ No newline at end of file diff --git a/tests/HandleTest/HandleTest/HandleTest.vcxproj.filters b/tests/HandleTest/HandleTest/HandleTest.vcxproj.filters deleted file mode 100644 index decff76..0000000 --- a/tests/HandleTest/HandleTest/HandleTest.vcxproj.filters +++ /dev/null @@ -1,22 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Source Files - - - \ No newline at end of file diff --git a/tests/HandleTest/HandleTest/main.c b/tests/HandleTest/HandleTest/main.c deleted file mode 100644 index dacecc9..0000000 --- a/tests/HandleTest/HandleTest/main.c +++ /dev/null @@ -1,139 +0,0 @@ -#define _CRT_SECURE_NO_WARNINGS -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -void console_output_test() -{ - printf( "Console output test\n" ); -} - -void read_file_test() -{ - FILE* fp = NULL; - char* buffer = NULL; - - fp = fopen( "./test_file.txt", "r" ); - - if( fp ) - { - fseek( fp, 0, SEEK_END ); - long len = ftell( fp ); - fseek( fp, 0, SEEK_SET ); - buffer = ( char* )malloc( len ); - if( buffer ) - { - fread( buffer, 1, len, fp ); - } - fclose( fp ); - } - - if( buffer ) - { - printf( "read_file_test: %s\n", buffer ); - } -} - -void write_file_test() -{ - FILE* fp = NULL; - char* buffer = NULL; - - fp = fopen( "./test_file.txt", "w+" ); - - if( fp ) - { - rewind( fp ); - fprintf( fp, "testing write file\n" ); - - fseek( fp, 0, SEEK_END ); - long len = ftell( fp ); - fseek( fp, 0, SEEK_SET ); - buffer = ( char* )malloc( len ); - if( buffer ) - { - fread( buffer, 1, len, fp ); - } - fclose( fp ); - } - - if( buffer ) - { - printf( "write_file_test: %s\n", buffer ); - } -} - -void write_file_offset_test() -{ - FILE* fp = NULL; - char* buffer = NULL; - - fp = fopen( "./test_file.txt", "w+" ); - - if( fp ) - { - fseek( fp, 5, SEEK_SET ); - fprintf( fp, "--writing file offset--\n" ); - - fseek( fp, 0, SEEK_END ); - long len = ftell( fp ); - fseek( fp, 0, SEEK_SET ); - buffer = ( char* )malloc( len ); - if( buffer ) - { - fread( buffer, 1, len, fp ); - } - fclose( fp ); - } - - if( buffer ) - { - printf( "write_file_test: %s\n", buffer ); - } -} - -void create_file_test() -{ - FILE* fp = NULL; - char* buffer = NULL; - - fp = fopen( "./nonexistant_file.txt", "w+" ); - - if( fp ) - { - rewind( fp ); - fprintf( fp, "testing creating and writing to a file\n" ); - - fseek( fp, 0, SEEK_END ); - long len = ftell( fp ); - fseek( fp, 0, SEEK_SET ); - buffer = ( char* )malloc( len ); - if( buffer ) - { - fread( buffer, 1, len, fp ); - } - fclose( fp ); - } - - if( buffer ) - { - printf( "create_file_test: %s\n", buffer ); - } -} - -int main() -{ - console_output_test(); - read_file_test(); - write_file_test(); - write_file_offset_test(); - create_file_test(); - return 0; -} - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/tests/handle-test32.py b/tests/handle-test32.py deleted file mode 100644 index 501c49c..0000000 --- a/tests/handle-test32.py +++ /dev/null @@ -1,21 +0,0 @@ -from dumpulator import Dumpulator -from dumpulator.native import * - -def main(): - dp = Dumpulator("TestHarness_x86.dmp") - - dp.handles.create_file("test_file.txt", FILE_OPEN) - dp.handles.create_file("nonexistant_file.txt", FILE_CREATE) - - with open("TestHarness/bin/HandleTest_x86.dll", "rb") as dll: - dll_data = dll.read() - - dp.map_module(dll_data, "HandleTest_x86.dll") - - for export in dp.modules["HandleTest_x86.dll"].exports: - print(f"\n---- calling {export.name} ----\n") - dp.call(export.address) - - -if __name__ == '__main__': - main() diff --git a/tests/handle-test64.py b/tests/handle-test64.py deleted file mode 100644 index 49fc5c3..0000000 --- a/tests/handle-test64.py +++ /dev/null @@ -1,21 +0,0 @@ -from dumpulator import Dumpulator -from dumpulator.native import * - -def main(): - dp = Dumpulator("TestHarness_x64.dmp") - - dp.handles.create_file("test_file.txt", FILE_OPEN) - dp.handles.create_file("nonexistant_file.txt", FILE_CREATE) - - with open("TestHarness/bin/HandleTest_x64.dll", "rb") as dll: - dll_data = dll.read() - - dp.map_module(dll_data, "HandleTest_x64.dll") - - for export in dp.modules["HandleTest_x64.dll"].exports: - print(f"\n---- calling {export.name} ----\n") - dp.call(export.address) - - -if __name__ == '__main__': - main() diff --git a/tests/handle_dmp_test.py b/tests/handle_dmp_test.py deleted file mode 100644 index cb493ab..0000000 --- a/tests/handle_dmp_test.py +++ /dev/null @@ -1,53 +0,0 @@ -import unittest -from dumpulator import Dumpulator -from dumpulator.native import * - - -class TestHandleManagerx64(unittest.TestCase): - dp = None - - @classmethod - def setUp(cls): - cls.dp = Dumpulator("TestHarness_x64.dmp", quiet=True) - with open("TestHarness/bin/HandleTest_x64.dll", "rb") as dll: - dll_data = dll.read() - cls.dp.map_module(dll_data, "HandleTest_x64.dll") - - def test_write_create_file(self): - self.dp.handles.create_file("nonexistant_file.txt", FILE_CREATE) - test_func = self.dp.modules["HandleTest_x64.dll"].find_export("WriteAndCreateFileTest") - ret_val = self.dp.call(test_func.address) - self.assertEqual(ret_val, 1) - - def test_read_file(self): - self.dp.handles.create_file("test_file.txt", FILE_OPEN) - test_func = self.dp.modules["HandleTest_x64.dll"].find_export("ReadFileTest") - ret_val = self.dp.call(test_func.address) - self.assertEqual(ret_val, 1) - - -class TestHandleManagerx86(unittest.TestCase): - dp = None - - @classmethod - def setUp(cls): - cls.dp = Dumpulator("TestHarness_x86.dmp", quiet=True) - with open("TestHarness/bin/HandleTest_x86.dll", "rb") as dll: - dll_data = dll.read() - cls.dp.map_module(dll_data, "HandleTest_x86.dll") - - def test_write_create_file(self): - self.dp.handles.create_file("nonexistant_file.txt", FILE_CREATE) - test_func = self.dp.modules["HandleTest_x86.dll"].find_export("WriteAndCreateFileTest") - ret_val = self.dp.call(test_func.address) - self.assertEqual(ret_val, 1) - - def test_read_file(self): - self.dp.handles.create_file("test_file.txt", FILE_OPEN) - test_func = self.dp.modules["HandleTest_x86.dll"].find_export("ReadFileTest") - ret_val = self.dp.call(test_func.address) - self.assertEqual(ret_val, 1) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/harness-tests.py b/tests/harness-tests.py new file mode 100644 index 0000000..454e7a8 --- /dev/null +++ b/tests/harness-tests.py @@ -0,0 +1,69 @@ +import sys +import inspect +from typing import Dict, List, Type + +from dumpulator import Dumpulator +from dumpulator.native import * +from dumpulator.modules import Module +import pefile + +class TestEnvironment: + def setup(self, dp: Dumpulator): + pass + +class HandleEnvironment(TestEnvironment): + def setup(self, dp: Dumpulator): + dp.handles.create_file("test_file.txt", FILE_OPEN) + dp.handles.create_file("nonexistent_file.txt", FILE_CREATE) + +def collect_environments(): + environments: Dict[str, Type[TestEnvironment]] = {} + for name, obj in inspect.getmembers(sys.modules[__name__], inspect.isclass): + if issubclass(obj, TestEnvironment) and not obj is TestEnvironment: + prefix = "" + for i in range(len(name)): + ch = name[i] + if i > 0 and ch.isupper(): + break + prefix += ch + environments[prefix] = obj + return environments + +def collect_tests(dll_data): + pe = pefile.PE(data=dll_data, fast_load=True) + module = Module(pe, "tests.dll") + tests: Dict[str, List[str]] = {} + for export in module.exports: + assert "_" in export.name, f"Invalid test export '{export.name}'" + prefix = export.name.split("_")[0] + if prefix not in tests: + tests[prefix] = [] + tests[prefix].append(export.name) + return tests, module.base + +def run_tests(dll_path: str, harness_dump: str): + print(f"--- {dll_path} ---") + with open(dll_path, "rb") as dll: + dll_data = dll.read() + environments = collect_environments() + tests, base = collect_tests(dll_data) + for prefix, exports in tests.items(): + print(f"\nRunning {prefix.lower()} tests:") + environment = environments.get(prefix, TestEnvironment) + for export in exports: + dp = Dumpulator(harness_dump, trace=True) + module = dp.map_module(dll_data, dll_path, base) + environment().setup(dp) + test = module.find_export(export) + assert test is not None + print(f"--- Executing {test.name} at {hex(test.address)} ---") + success = dp.call(test.address) + print(f"{export} -> {success}") + +def main(): + run_tests("DumpulatorTests/bin/Tests_x64.dll", "HarnessMinimal_x64.dmp") + print("") + run_tests("DumpulatorTests/bin/Tests_x86.dll", "HarnessMinimal_x86.dmp") + +if __name__ == "__main__": + main() From cf82a6f75429bf31b8528cab37f7c8a0bfc75881 Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Tue, 18 Oct 2022 01:07:30 +0200 Subject: [PATCH 03/17] Fix a bug when fetching instructions from unmapped memory --- src/dumpulator/dumpulator.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/dumpulator/dumpulator.py b/src/dumpulator/dumpulator.py index fcff463..2369e01 100644 --- a/src/dumpulator/dumpulator.py +++ b/src/dumpulator/dumpulator.py @@ -855,9 +855,13 @@ def _hook_code_exception(uc: Uc, address, size, dp: Dumpulator): raise err def _hook_mem(uc: Uc, access, address, size, value, dp: Dumpulator): + fetch_accesses = [UC_MEM_FETCH, UC_MEM_FETCH_PROT, UC_MEM_FETCH_UNMAPPED] if access == UC_MEM_FETCH_UNMAPPED and address >= FORCE_KILL_ADDR - 0x10 and address <= FORCE_KILL_ADDR + 0x10 and dp.kill_me is not None: dp.error(f"forced exit memory operation {access} of {address:x}[{size:x}] = {value:X}") return False + if dp.exception.final and access in fetch_accesses: + dp.info(f"fetch from {hex(address)}[{size}] already reported") + return False # TODO: figure out why when you start executing at 0 this callback is triggered more than once try: # Extract exception information @@ -868,10 +872,11 @@ def _hook_mem(uc: Uc, access, address, size, value, dp: Dumpulator): exception.memory_size = size exception.memory_value = value exception.context = uc.context_save() - tb = uc.ctl_request_cache(dp.regs.cip) - exception.tb_start = tb.pc - exception.tb_size = tb.size - exception.tb_icount = tb.icount + if access not in fetch_accesses: + tb = uc.ctl_request_cache(dp.regs.cip) + exception.tb_start = tb.pc + exception.tb_size = tb.size + exception.tb_icount = tb.icount # Print exception info final = dp.trace or dp.exception.code_hook_h is not None @@ -922,7 +927,8 @@ def _hook_mem(uc: Uc, access, address, size, value, dp: Dumpulator): # Remove the translation block cache for this block # Without doing this single stepping the block won't work - uc.ctl_remove_cache(exception.tb_start, exception.tb_start + exception.tb_size) + if exception.tb_start != 0: + uc.ctl_remove_cache(exception.tb_start, exception.tb_start + exception.tb_size) # Install the code hook to single step the basic block again. # This will prevent translation block caching and give us the correct cip @@ -1059,6 +1065,7 @@ def _hook_interrupt(uc: Uc, number, dp: Dumpulator): exception.type = ExceptionType.Interrupt exception.interrupt_number = number exception.context = uc.context_save() + # TODO: this might crash if cip is not valid memory tb = uc.ctl_request_cache(dp.regs.cip) exception.tb_start = tb.pc exception.tb_size = tb.size From e4d785becd61bcbad82b6f9104c198ccd3bb4e4a Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Tue, 18 Oct 2022 11:33:17 +0200 Subject: [PATCH 04/17] Fix paths with / in them for modules --- src/dumpulator/modules.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dumpulator/modules.py b/src/dumpulator/modules.py index 162e35b..889d2ad 100644 --- a/src/dumpulator/modules.py +++ b/src/dumpulator/modules.py @@ -14,6 +14,7 @@ def __init__(self, address: int, ordinal: int, name: str, forward: Tuple[str, st class Module: def __init__(self, pe: pefile.PE, path: str): self.pe = pe + path = path.replace("/", "\\") self.path = path self.name = path.split("\\")[-1] self._exports_by_address: Dict[int, int] = {} From f2d43f21d808b735979e745aa0e3820efa24d253 Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Wed, 19 Oct 2022 16:45:36 +0200 Subject: [PATCH 05/17] Add a DllMain to the DumpulatorTests to make things easier --- tests/DumpulatorTests/Tests/DllMain.cpp | 6 ++++++ tests/DumpulatorTests/Tests/Tests.vcxproj | 5 +++-- tests/DumpulatorTests/Tests/Tests.vcxproj.filters | 3 +++ 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 tests/DumpulatorTests/Tests/DllMain.cpp diff --git a/tests/DumpulatorTests/Tests/DllMain.cpp b/tests/DumpulatorTests/Tests/DllMain.cpp new file mode 100644 index 0000000..f6776ed --- /dev/null +++ b/tests/DumpulatorTests/Tests/DllMain.cpp @@ -0,0 +1,6 @@ +#include + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + return TRUE; +} \ No newline at end of file diff --git a/tests/DumpulatorTests/Tests/Tests.vcxproj b/tests/DumpulatorTests/Tests/Tests.vcxproj index b5b2fe7..071c2c2 100644 --- a/tests/DumpulatorTests/Tests/Tests.vcxproj +++ b/tests/DumpulatorTests/Tests/Tests.vcxproj @@ -11,6 +11,7 @@ + @@ -82,9 +83,9 @@ /DYNAMICBASE:NO %(AdditionalOptions) true exception_handler_x86.lib;ntdll.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - true false Default + DllMain @@ -106,8 +107,8 @@ /DYNAMICBASE:NO %(AdditionalOptions) true exception_handler_x64.lib;ntdll.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - true Default + DllMain diff --git a/tests/DumpulatorTests/Tests/Tests.vcxproj.filters b/tests/DumpulatorTests/Tests/Tests.vcxproj.filters index ddf6038..558c777 100644 --- a/tests/DumpulatorTests/Tests/Tests.vcxproj.filters +++ b/tests/DumpulatorTests/Tests/Tests.vcxproj.filters @@ -21,6 +21,9 @@ Source Files + + Source Files + From affe0600c5d5d7ff97298a7447349060dd39f29f Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Wed, 19 Oct 2022 16:46:19 +0200 Subject: [PATCH 06/17] Clarify the code that's extracting the prefix from the test environments --- tests/harness-tests.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/harness-tests.py b/tests/harness-tests.py index 454e7a8..742bd65 100644 --- a/tests/harness-tests.py +++ b/tests/harness-tests.py @@ -1,3 +1,4 @@ +import re import sys import inspect from typing import Dict, List, Type @@ -20,12 +21,10 @@ def collect_environments(): environments: Dict[str, Type[TestEnvironment]] = {} for name, obj in inspect.getmembers(sys.modules[__name__], inspect.isclass): if issubclass(obj, TestEnvironment) and not obj is TestEnvironment: - prefix = "" - for i in range(len(name)): - ch = name[i] - if i > 0 and ch.isupper(): - break - prefix += ch + # Extract the first capital word from the class name + match = re.match(r"^([A-Z][a-z]+)", name) + assert match is not None + prefix = match.group(1) environments[prefix] = obj return environments From 2c988b3ed693a3fddb9b4edf66756bd21bdb77df Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Wed, 23 Nov 2022 13:38:19 +0100 Subject: [PATCH 07/17] Correctly map WRITECOMBINE, NOCACHE and GUARD pages to unicorn --- src/dumpulator/details.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/dumpulator/details.py b/src/dumpulator/details.py index af13b8e..c647fe9 100644 --- a/src/dumpulator/details.py +++ b/src/dumpulator/details.py @@ -11,6 +11,7 @@ def map_unicorn_perms(protect: MemoryProtect): if isinstance(protect, int): protect = MemoryProtect(protect) assert isinstance(protect, MemoryProtect) + baseprotect = protect & ~(MemoryProtect.PAGE_WRITECOMBINE | MemoryProtect.PAGE_NOCACHE | MemoryProtect.PAGE_GUARD) mapping = { MemoryProtect.PAGE_EXECUTE: UC_PROT_EXEC | UC_PROT_READ, MemoryProtect.PAGE_EXECUTE_READ: UC_PROT_EXEC | UC_PROT_READ, @@ -21,7 +22,10 @@ def map_unicorn_perms(protect: MemoryProtect): MemoryProtect.PAGE_READWRITE: UC_PROT_READ | UC_PROT_WRITE, MemoryProtect.PAGE_WRITECOPY: UC_PROT_READ | UC_PROT_WRITE, } - return mapping[protect] + perms = mapping[baseprotect] + if protect & MemoryProtect.PAGE_GUARD: + perms = UC_PROT_NONE + return perms class Registers: From 7e0cbcddcf61fa70729ab374505778d61d8ee929 Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Wed, 23 Nov 2022 13:40:11 +0100 Subject: [PATCH 08/17] Fix a crash when printing combined page protection --- src/dumpulator/dumpulator.py | 4 ++-- src/dumpulator/memory.py | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/dumpulator/dumpulator.py b/src/dumpulator/dumpulator.py index 2369e01..260d7cd 100644 --- a/src/dumpulator/dumpulator.py +++ b/src/dumpulator/dumpulator.py @@ -139,7 +139,7 @@ def print_memory(self): protect = region.protect if region.state == MemoryState.MEM_RESERVE: protect = region.allocation_protect - entry[3] = protect.name + entry[3] = str(protect) if isinstance(region.info, Module): module: Module = region.info entry[4] = f" {module.name}[{hex(module.size)}]" @@ -813,7 +813,7 @@ def map_module(self, file_data: bytes, file_path: str = "", requested_base: int protect = MemoryProtect.PAGE_READWRITE if execute: protect = MemoryProtect(protect.value << 4) - print(f"Mapping section '{name.decode()}' {hex(rva)}[{hex(rva)}] -> {hex(va)} as {protect.name}") + print(f"Mapping section '{name.decode()}' {hex(rva)}[{hex(rva)}] -> {hex(va)} as {protect}") self.memory.commit(va, size, protect) self.write(va, data) diff --git a/src/dumpulator/memory.py b/src/dumpulator/memory.py index ddcedc1..f9d87e9 100644 --- a/src/dumpulator/memory.py +++ b/src/dumpulator/memory.py @@ -19,6 +19,12 @@ class MemoryProtect(Flag): PAGE_NOCACHE = 0x200 PAGE_WRITECOMBINE = 0x400 + def __str__(self): + result = self.name + if result is None: + result = super().__str__().replace(f"{self.__class__.__name__}.", "") + return result + class MemoryType(Enum): UNDEFINED = 0 MEM_IMAGE = 0x1000000 From cd8edb8de7b4d6e14f2acb10aea319a55d7e25b6 Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Wed, 23 Nov 2022 13:40:47 +0100 Subject: [PATCH 09/17] Correctly mask addresses on 32-bit dumps --- src/dumpulator/dumpulator.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/dumpulator/dumpulator.py b/src/dumpulator/dumpulator.py index 260d7cd..56a9752 100644 --- a/src/dumpulator/dumpulator.py +++ b/src/dumpulator/dumpulator.py @@ -191,7 +191,10 @@ def _setup_gdt(self): def _setup_memory(self): info: minidump.MinidumpMemoryInfo regions: List[List[minidump.MinidumpMemoryInfo]] = [] + mask = 0xFFFFFFFFFFFFFFFF if self._x64 else 0xFFFFFFFF for info in self._minidump.memory_info.infos: + info.AllocationBase &= mask + info.BaseAddress &= mask if len(regions) == 0 or info.AllocationBase != regions[-1][0].AllocationBase or info.State == minidump.MemoryState.MEM_FREE: regions.append([]) regions[-1].append(info) From e91d0941360a36f3ac0a356ac3b53dba01702eef Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Wed, 23 Nov 2022 13:41:17 +0100 Subject: [PATCH 10/17] Improve ZwAllocateVirtualMemory implementation --- src/dumpulator/dumpulator.py | 2 +- src/dumpulator/native.py | 3 +++ src/dumpulator/ntprimitives.py | 2 +- src/dumpulator/ntsyscalls.py | 13 +++++++++---- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/dumpulator/dumpulator.py b/src/dumpulator/dumpulator.py index 56a9752..0c5feee 100644 --- a/src/dumpulator/dumpulator.py +++ b/src/dumpulator/dumpulator.py @@ -524,7 +524,7 @@ def handle_exception(self): self.exception.handling = True if self.exception.type == ExceptionType.ContextSwitch: - self.info(f"switching context, cip: {self.regs.cip}") + self.info(f"switching context, cip: {hex(self.regs.cip)}") # Clear the pending exception self.last_exception = self.exception self.exception = ExceptionInfo() diff --git a/src/dumpulator/native.py b/src/dumpulator/native.py index 849fbbe..2875f59 100644 --- a/src/dumpulator/native.py +++ b/src/dumpulator/native.py @@ -18,6 +18,9 @@ STATUS_INFO_LENGTH_MISMATCH = 0xC0000004 STATUS_INVALID_PARAMETER = 0xC000000D STATUS_OBJECT_NAME_NOT_FOUND = 0xC0000034 +STATUS_NOT_FOUND = 0xC0000225 +STATUS_MEMORY_NOT_ALLOCATED = 0xC00000A0 +STATUS_CONFLICTING_ADDRESSES = 0xC0000018 # Exceptions DBG_PRINTEXCEPTION_C = 0x40010006 diff --git a/src/dumpulator/ntprimitives.py b/src/dumpulator/ntprimitives.py index f9b0158..ce257a5 100644 --- a/src/dumpulator/ntprimitives.py +++ b/src/dumpulator/ntprimitives.py @@ -4,7 +4,7 @@ from enum import Enum class Architecture(object): - def __init__(self, x64): + def __init__(self, x64: bool): self._x64 = x64 def ptr_size(self): diff --git a/src/dumpulator/ntsyscalls.py b/src/dumpulator/ntsyscalls.py index 31e21c8..2ef872c 100644 --- a/src/dumpulator/ntsyscalls.py +++ b/src/dumpulator/ntsyscalls.py @@ -312,20 +312,25 @@ def ZwAllocateVirtualMemory(dp: Dumpulator, protect = MemoryProtect(Protect) if AllocationType == MEM_COMMIT: if base == 0: - base = dp.allocate(size, True) - print(f"commit({hex(base)}[{hex(size)}])") + base = dp.memory.find_free(size) + dp.memory.reserve(base, size, protect) + BaseAddress.write_ptr(base) + RegionSize.write_ptr(size) + print(f"commit({hex(base)}[{hex(size)}], {protect})") dp.memory.commit(base, size, protect) elif AllocationType == MEM_RESERVE: if base == 0: base = dp.memory.find_free(size) BaseAddress.write_ptr(base) - print(f"reserve({hex(base)}[{hex(size)}])") + RegionSize.write_ptr(size) + print(f"reserve({hex(base)}[{hex(size)}], {protect})") dp.memory.reserve(base, size, protect) elif AllocationType == MEM_COMMIT | MEM_RESERVE: if base == 0: base = dp.memory.find_free(size) BaseAddress.write_ptr(base) - print(f"reserve+commit({hex(base)}[{hex(size)}])") + RegionSize.write_ptr(size) + print(f"reserve+commit({hex(base)}[{hex(size)}], {protect})") dp.memory.reserve(base, size, protect) dp.memory.commit(base, size) else: From f41fc2bc4502b937fc1549c05e07b5a682970fc4 Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Wed, 23 Nov 2022 13:42:21 +0100 Subject: [PATCH 11/17] Implement ZwFreeVirtualMemory --- src/dumpulator/ntsyscalls.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/dumpulator/ntsyscalls.py b/src/dumpulator/ntsyscalls.py index 2ef872c..64927cf 100644 --- a/src/dumpulator/ntsyscalls.py +++ b/src/dumpulator/ntsyscalls.py @@ -1747,7 +1747,18 @@ def ZwFreeVirtualMemory(dp: Dumpulator, RegionSize: P(SIZE_T), FreeType: ULONG ): - raise NotImplementedError() + base = BaseAddress.read_ptr() + size = RegionSize.read_ptr() + if FreeType == MEM_RELEASE: + print(f"release {hex(base)}[{hex(size)}]") + assert size == 0 + region = dp.memory.find_region(base) + if region is None: + return STATUS_MEMORY_NOT_ALLOCATED + dp.memory.release(base) + return STATUS_SUCCESS + else: + raise NotImplementedError() @syscall def ZwFreezeRegistry(dp: Dumpulator, From a98fa852d19f025e0e0fd9ac7a9099ebe2a5195a Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Wed, 23 Nov 2022 13:42:58 +0100 Subject: [PATCH 12/17] Rudimentary support for ZwCreateEvent --- src/dumpulator/handles.py | 16 ++++++++++++---- src/dumpulator/ntsyscalls.py | 7 ++++++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/dumpulator/handles.py b/src/dumpulator/handles.py index a1ca826..59024ca 100644 --- a/src/dumpulator/handles.py +++ b/src/dumpulator/handles.py @@ -33,7 +33,7 @@ def write(self, buffer: bytes, size: Optional[int] = None): # TODO: store file access flags to handle access violations # currently overwrites data given offset and buffer size, does not overwrite with zeros with different - # creation options + # creation options # incase input size differs from actual buffer size if self.data is None: if size is not None: @@ -54,7 +54,7 @@ def write(self, buffer: bytes, size: Optional[int] = None): class SectionObject: def __init__(self, file: FileObject): self.file = file - + def __str__(self): return f"{type(self).__name__}({self.file})" @@ -77,11 +77,19 @@ def __str__(self): def io_control(self, dp, code: int, data: bytes) -> bytes: raise NotImplementedError() +class EventObject: + def __init__(self, type: EVENT_TYPE, signalled: bool): + self.type = type + self.signalled = signalled + + def __str__(self): + return f"{type(self).__name__}(type: {self.type.name}, signalled: {self.signalled})" + class RegistryKeyObject: def __init__(self, key: str, values: Dict[str, Any] = {}): self.key = key self.values = values - + def __str__(self): return f"{type(self).__name__}({self.key})" @@ -146,7 +154,7 @@ def duplicate(self, handle_value: int) -> int: def map_file(self, filename: str, handle_data: Any): self.mapped_files[filename] = handle_data - + def open_file(self, filename: str): data = self.mapped_files.get(filename, None) if data is None: diff --git a/src/dumpulator/ntsyscalls.py b/src/dumpulator/ntsyscalls.py index 64927cf..f0bffb8 100644 --- a/src/dumpulator/ntsyscalls.py +++ b/src/dumpulator/ntsyscalls.py @@ -835,7 +835,12 @@ def ZwCreateEvent(dp: Dumpulator, EventType: EVENT_TYPE, InitialState: BOOLEAN ): - raise NotImplementedError() + assert DesiredAccess == 0x1f0003 + assert ObjectAttributes == 0 + event = EventObject(EventType, InitialState) + handle = dp.handles.new(event) + EventHandle.write_ptr(handle) + return STATUS_SUCCESS @syscall def ZwCreateEventPair(dp: Dumpulator, From 4580548529c869c8290359eda2071961e204363b Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Wed, 23 Nov 2022 13:44:51 +0100 Subject: [PATCH 13/17] Fix a crash when trying to find a module address that's not loaded --- src/dumpulator/modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dumpulator/modules.py b/src/dumpulator/modules.py index 889d2ad..0051993 100644 --- a/src/dumpulator/modules.py +++ b/src/dumpulator/modules.py @@ -93,7 +93,7 @@ def add(self, pe: pefile.PE, path: str): def find(self, key: Union[str, int]) -> Optional[Module]: if isinstance(key, int): region = self._memory.find_region(key) - if region.info: + if region is not None and region.info: assert isinstance(region.info, Module) return region.info return None From 9eb2ab90b617f5f86c70f54d5d54b09f451ba362 Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Wed, 23 Nov 2022 13:45:31 +0100 Subject: [PATCH 14/17] Do not resolve imports in ZwMapViewOfSection --- src/dumpulator/dumpulator.py | 2 +- src/dumpulator/ntsyscalls.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dumpulator/dumpulator.py b/src/dumpulator/dumpulator.py index 0c5feee..4ed2b03 100644 --- a/src/dumpulator/dumpulator.py +++ b/src/dumpulator/dumpulator.py @@ -726,7 +726,7 @@ def NtCurrentProcess(self): def NtCurrentThread(self): return 0xFFFFFFFFFFFFFFFE if self._x64 else 0xFFFFFFFE - def map_module(self, file_data: bytes, file_path: str = "", requested_base: int = 0): + def map_module(self, file_data: bytes, file_path: str = "", requested_base: int = 0, resolve_imports = True): if not file_path: file_path = "" print(f"Mapping module {file_path}") diff --git a/src/dumpulator/ntsyscalls.py b/src/dumpulator/ntsyscalls.py index f0bffb8..bb69ae8 100644 --- a/src/dumpulator/ntsyscalls.py +++ b/src/dumpulator/ntsyscalls.py @@ -2147,7 +2147,7 @@ def ZwMapViewOfSection(dp: Dumpulator, assert requested_base == 0 section = dp.handles.get(SectionHandle, SectionObject) data = section.file.read() - module = dp.map_module(data, section.file.path, requested_base) + module = dp.map_module(data, section.file.path, requested_base, False) # Handle out parameters BaseAddress.write_ptr(module.base) From 569577f1480c73e89e7442c243594b0a3095f28e Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Wed, 23 Nov 2022 13:46:18 +0100 Subject: [PATCH 15/17] Flush the trace every syscall to make debugging easier --- src/dumpulator/dumpulator.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/dumpulator/dumpulator.py b/src/dumpulator/dumpulator.py index 4ed2b03..91cce49 100644 --- a/src/dumpulator/dumpulator.py +++ b/src/dumpulator/dumpulator.py @@ -1100,6 +1100,9 @@ def _hook_interrupt(uc: Uc, number, dp: Dumpulator): raise UcError(UC_ERR_EXCEPTION) def _hook_syscall(uc: Uc, dp: Dumpulator): + # Flush the trace for easier debugging + if dp.trace is not None: + dp.trace.flush() index = dp.regs.cax & 0xffff if index < len(dp.syscalls): name, syscall_impl, argcount = dp.syscalls[index] From 86f2c50f97b6cb32d4c40e22914afa3e466b91d3 Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Wed, 23 Nov 2022 13:47:26 +0100 Subject: [PATCH 16/17] Allow emulating unsupported instructions Currently only RDRAND is implemented --- src/dumpulator/dumpulator.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/dumpulator/dumpulator.py b/src/dumpulator/dumpulator.py index 91cce49..57140dd 100644 --- a/src/dumpulator/dumpulator.py +++ b/src/dumpulator/dumpulator.py @@ -18,7 +18,7 @@ from .memory import * from .modules import * from capstone import * -from capstone.x86_const import * +from capstone.x86 import * syscall_functions = {} @@ -1162,6 +1162,21 @@ def syscall_arg(index): dp.error(f"syscall index {index:x} out of range") dp.raise_kill(IndexError()) +def _emulate_unsupported_instruction(dp: Dumpulator, instr: CsInsn): + if instr.id == X86_INS_RDRAND: + op: X86Op = instr.operands[0] + regname = instr.reg_name(op.reg) + if dp._x64 and op.size * 8 == 32: + regname = "r" + regname[1:] + print(f"emulated rdrand {regname}:{op.size * 8}, cip = {hex(instr.address)}+{instr.size}") + dp.regs[regname] = 42 # TODO: PRNG based on dmp hash + dp.regs.cip += instr.size + else: + # Unsupported instruction + return False + # Resume execution + return True + def _hook_invalid(uc: Uc, dp: Dumpulator): address = dp.regs.cip # HACK: unicorn cannot gracefully exit in all contexts @@ -1169,4 +1184,20 @@ def _hook_invalid(uc: Uc, dp: Dumpulator): dp.error(f"terminating emulation...") return False dp.error(f"invalid instruction at {address:x}") + try: + code = dp.read(address, 15) + instr = next(dp.cs.disasm(code, address, 1)) + if _emulate_unsupported_instruction(dp, instr): + # Resume execution with a context switch + assert dp.exception.type == ExceptionType.NoException + exception = ExceptionInfo() + exception.type = ExceptionType.ContextSwitch + exception.final = True + dp.exception = exception + return False # NOTE: returning True would stop emulation + except StopIteration: + pass # Unsupported instruction + except UcError: + pass # Invalid memory access (NOTE: this should not be possible actually) + raise NotImplementedError("TODO: throw invalid instruction exception") return False From d17fd3b0a74f1abf5ae153f2687f02f6c37a2866 Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Wed, 23 Nov 2022 15:23:25 +0100 Subject: [PATCH 17/17] WIP: continue implementing exceptions --- src/dumpulator/dumpulator.py | 4 ++ src/dumpulator/native.py | 23 +++++++++++ src/dumpulator/ntsyscalls.py | 20 ++++++++-- tests/DumpulatorTests/Tests/ExceptionTest.cpp | 39 ++++++++++++++++++- tests/DumpulatorTests/Tests/debug.h | 2 + tests/harness-tests.py | 9 +++++ 6 files changed, 92 insertions(+), 5 deletions(-) diff --git a/src/dumpulator/dumpulator.py b/src/dumpulator/dumpulator.py index 57140dd..d04e4be 100644 --- a/src/dumpulator/dumpulator.py +++ b/src/dumpulator/dumpulator.py @@ -484,6 +484,10 @@ def call(self, addr, args: List[int] = [], regs: dict = {}, count=0): # set up arguments if self._x64: + # Align the stack + # TODO: unalign the stack after? + if self.regs.rsp & 0xF != 0: + self.regs.rsp -= 8 for index, value in enumerate(args): self.args[index] = value else: diff --git a/src/dumpulator/native.py b/src/dumpulator/native.py index 2875f59..ba0d895 100644 --- a/src/dumpulator/native.py +++ b/src/dumpulator/native.py @@ -21,6 +21,7 @@ STATUS_NOT_FOUND = 0xC0000225 STATUS_MEMORY_NOT_ALLOCATED = 0xC00000A0 STATUS_CONFLICTING_ADDRESSES = 0xC0000018 +STATUS_PORT_NOT_SET = 0xC0000353 # Exceptions DBG_PRINTEXCEPTION_C = 0x40010006 @@ -566,6 +567,28 @@ class FILE_BASIC_INFORMATION(ctypes.Structure): ] return FILE_BASIC_INFORMATION() +def SECTION_IMAGE_INFORMATION(arch: Architecture): + class SECTION_IMAGE_INFORMATION(ctypes.Structure): + _alignment_ = arch.alignment() + _fields_ = [ + ("TransferAddress", arch.ptr_type()), + ("ZeroBits", ctypes.c_uint32), + ("MaximumStackSize", arch.ptr_type()), + ("CommittedStackSize", arch.ptr_type()), + ("SubSystemType", ctypes.c_uint32), + ("SubSystemVersion", ctypes.c_uint32), + ("OperatingSystemVersion", ctypes.c_uint32), + ("ImageCharacteristics", ctypes.c_uint16), + ("DllCharacteristics", ctypes.c_uint16), + ("Machine", ctypes.c_uint16), + ("ImageContainsCode", ctypes.c_uint8), + ("ImageFlags", ctypes.c_uint8), + ("LoaderFlags", ctypes.c_uint32), + ("ImageFileSize", ctypes.c_uint32), + ("CheckSum", ctypes.c_uint32), + ] + return SECTION_IMAGE_INFORMATION() + def P(t): class P(PVOID): def __init__(self, ptr, mem_read): diff --git a/src/dumpulator/ntsyscalls.py b/src/dumpulator/ntsyscalls.py index bb69ae8..88196eb 100644 --- a/src/dumpulator/ntsyscalls.py +++ b/src/dumpulator/ntsyscalls.py @@ -836,7 +836,6 @@ def ZwCreateEvent(dp: Dumpulator, InitialState: BOOLEAN ): assert DesiredAccess == 0x1f0003 - assert ObjectAttributes == 0 event = EventObject(EventType, InitialState) handle = dp.handles.new(event) EventHandle.write_ptr(handle) @@ -1846,6 +1845,7 @@ def ZwGetMUIRegistryInfo(dp: Dumpulator, DataSize: P(ULONG), Data: PVOID ): + return STATUS_NOT_IMPLEMENTED raise NotImplementedError() @syscall @@ -1981,6 +1981,7 @@ def ZwIsSystemResumeAutomatic(dp: Dumpulator @syscall def ZwIsUILanguageComitted(dp: Dumpulator ): + return False raise NotImplementedError() @syscall @@ -2686,6 +2687,7 @@ def ZwQueryDebugFilterState(dp: Dumpulator, ComponentId: ULONG, Level: ULONG ): + return 0 # STATUS_SUCCESS will print debug messages with RaiseException return STATUS_NOT_IMPLEMENTED @@ -2906,12 +2908,18 @@ def ZwQueryInformationProcess(dp: Dumpulator, ReturnLength: P(ULONG) ): assert (ProcessHandle == dp.NtCurrentProcess()) - if ProcessInformationClass in [PROCESSINFOCLASS.ProcessDebugPort, PROCESSINFOCLASS.ProcessDebugObjectHandle]: + if ProcessInformationClass == PROCESSINFOCLASS.ProcessDebugPort: assert ProcessInformationLength == dp.ptr_size() dp.write_ptr(ProcessInformation.ptr, 0) if ReturnLength != 0: dp.write_ulong(ReturnLength.ptr, dp.ptr_size()) return STATUS_SUCCESS + elif ProcessInformationClass == PROCESSINFOCLASS.ProcessDebugObjectHandle: + assert ProcessInformationLength == dp.ptr_size() + dp.write_ptr(ProcessInformation.ptr, 0) + if ReturnLength != 0: + dp.write_ulong(ReturnLength.ptr, dp.ptr_size()) + return STATUS_PORT_NOT_SET elif ProcessInformationClass == PROCESSINFOCLASS.ProcessDefaultHardErrorMode: assert ProcessInformationLength == 4 dp.write_ulong(ProcessInformation.ptr, 1) @@ -2924,6 +2932,10 @@ def ZwQueryInformationProcess(dp: Dumpulator, if ReturnLength.ptr: dp.write_ulong(ReturnLength.ptr, 4) return STATUS_SUCCESS + elif ProcessInformationClass == PROCESSINFOCLASS.ProcessImageInformation: + sii = SECTION_IMAGE_INFORMATION(dp) + assert ProcessInformationLength == ctypes.sizeof(sii) + return STATUS_NOT_IMPLEMENTED raise NotImplementedError() @syscall @@ -2995,6 +3007,7 @@ def ZwQueryInformationWorkerFactory(dp: Dumpulator, def ZwQueryInstallUILanguage(dp: Dumpulator, InstallUILanguageId: P(LANGID) ): + return STATUS_ACCESS_DENIED raise NotImplementedError() @syscall @@ -3032,6 +3045,7 @@ def ZwQueryLicenseValue(dp: Dumpulator, DataSize: ULONG, ResultDataSize: P(ULONG) ): + return STATUS_NOT_FOUND raise NotImplementedError() @syscall @@ -3130,7 +3144,7 @@ def ZwQuerySecurityAttributesToken(dp: Dumpulator, Length: ULONG, ReturnLength: P(ULONG) ): - raise NotImplementedError() + return STATUS_NOT_FOUND @syscall def ZwQuerySecurityObject(dp: Dumpulator, diff --git a/tests/DumpulatorTests/Tests/ExceptionTest.cpp b/tests/DumpulatorTests/Tests/ExceptionTest.cpp index 27ba847..0ad2eb7 100644 --- a/tests/DumpulatorTests/Tests/ExceptionTest.cpp +++ b/tests/DumpulatorTests/Tests/ExceptionTest.cpp @@ -1,13 +1,20 @@ #include "debug.h" +static int g_VectoredHandlerCount = 0; +static int g_ContinueHandlerCount = 0; +static int g_ExceptionFilterCount = 0; +static int g_TryFilterCount = 0; + static LONG WINAPI VectoredHandler(struct _EXCEPTION_POINTERS* ExceptionInfo) { + g_VectoredHandlerCount++; DebugPrint(WIDEN(__FUNCTION__)); return EXCEPTION_CONTINUE_SEARCH; } static LONG WINAPI ContinueHandler(struct _EXCEPTION_POINTERS* ExceptionInfo) { + g_ContinueHandlerCount++; DebugPrint(WIDEN(__FUNCTION__)); return EXCEPTION_CONTINUE_SEARCH; } @@ -16,6 +23,7 @@ static LPTOP_LEVEL_EXCEPTION_FILTER previousFilter; static LONG WINAPI ExceptionFilter(struct _EXCEPTION_POINTERS* ExceptionInfo) { + g_ExceptionFilterCount++; DebugPrint(WIDEN(__FUNCTION__)); if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT) { @@ -31,6 +39,7 @@ static LONG WINAPI ExceptionFilter(struct _EXCEPTION_POINTERS* ExceptionInfo) static int __try_filter(unsigned int code, struct _EXCEPTION_POINTERS* ExceptionInfo) { + g_TryFilterCount++; DebugPrint(WIDEN(__FUNCTION__)); const auto& er = *ExceptionInfo->ExceptionRecord; if (er.ExceptionCode == EXCEPTION_ACCESS_VIOLATION && er.ExceptionInformation[1] == 0xDEADF00D) @@ -40,7 +49,7 @@ static int __try_filter(unsigned int code, struct _EXCEPTION_POINTERS* Exception return EXCEPTION_CONTINUE_SEARCH; } -extern "C" __declspec(dllexport) bool Exception_Test() +extern "C" __declspec(dllexport) bool Exception_RegularTest() { DebugPrint(WIDEN(__FUNCTION__)); DebugPrint(L"Test VEH, SEH, VCH"); @@ -56,9 +65,35 @@ extern "C" __declspec(dllexport) bool Exception_Test() DebugPrint(L"__except handler"); } + auto sehWorking = g_VectoredHandlerCount == 1 && g_ContinueHandlerCount == 0 && g_ExceptionFilterCount == 0 && g_TryFilterCount == 1; + if (!sehWorking) + DebugPrint(L"SEH not working!"); + + g_VectoredHandlerCount = 0; + g_ContinueHandlerCount = 0; + g_ExceptionFilterCount = 0; + g_TryFilterCount = 0; + + return sehWorking; +} + +#if 0 +extern "C" __declspec(dllexport) bool Exception_FilterTest() +{ DebugPrint(L"Test SetUnhandledExceptionFilter"); previousFilter = SetUnhandledExceptionFilter(ExceptionFilter); __debugbreak(); DebugPrint(L"Finished!"); - return true; + + auto uefWorking = g_VectoredHandlerCount == 1 && g_ContinueHandlerCount == 1 && g_ExceptionFilterCount == 1 && g_TryFilterCount == 0; + if (!uefWorking) + DebugPrint(L"UnhandledExceptionFilter not working!"); + + g_VectoredHandlerCount = 0; + g_ContinueHandlerCount = 0; + g_ExceptionFilterCount = 0; + g_TryFilterCount = 0; + + return uefWorking; } +#endif \ No newline at end of file diff --git a/tests/DumpulatorTests/Tests/debug.h b/tests/DumpulatorTests/Tests/debug.h index f85172a..9e6f86b 100644 --- a/tests/DumpulatorTests/Tests/debug.h +++ b/tests/DumpulatorTests/Tests/debug.h @@ -36,5 +36,7 @@ static void DebugPrint(const wchar_t* str) ustr.MaximumLength = (len + 1) * 2; ustr.Buffer = (PWSTR)str; NtDisplayString(&ustr); + //MessageBoxW(0, str, 0, MB_SYSTEMMODAL) + ; } #endif // __cplusplus diff --git a/tests/harness-tests.py b/tests/harness-tests.py index 742bd65..d57c666 100644 --- a/tests/harness-tests.py +++ b/tests/harness-tests.py @@ -52,6 +52,15 @@ def run_tests(dll_path: str, harness_dump: str): for export in exports: dp = Dumpulator(harness_dump, trace=True) module = dp.map_module(dll_data, dll_path, base) + # Register the EXCEPTION_DIRECTORY + if dp.ptr_size() == 8: + dir = module.pe.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_EXCEPTION"]] + va = module.base + dir.VirtualAddress + size = dir.Size + assert size % 12 == 0 + RtlAddFunctionTable = dp.modules.resolve_export("ntdll.dll", "RtlAddFunctionTable").address + success = dp.call(RtlAddFunctionTable, [va, size // 12, module.base]) + print(f"RtlAddFunctionTable: {success}") environment().setup(dp) test = module.find_export(export) assert test is not None