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: diff --git a/src/dumpulator/dumpulator.py b/src/dumpulator/dumpulator.py index fcff463..d04e4be 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 = {} @@ -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)}]" @@ -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) @@ -481,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: @@ -521,7 +528,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() @@ -723,7 +730,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}") @@ -813,7 +820,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) @@ -855,9 +862,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 +879,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 +934,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 +1072,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 @@ -1090,6 +1104,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] @@ -1149,6 +1166,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 @@ -1156,4 +1188,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 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/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 diff --git a/src/dumpulator/modules.py b/src/dumpulator/modules.py index 162e35b..0051993 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] = {} @@ -92,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 diff --git a/src/dumpulator/native.py b/src/dumpulator/native.py index 849fbbe..ba0d895 100644 --- a/src/dumpulator/native.py +++ b/src/dumpulator/native.py @@ -18,6 +18,10 @@ 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 +STATUS_PORT_NOT_SET = 0xC0000353 # Exceptions DBG_PRINTEXCEPTION_C = 0x40010006 @@ -563,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/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 a3ae6fb..88196eb 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: @@ -630,7 +635,7 @@ def ZwCancelTimer(dp: Dumpulator, TimerHandle: HANDLE, CurrentState: P(BOOLEAN) ): - raise NotImplementedError() + return STATUS_SUCCESS @syscall def ZwCancelTimer2(dp: Dumpulator, @@ -830,7 +835,11 @@ def ZwCreateEvent(dp: Dumpulator, EventType: EVENT_TYPE, InitialState: BOOLEAN ): - raise NotImplementedError() + assert DesiredAccess == 0x1f0003 + event = EventObject(EventType, InitialState) + handle = dp.handles.new(event) + EventHandle.write_ptr(handle) + return STATUS_SUCCESS @syscall def ZwCreateEventPair(dp: Dumpulator, @@ -1742,7 +1751,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, @@ -1825,6 +1845,7 @@ def ZwGetMUIRegistryInfo(dp: Dumpulator, DataSize: P(ULONG), Data: PVOID ): + return STATUS_NOT_IMPLEMENTED raise NotImplementedError() @syscall @@ -1960,6 +1981,7 @@ def ZwIsSystemResumeAutomatic(dp: Dumpulator @syscall def ZwIsUILanguageComitted(dp: Dumpulator ): + return False raise NotImplementedError() @syscall @@ -2126,7 +2148,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) @@ -2665,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 @@ -2885,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) @@ -2903,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 @@ -2974,6 +3007,7 @@ def ZwQueryInformationWorkerFactory(dp: Dumpulator, def ZwQueryInstallUILanguage(dp: Dumpulator, InstallUILanguageId: P(LANGID) ): + return STATUS_ACCESS_DENIED raise NotImplementedError() @syscall @@ -3011,6 +3045,7 @@ def ZwQueryLicenseValue(dp: Dumpulator, DataSize: ULONG, ResultDataSize: P(ULONG) ): + return STATUS_NOT_FOUND raise NotImplementedError() @syscall @@ -3109,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/.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/HandleTest/HandleTest/HandleTest.vcxproj b/tests/DumpulatorTests/HarnessFull/HarnessFull.vcxproj similarity index 55% rename from tests/HandleTest/HandleTest/HandleTest.vcxproj rename to tests/DumpulatorTests/HarnessFull/HarnessFull.vcxproj index 3bf3dfd..b5a2ea5 100644 --- a/tests/HandleTest/HandleTest/HandleTest.vcxproj +++ b/tests/DumpulatorTests/HarnessFull/HarnessFull.vcxproj @@ -1,149 +1,117 @@ - - - - - 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 - - - - - - - - + + + + + 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/ExceptionTest/ExceptionTest/ExceptionTest.vcxproj.filters b/tests/DumpulatorTests/HarnessFull/HarnessFull.vcxproj.filters similarity index 83% rename from tests/ExceptionTest/ExceptionTest/ExceptionTest.vcxproj.filters rename to tests/DumpulatorTests/HarnessFull/HarnessFull.vcxproj.filters index afdc275..4cc5472 100644 --- a/tests/ExceptionTest/ExceptionTest/ExceptionTest.vcxproj.filters +++ b/tests/DumpulatorTests/HarnessFull/HarnessFull.vcxproj.filters @@ -15,12 +15,7 @@ - - Header Files - - - - + Source Files 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/ExceptionTest/ExceptionTest/ExceptionTest.vcxproj b/tests/DumpulatorTests/HarnessMinimal/HarnessMinimal.vcxproj similarity index 54% rename from tests/ExceptionTest/ExceptionTest/ExceptionTest.vcxproj rename to tests/DumpulatorTests/HarnessMinimal/HarnessMinimal.vcxproj index c97f2e5..b7eb595 100644 --- a/tests/ExceptionTest/ExceptionTest/ExceptionTest.vcxproj +++ b/tests/DumpulatorTests/HarnessMinimal/HarnessMinimal.vcxproj @@ -1,18 +1,10 @@ - - Debug - Win32 - Release Win32 - - Debug - x64 - Release x64 @@ -21,17 +13,11 @@ 16.0 Win32Proj - {4df88650-7af7-450c-b867-534d2a5304e1} - ExceptionTest + {cbd65637-b773-430b-af11-a0afeb699d8f} + HarnessMinimal 10.0 - - Application - true - v142 - Unicode - Application false @@ -39,12 +25,6 @@ true Unicode - - Application - true - v142 - Unicode - Application false @@ -57,76 +37,50 @@ - - - - - - - - true - false - - - true + $(SolutionDir)bin\ + $(SolutionDir)_obj\$(Platform)\$(ProjectName)\ + $(ProjectName)_x86 false + $(SolutionDir)bin\ + $(SolutionDir)_obj\$(Platform)\$(ProjectName)\ + $(ProjectName)_x64 - - - Level3 - true - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp17 - - - Console - true - - Level3 true true - true + false WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true - stdcpp17 - false MultiThreaded + false + Sync + StdCall + Disabled Console true true true + EntryPoint false - Default - - - - - Level3 - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp17 - - - Console - true + 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 @@ -134,27 +88,29 @@ Level3 true true - true + false NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true - stdcpp17 - false MultiThreaded + false + Sync + StdCall + Disabled Console true true true + EntryPoint false - Default + true + false + 0x130000000 - - - - + 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 0000000..4052aa3 Binary files /dev/null and b/tests/DumpulatorTests/HarnessMinimal/msvcrt_x86.lib differ 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/HandleTest/HandleTest/HandleTest.vcxproj.filters b/tests/DumpulatorTests/Loader/Loader.vcxproj.filters similarity index 94% rename from tests/HandleTest/HandleTest/HandleTest.vcxproj.filters rename to tests/DumpulatorTests/Loader/Loader.vcxproj.filters index decff76..cad3232 100644 --- a/tests/HandleTest/HandleTest/HandleTest.vcxproj.filters +++ b/tests/DumpulatorTests/Loader/Loader.vcxproj.filters @@ -1,22 +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 - - + + + + + {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..0594321 --- /dev/null +++ b/tests/DumpulatorTests/README.md @@ -0,0 +1,24 @@ +# DumpulatorTests + +## Creating a dump + +The harness dumps were created as follows: + +- 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 + +## 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/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/ExceptionTest.cpp b/tests/DumpulatorTests/Tests/ExceptionTest.cpp new file mode 100644 index 0000000..0ad2eb7 --- /dev/null +++ b/tests/DumpulatorTests/Tests/ExceptionTest.cpp @@ -0,0 +1,99 @@ +#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; +} + +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) + { +#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) +{ + g_TryFilterCount++; + 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 Exception_RegularTest() +{ + 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"); + } + + 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!"); + + 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/HandleTest.cpp b/tests/DumpulatorTests/Tests/HandleTest.cpp new file mode 100644 index 0000000..97155d9 --- /dev/null +++ b/tests/DumpulatorTests/Tests/HandleTest.cpp @@ -0,0 +1,76 @@ +#include "debug.h" + +extern "C" __declspec(dllexport) bool Handle_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"nonexistent_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 Handle_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..071c2c2 --- /dev/null +++ b/tests/DumpulatorTests/Tests/Tests.vcxproj @@ -0,0 +1,117 @@ + + + + + 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) + false + Default + DllMain + + + + + 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) + Default + DllMain + + + + + + \ 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..558c777 --- /dev/null +++ b/tests/DumpulatorTests/Tests/Tests.vcxproj.filters @@ -0,0 +1,33 @@ + + + + + {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 + + + 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..9e6f86b --- /dev/null +++ b/tests/DumpulatorTests/Tests/debug.h @@ -0,0 +1,42 @@ +#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); + //MessageBoxW(0, str, 0, MB_SYSTEMMODAL) + ; +} +#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 0000000..cc91f61 Binary files /dev/null and b/tests/DumpulatorTests/Tests/exception_handler_x64.lib differ 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 0000000..cb54688 Binary files /dev/null and b/tests/DumpulatorTests/Tests/exception_handler_x86.lib differ 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/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/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 542b4d8..0000000 --- a/tests/handle-test32.py +++ /dev/null @@ -1,25 +0,0 @@ -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.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) - - -if __name__ == '__main__': - main() diff --git a/tests/handle-test64.py b/tests/handle-test64.py deleted file mode 100644 index 89ca69d..0000000 --- a/tests/handle-test64.py +++ /dev/null @@ -1,25 +0,0 @@ -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.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) - - -if __name__ == '__main__': - main() diff --git a/tests/harness-tests.py b/tests/harness-tests.py new file mode 100644 index 0000000..d57c666 --- /dev/null +++ b/tests/harness-tests.py @@ -0,0 +1,77 @@ +import re +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: + # 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 + +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) + # 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 + 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()