From 89e251c54e07f15a3e4a4a4ca06298816553b44a Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Sun, 2 Aug 2026 12:31:30 +0200 Subject: [PATCH 1/2] Add CS-aware WoW64 execution mode support --- TitanEngine/Global.Debugger.cpp | 4 +- TitanEngine/Global.Engine.Context.cpp | 48 ++++++++++ TitanEngine/Global.Engine.Context.h | 13 +++ TitanEngine/TitanEngine.Debugger.Control.cpp | 11 ++- .../TitanEngine.Debugger.DebugLoop.cpp | 34 ++++++-- TitanEngine/TitanEngine.Debugger.Helper.cpp | 87 +++++++------------ TitanEngine/TitanEngine.Disassembler.cpp | 58 ++++++++++--- TitanEngine/definitions.h | 8 ++ 8 files changed, 184 insertions(+), 79 deletions(-) diff --git a/TitanEngine/Global.Debugger.cpp b/TitanEngine/Global.Debugger.cpp index 11e4c5f..17be3e1 100644 --- a/TitanEngine/Global.Debugger.cpp +++ b/TitanEngine/Global.Debugger.cpp @@ -43,7 +43,9 @@ CRITICAL_SECTION engineStepActiveCr; // Workaround for a bug in the kernel with x64 emulation on ARM DWORD ContextControlFlags = [] { - DWORD flags = CONTEXT_CONTROL; + // Preserve hardware-breakpoint state whenever a control-context update is + // written back. This is especially important across WoW64 mode switches. + DWORD flags = CONTEXT_CONTROL | CONTEXT_DEBUG_REGISTERS; typedef BOOL(WINAPI * type_IsWow64Process2)(HANDLE, USHORT*, USHORT*); auto p_IsWow64Process2 = (type_IsWow64Process2)GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "IsWow64Process2"); if(p_IsWow64Process2) diff --git a/TitanEngine/Global.Engine.Context.cpp b/TitanEngine/Global.Engine.Context.cpp index 5830384..e9a4203 100644 --- a/TitanEngine/Global.Engine.Context.cpp +++ b/TitanEngine/Global.Engine.Context.cpp @@ -1,6 +1,9 @@ #include "stdafx.h" #include "definitions.h" #include "Global.Engine.Context.h" +#include "Global.Debugger.h" +#include "Global.Engine.h" +#include "Global.Handle.h" #ifdef _WIN64 //https://stackoverflow.com/a/869597/1806760 @@ -134,6 +137,51 @@ PGETXSTATEFEATURESMASK _GetXStateFeaturesMask = NULL; LOCATEXSTATEFEATURE _LocateXStateFeature = NULL; SETXSTATEFEATURESMASK _SetXStateFeaturesMask = NULL; +EngineContextMode EngineGetThreadContextMode(HANDLE hThread) +{ +#ifndef _WIN64 + (void)hThread; + return EngineContextMode::X86; +#else + if(hThread != NULL) + { + CONTEXT context = {}; + context.ContextFlags = CONTEXT_CONTROL; + if(GetThreadContext(hThread, &context)) + { + // Windows uses selector 0x23 for 32-bit compatibility mode and + // selector 0x33 for 64-bit user mode. Check every time because a + // WoW64 thread can transition between the two modes. + if(context.SegCs == 0x23) + return EngineContextMode::X86; + if(context.SegCs == 0x33) + return EngineContextMode::X64; + } + } + return EngineContextMode::X64; +#endif +} + +EngineContextMode EngineGetCurrentContextMode() +{ +#ifndef _WIN64 + return EngineContextMode::X86; +#else + HANDLE hThread = EngineOpenThread(THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION, false, DBGEvent.dwThreadId); + if(hThread == NULL) + return EngineContextMode::X64; + + auto mode = EngineGetThreadContextMode(hThread); + EngineCloseHandle(hThread); + return mode; +#endif +} + +SIZE_T EngineGetContextPointerSize(EngineContextMode mode) +{ + return mode == EngineContextMode::X86 ? sizeof(DWORD) : sizeof(DWORD64); +} + bool _SetFullContextDataEx(HANDLE hActiveThread, TITAN_ENGINE_CONTEXT_t* titcontext, bool AVX_PRIORITY) { CONTEXT DBGContext; diff --git a/TitanEngine/Global.Engine.Context.h b/TitanEngine/Global.Engine.Context.h index b4ad9fd..b84b91c 100644 --- a/TitanEngine/Global.Engine.Context.h +++ b/TitanEngine/Global.Engine.Context.h @@ -24,6 +24,19 @@ extern PGETXSTATEFEATURESMASK _GetXStateFeaturesMask; extern LOCATEXSTATEFEATURE _LocateXStateFeature; extern SETXSTATEFEATURESMASK _SetXStateFeaturesMask; +enum class EngineContextMode +{ + X86, + X64, +}; + +// Determine the mode in which a thread is currently executing. A WoW64 thread +// can switch between 32-bit and 64-bit code, so this deliberately uses CS from +// the live thread context instead of the process image architecture. +EngineContextMode EngineGetThreadContextMode(HANDLE hThread); +EngineContextMode EngineGetCurrentContextMode(); +SIZE_T EngineGetContextPointerSize(EngineContextMode mode); + bool _SetFullContextDataEx(HANDLE hActiveThread, TITAN_ENGINE_CONTEXT_t* titcontext, bool AVX_PRIORITY); bool _GetFullContextDataEx(HANDLE hActiveThread, TITAN_ENGINE_CONTEXT_t* titcontext, bool avx); bool InitXState(void); diff --git a/TitanEngine/TitanEngine.Debugger.Control.cpp b/TitanEngine/TitanEngine.Debugger.Control.cpp index 3984c0d..c448b31 100644 --- a/TitanEngine/TitanEngine.Debugger.Control.cpp +++ b/TitanEngine/TitanEngine.Debugger.Control.cpp @@ -5,6 +5,7 @@ #include "Global.Threader.h" #include "Global.Librarian.h" #include "Global.Engine.h" +#include "Global.Engine.Context.h" __declspec(dllexport) void TITCALL ForceClose() { @@ -43,14 +44,15 @@ __declspec(dllexport) void TITCALL StepInto(LPVOID StepCallBack) if(engineStepThreads.find(DBGEvent.dwThreadId) == engineStepThreads.end()) { ULONG_PTR ueCurrentPosition = GetContextData(UE_CIP); + bool is32Bit = EngineGetCurrentContextMode() == EngineContextMode::X86; unsigned char instr[16]; MemoryReadSafe(dbgProcessInformation.hProcess, (void*)ueCurrentPosition, instr, sizeof(instr), 0); - char* DisassembledString = (char*)StaticDisassembleEx(ueCurrentPosition, (LPVOID)instr); + char* DisassembledString = (char*)EngineStaticDisassembleEx(ueCurrentPosition, (LPVOID)instr, is32Bit); if(strstr(DisassembledString, "PUSHF")) StepOver(StepCallBack); else if(strstr(DisassembledString, "POP SS") || strstr(DisassembledString, "MOV SS")) //prevent the 'PUSH SS', 'POP SS' step trick { - ueCurrentPosition += StaticLengthDisassemble((void*)instr); + ueCurrentPosition += EngineStaticLengthDisassemble((void*)instr, is32Bit); SetBPX(ueCurrentPosition, UE_BREAKPOINT_TYPE_INT3 + UE_SINGLESHOOT, StepCallBack); } else @@ -71,12 +73,13 @@ __declspec(dllexport) void TITCALL StepInto(LPVOID StepCallBack) __declspec(dllexport) void TITCALL StepOver(LPVOID StepCallBack) { ULONG_PTR ueCurrentPosition = GetContextData(UE_CIP); + bool is32Bit = EngineGetCurrentContextMode() == EngineContextMode::X86; unsigned char instr[16]; MemoryReadSafe(dbgProcessInformation.hProcess, (void*)ueCurrentPosition, instr, sizeof(instr), 0); - char* DisassembledString = (char*)StaticDisassembleEx(ueCurrentPosition, (LPVOID)instr); + char* DisassembledString = (char*)EngineStaticDisassembleEx(ueCurrentPosition, (LPVOID)instr, is32Bit); if(strstr(DisassembledString, "CALL") || strstr(DisassembledString, "REP") || strstr(DisassembledString, "PUSHF")) { - ueCurrentPosition += StaticLengthDisassemble((void*)instr); + ueCurrentPosition += EngineStaticLengthDisassemble((void*)instr, is32Bit); SetBPX(ueCurrentPosition, UE_BREAKPOINT_TYPE_INT3 + UE_SINGLESHOOT, StepCallBack); } else diff --git a/TitanEngine/TitanEngine.Debugger.DebugLoop.cpp b/TitanEngine/TitanEngine.Debugger.DebugLoop.cpp index 3fd4fba..9f3b0f0 100644 --- a/TitanEngine/TitanEngine.Debugger.DebugLoop.cpp +++ b/TitanEngine/TitanEngine.Debugger.DebugLoop.cpp @@ -7,6 +7,7 @@ #include "Global.Threader.h" #include "Global.Librarian.h" #include "Global.TLS.h" +#include "Global.Engine.Context.h" #include #include #include @@ -14,6 +15,25 @@ #define UE_MODULEx86 0x2000; #define UE_MODULEx64 0x2000; +static char* DisassembleCurrentInstruction(ULONG_PTR address, void* data) +{ + bool is32Bit = EngineGetCurrentContextMode() == EngineContextMode::X86; + return (char*)EngineStaticDisassembleEx(address, data, is32Bit); +} + +static void ClearTrapFlagFromStack() +{ + auto mode = EngineGetCurrentContextMode(); + SIZE_T pointerSize = EngineGetContextPointerSize(mode); + void* csp = (void*)GetContextData(UE_CSP); + DWORD64 data = 0; + if(ReadProcessMemory(dbgProcessInformation.hProcess, csp, &data, pointerSize, nullptr)) + { + data &= ~DWORD64(UE_TRAP_FLAG); + WriteProcessMemory(dbgProcessInformation.hProcess, csp, &data, pointerSize, nullptr); + } +} + static void engineStep() { EnterCriticalSection(&engineStepActiveCr); @@ -645,7 +665,7 @@ __declspec(dllexport) void TITCALL DebugLoop() ULONG_PTR ueCurrentPosition = FoundBreakPoint.BreakPointAddress; unsigned char instr[16]; MemoryReadSafe(dbgProcessInformation.hProcess, (void*)ueCurrentPosition, instr, sizeof(instr), 0); - char* DisassembledString = (char*)StaticDisassembleEx(ueCurrentPosition, (LPVOID)instr); + char* DisassembledString = DisassembleCurrentInstruction(ueCurrentPosition, (LPVOID)instr); if(strstr(DisassembledString, "PUSHF")) PushfBPX = true; } @@ -755,11 +775,7 @@ __declspec(dllexport) void TITCALL DebugLoop() if(PushfBPX) //remove trap flag from stack { PushfBPX = false; - void* csp = (void*)GetContextData(UE_CSP); - ULONG_PTR data = 0; - ReadProcessMemory(dbgProcessInformation.hProcess, csp, &data, sizeof(ULONG_PTR), 0); - data &= ~UE_TRAP_FLAG; - WriteProcessMemory(dbgProcessInformation.hProcess, csp, &data, sizeof(ULONG_PTR), 0); + ClearTrapFlagFromStack(); } if(ResetBPX) //restore 'normal' breakpoint { @@ -947,7 +963,7 @@ __declspec(dllexport) void TITCALL DebugLoop() ULONG_PTR ueCurrentPosition = GetContextData(UE_CIP); unsigned char instr[16]; MemoryReadSafe(dbgProcessInformation.hProcess, (void*)ueCurrentPosition, instr, sizeof(instr), 0); - char* DisassembledString = (char*)StaticDisassembleEx(ueCurrentPosition, (LPVOID)instr); + char* DisassembledString = DisassembleCurrentInstruction(ueCurrentPosition, (LPVOID)instr); if(strstr(DisassembledString, "PUSHF")) PushfBPX = true; } @@ -1186,7 +1202,7 @@ __declspec(dllexport) void TITCALL DebugLoop() ULONG_PTR ueCurrentPosition = GetContextData(UE_CIP); unsigned char instr[16]; MemoryReadSafe(dbgProcessInformation.hProcess, (void*)ueCurrentPosition, instr, sizeof(instr), nullptr); - char* DisassembledString = (char*)StaticDisassembleEx(ueCurrentPosition, (LPVOID)instr); + char* DisassembledString = DisassembleCurrentInstruction(ueCurrentPosition, (LPVOID)instr); if(strstr(DisassembledString, "PUSHF")) PushfBPX = true; } @@ -1269,7 +1285,7 @@ __declspec(dllexport) void TITCALL DebugLoop() ULONG_PTR ueCurrentPosition = FoundBreakPoint.BreakPointAddress; unsigned char instr[16]; MemoryReadSafe(dbgProcessInformation.hProcess, (void*)ueCurrentPosition, instr, sizeof(instr), 0); - char* DisassembledString = (char*)StaticDisassembleEx(ueCurrentPosition, (LPVOID)instr); + char* DisassembledString = DisassembleCurrentInstruction(ueCurrentPosition, (LPVOID)instr); if(strstr(DisassembledString, "PUSHF")) PushfBPX = true; } diff --git a/TitanEngine/TitanEngine.Debugger.Helper.cpp b/TitanEngine/TitanEngine.Debugger.Helper.cpp index 3793cbd..40c6e5a 100644 --- a/TitanEngine/TitanEngine.Debugger.Helper.cpp +++ b/TitanEngine/TitanEngine.Debugger.Helper.cpp @@ -1,6 +1,7 @@ #include "stdafx.h" #include "definitions.h" #include "Global.Debugger.h" +#include "Global.Engine.Context.h" static char szParameterString[512]; @@ -51,6 +52,8 @@ __declspec(dllexport) ULONG_PTR TITCALL GetFunctionParameter(HANDLE hProcess, DW DWORD StackReadSize = 512; DWORD StringReadSize = 512; bool ValueIsPointer = false; + auto mode = EngineGetCurrentContextMode(); + SIZE_T pointerSize = EngineGetContextPointerSize(mode); if(ParameterType == UE_PARAMETER_BYTE) { @@ -94,81 +97,55 @@ __declspec(dllexport) ULONG_PTR TITCALL GetFunctionParameter(HANDLE hProcess, DW { StackSecondReadSize = 0; } - StackReadSize = sizeof(ULONG_PTR); + StackReadSize = (DWORD)pointerSize; } - if(FunctionType >= UE_FUNCTION_STDCALL && FunctionType <= UE_FUNCTION_CCALL_CALL && FunctionType != UE_FUNCTION_FASTCALL_RET) + if(FunctionType >= UE_FUNCTION_STDCALL && FunctionType <= UE_FUNCTION_FASTCALL_CALL && FunctionType != UE_FUNCTION_FASTCALL_RET) { StackReadAddress = (ULONG_PTR)GetContextData(UE_CSP); if(FunctionType != UE_FUNCTION_FASTCALL_CALL) { - StackReadAddress = StackReadAddress + (ParameterNumber * sizeof(ULONG_PTR)); + StackReadAddress = StackReadAddress + (ParameterNumber * pointerSize); if(FunctionType >= UE_FUNCTION_STDCALL_CALL) { - StackReadAddress = StackReadAddress - sizeof(ULONG_PTR); + StackReadAddress = StackReadAddress - pointerSize; } } else { - if(ParameterNumber <= 4) + DWORD registerCount = mode == EngineContextMode::X86 ? 2 : 4; + if(ParameterNumber <= registerCount) { + static const DWORD x86Registers[] = { UE_ECX, UE_EDX }; + static const DWORD x64Registers[] = { UE_RCX, UE_RDX, UE_R8, UE_R9 }; + DWORD registerIndex = mode == EngineContextMode::X86 + ? x86Registers[ParameterNumber - 1] + : x64Registers[ParameterNumber - 1]; + ULONG_PTR registerValue = (ULONG_PTR)GetContextData(registerIndex); if(!ValueIsPointer) - { - if(ParameterNumber == 1) - { - return((ULONG_PTR)GetContextData(UE_RCX)); - } - else if(ParameterNumber == 2) - { - return((ULONG_PTR)GetContextData(UE_RDX)); - } - else if(ParameterNumber == 3) - { - return((ULONG_PTR)GetContextData(UE_R8)); - } - else if(ParameterNumber == 4) - { - return((ULONG_PTR)GetContextData(UE_R9)); - } - } - else - { - if(ParameterNumber == 1) - { - StackReadAddress = (ULONG_PTR)GetContextData(UE_RCX); - } - else if(ParameterNumber == 2) - { - StackReadAddress = (ULONG_PTR)GetContextData(UE_RDX); - } - else if(ParameterNumber == 3) - { - StackReadAddress = (ULONG_PTR)GetContextData(UE_R8); - } - else if(ParameterNumber == 4) - { - StackReadAddress = (ULONG_PTR)GetContextData(UE_R9); - } - } + return registerValue; + StackReadAddress = registerValue; } else { - StackReadAddress = StackReadAddress + 0x20 + ((ParameterNumber - 4) * sizeof(ULONG_PTR)) - sizeof(ULONG_PTR); + SIZE_T shadowSpace = mode == EngineContextMode::X86 ? 0 : 0x20; + StackReadAddress += shadowSpace + ((ParameterNumber - registerCount) * pointerSize) - pointerSize; } } - if(ReadProcessMemory(hProcess, (LPVOID)StackReadAddress, &StackReadBuffer, sizeof(ULONG_PTR), &ueNumberOfBytesRW)) + SIZE_T initialReadSize = ValueIsPointer ? pointerSize : (StackReadSize < sizeof(StackReadBuffer) ? StackReadSize : sizeof(StackReadBuffer)); + if(ReadProcessMemory(hProcess, (LPVOID)StackReadAddress, &StackReadBuffer, initialReadSize, &ueNumberOfBytesRW)) { if(!ValueIsPointer) { - RtlMoveMemory((LPVOID)((ULONG_PTR)&StackFinalBuffer + sizeof(ULONG_PTR) - StackReadSize), (LPVOID)((ULONG_PTR)&StackReadBuffer + sizeof(ULONG_PTR) - StackReadSize), StackReadSize); + RtlMoveMemory(&StackFinalBuffer, &StackReadBuffer, StackReadSize); } else { StackReadAddress = StackReadBuffer; if(StackSecondReadSize > NULL) { - if(ReadProcessMemory(hProcess, (LPVOID)StackReadAddress, &StackReadBuffer, sizeof(ULONG_PTR), &ueNumberOfBytesRW)) + if(ReadProcessMemory(hProcess, (LPVOID)StackReadAddress, &StackReadBuffer, StackSecondReadSize, &ueNumberOfBytesRW)) { - RtlMoveMemory((LPVOID)((ULONG_PTR)&StackFinalBuffer + sizeof(ULONG_PTR) - StackSecondReadSize), (LPVOID)((ULONG_PTR)&StackReadBuffer + sizeof(ULONG_PTR) - StackSecondReadSize), StackSecondReadSize); + RtlMoveMemory(&StackFinalBuffer, &StackReadBuffer, StackSecondReadSize); } else { @@ -218,6 +195,7 @@ __declspec(dllexport) ULONG_PTR TITCALL GetJumpDestinationEx(HANDLE hProcess, UL DWORD CurrentInstructionSize; int ReadMemData = NULL; BYTE ReadByteData = NULL; + bool is32Bit = EngineGetCurrentContextMode() == EngineContextMode::X86; if(hProcess != NULL) { @@ -227,7 +205,7 @@ __declspec(dllexport) ULONG_PTR TITCALL GetJumpDestinationEx(HANDLE hProcess, UL if(ReadProcessMemory(hProcess, (LPVOID)InstructionAddress, ReadMemory, MAXIMUM_INSTRUCTION_SIZE, &ueNumberOfBytesRead)) { CompareMemory = (PMEMORY_CMP_HANDLER)ReadMemory; - CurrentInstructionSize = StaticLengthDisassemble(ReadMemory); + CurrentInstructionSize = EngineStaticLengthDisassemble(ReadMemory, is32Bit); if(CompareMemory->DataByte[0] == 0xE9 && CurrentInstructionSize == 5) { RtlMoveMemory(&ReadMemData, (LPVOID)((ULONG_PTR)ReadMemory + 1), 4); @@ -305,7 +283,7 @@ __declspec(dllexport) ULONG_PTR TITCALL GetJumpDestinationEx(HANDLE hProcess, UL { RtlMoveMemory(&ReadMemData, (LPVOID)((ULONG_PTR)ReadMemory + 2), 4); TargetedAddress = ReadMemData; - if(sizeof(HANDLE) == 8) + if(!is32Bit) { TargetedAddress = TargetedAddress + InstructionAddress; } @@ -314,7 +292,7 @@ __declspec(dllexport) ULONG_PTR TITCALL GetJumpDestinationEx(HANDLE hProcess, UL { RtlMoveMemory(&ReadMemData, (LPVOID)((ULONG_PTR)ReadMemory + 2), 4); TargetedAddress = ReadMemData; - if(sizeof(HANDLE) == 8) + if(!is32Bit) { TargetedAddress = TargetedAddress + InstructionAddress; } @@ -362,7 +340,7 @@ __declspec(dllexport) ULONG_PTR TITCALL GetJumpDestinationEx(HANDLE hProcess, UL else { CompareMemory = (PMEMORY_CMP_HANDLER)InstructionAddress; - CurrentInstructionSize = StaticLengthDisassemble((LPVOID)InstructionAddress); + CurrentInstructionSize = EngineStaticLengthDisassemble((LPVOID)InstructionAddress, is32Bit); if(CompareMemory->DataByte[0] == 0xE9 && CurrentInstructionSize == 5) { RtlMoveMemory(&ReadMemData, (LPVOID)((ULONG_PTR)InstructionAddress + 1), 4); @@ -440,7 +418,7 @@ __declspec(dllexport) ULONG_PTR TITCALL GetJumpDestinationEx(HANDLE hProcess, UL { RtlMoveMemory(&ReadMemData, (LPVOID)((ULONG_PTR)InstructionAddress + 2), 4); TargetedAddress = ReadMemData; - if(sizeof(HANDLE) == 8) + if(!is32Bit) { TargetedAddress = TargetedAddress + InstructionAddress; } @@ -449,7 +427,7 @@ __declspec(dllexport) ULONG_PTR TITCALL GetJumpDestinationEx(HANDLE hProcess, UL { RtlMoveMemory(&ReadMemData, (LPVOID)((ULONG_PTR)InstructionAddress + 2), 4); TargetedAddress = ReadMemData; - if(sizeof(HANDLE) == 8) + if(!is32Bit) { TargetedAddress = TargetedAddress + InstructionAddress; } @@ -530,7 +508,8 @@ __declspec(dllexport) bool TITCALL IsJumpGoingToExecuteEx(HANDLE hProcess, HANDL { ThreadEflags = (DWORD)RegFlags; } - DisassembledString = (char*)DisassembleEx(hProcess, (LPVOID)ThreadCIP, true); + auto mode = hThread != NULL ? EngineGetThreadContextMode(hThread) : EngineGetCurrentContextMode(); + DisassembledString = (char*)EngineDisassembleEx(hProcess, (LPVOID)ThreadCIP, true, mode == EngineContextMode::X86); if(DisassembledString != NULL) { if(ThreadEflags & (1 << 0)) diff --git a/TitanEngine/TitanEngine.Disassembler.cpp b/TitanEngine/TitanEngine.Disassembler.cpp index 577fb1e..d952977 100644 --- a/TitanEngine/TitanEngine.Disassembler.cpp +++ b/TitanEngine/TitanEngine.Disassembler.cpp @@ -1,15 +1,31 @@ #include "stdafx.h" #include "definitions.h" #include "Global.Debugger.h" +#include "Global.Engine.Context.h" #include "distorm.h" static char engineDisassembledInstruction[128]; -#if !defined(_WIN64) -_DecodeType DecodingType = Decode32Bits; +static bool DefaultIs32Bit() +{ +#ifndef _WIN64 + return true; #else -_DecodeType DecodingType = Decode64Bits; + return false; #endif +} + +static _DecodeType GetDecodingType(bool Is32Bit) +{ + return Is32Bit ? Decode32Bits : Decode64Bits; +} + +static bool GetProcessMode(HANDLE hProcess) +{ + if(hProcess != NULL && hProcess == dbgProcessInformation.hProcess && DBGEvent.dwThreadId != 0) + return EngineGetCurrentContextMode() == EngineContextMode::X86; + return DefaultIs32Bit(); +} SIZE_T IsBadReadPtrRemote(HANDLE hProcess, const VOID* lp, SIZE_T length) @@ -62,7 +78,7 @@ SIZE_T IsBadReadPtrRemote(HANDLE hProcess, const VOID* lp, SIZE_T length) return 0; } -__declspec(dllexport) void* TITCALL StaticDisassembleEx(ULONG_PTR DisassmStart, LPVOID DisassmAddress) +void* EngineStaticDisassembleEx(ULONG_PTR DisassmStart, LPVOID DisassmAddress, bool Is32Bit) { _DecodedInst engineDecodedInstructions[1]; unsigned int DecodedInstructionsCount = 0; @@ -70,7 +86,7 @@ __declspec(dllexport) void* TITCALL StaticDisassembleEx(ULONG_PTR DisassmStart, int MaxDisassmSize = MAXIMUM_INSTRUCTION_SIZE; // (int)IsBadReadPtrRemote(GetCurrentProcess(), DisassmAddress, MAXIMUM_INSTRUCTION_SIZE); if(MaxDisassmSize) { - if(distorm_decode((ULONG_PTR)DisassmStart, (const unsigned char*)DisassmAddress, MaxDisassmSize, DecodingType, engineDecodedInstructions, _countof(engineDecodedInstructions), &DecodedInstructionsCount) != DECRES_INPUTERR) + if(distorm_decode((ULONG_PTR)DisassmStart, (const unsigned char*)DisassmAddress, MaxDisassmSize, GetDecodingType(Is32Bit), engineDecodedInstructions, _countof(engineDecodedInstructions), &DecodedInstructionsCount) != DECRES_INPUTERR) { RtlZeroMemory(engineDisassembledInstruction, sizeof(engineDisassembledInstruction)); @@ -87,12 +103,17 @@ __declspec(dllexport) void* TITCALL StaticDisassembleEx(ULONG_PTR DisassmStart, return 0; } +__declspec(dllexport) void* TITCALL StaticDisassembleEx(ULONG_PTR DisassmStart, LPVOID DisassmAddress) +{ + return EngineStaticDisassembleEx(DisassmStart, DisassmAddress, DefaultIs32Bit()); +} + __declspec(dllexport) void* TITCALL StaticDisassemble(LPVOID DisassmAddress) { return StaticDisassembleEx((ULONG_PTR)DisassmAddress, DisassmAddress); } -__declspec(dllexport) void* TITCALL DisassembleEx(HANDLE hProcess, LPVOID DisassmAddress, bool ReturnInstructionType) +void* EngineDisassembleEx(HANDLE hProcess, LPVOID DisassmAddress, bool ReturnInstructionType, bool Is32Bit) { _DecodedInst engineDecodedInstructions[1]; unsigned int DecodedInstructionsCount = 0; @@ -107,7 +128,7 @@ __declspec(dllexport) void* TITCALL DisassembleEx(HANDLE hProcess, LPVOID Disass BOOL rpm = MemoryReadSafe(hProcess, DisassmAddress, readBuffer, MaxDisassmSize, 0); if(rpm) { - if(distorm_decode((ULONG_PTR)DisassmAddress, readBuffer, MaxDisassmSize, DecodingType, engineDecodedInstructions, _countof(engineDecodedInstructions), &DecodedInstructionsCount) != DECRES_INPUTERR) + if(distorm_decode((ULONG_PTR)DisassmAddress, readBuffer, MaxDisassmSize, GetDecodingType(Is32Bit), engineDecodedInstructions, _countof(engineDecodedInstructions), &DecodedInstructionsCount) != DECRES_INPUTERR) { RtlZeroMemory(engineDisassembledInstruction, sizeof(engineDisassembledInstruction)); @@ -130,17 +151,27 @@ __declspec(dllexport) void* TITCALL DisassembleEx(HANDLE hProcess, LPVOID Disass return 0; } +__declspec(dllexport) void* TITCALL DisassembleEx(HANDLE hProcess, LPVOID DisassmAddress, bool ReturnInstructionType) +{ + return EngineDisassembleEx(hProcess, DisassmAddress, ReturnInstructionType, GetProcessMode(hProcess)); +} + __declspec(dllexport) void* TITCALL Disassemble(LPVOID DisassmAddress) { - return(DisassembleEx(dbgProcessInformation.hProcess, DisassmAddress, false)); + return DisassembleEx(dbgProcessInformation.hProcess, DisassmAddress, false); +} + +long EngineStaticLengthDisassemble(LPVOID DisassmAddress, bool Is32Bit) +{ + return EngineLengthDisassembleEx(GetCurrentProcess(), DisassmAddress, Is32Bit); } __declspec(dllexport) long TITCALL StaticLengthDisassemble(LPVOID DisassmAddress) { - return LengthDisassembleEx(GetCurrentProcess(), DisassmAddress); + return EngineStaticLengthDisassemble(DisassmAddress, DefaultIs32Bit()); } -__declspec(dllexport) long TITCALL LengthDisassembleEx(HANDLE hProcess, LPVOID DisassmAddress) +long EngineLengthDisassembleEx(HANDLE hProcess, LPVOID DisassmAddress, bool Is32Bit) { unsigned int DecodedInstructionsCount = 0; _CodeInfo decomposerCi = {0}; @@ -155,7 +186,7 @@ __declspec(dllexport) long TITCALL LengthDisassembleEx(HANDLE hProcess, LPVOID D { decomposerCi.code = readBuffer; decomposerCi.codeLen = MaxDisassmSize; - decomposerCi.dt = DecodingType; + decomposerCi.dt = GetDecodingType(Is32Bit); decomposerCi.codeOffset = (LONG_PTR)DisassmAddress; if(distorm_decompose(&decomposerCi, decomposerResult, _countof(decomposerResult), &DecodedInstructionsCount) != DECRES_INPUTERR) @@ -171,6 +202,11 @@ __declspec(dllexport) long TITCALL LengthDisassembleEx(HANDLE hProcess, LPVOID D return -1; } +__declspec(dllexport) long TITCALL LengthDisassembleEx(HANDLE hProcess, LPVOID DisassmAddress) +{ + return EngineLengthDisassembleEx(hProcess, DisassmAddress, GetProcessMode(hProcess)); +} + __declspec(dllexport) long TITCALL LengthDisassemble(LPVOID DisassmAddress) { return LengthDisassembleEx(dbgProcessInformation.hProcess, DisassmAddress); diff --git a/TitanEngine/definitions.h b/TitanEngine/definitions.h index 2edf912..916b584 100644 --- a/TitanEngine/definitions.h +++ b/TitanEngine/definitions.h @@ -158,6 +158,14 @@ __declspec(dllexport) void* TITCALL Disassemble(LPVOID DisassmAddress); __declspec(dllexport) long TITCALL StaticLengthDisassemble(LPVOID DisassmAddress); __declspec(dllexport) long TITCALL LengthDisassembleEx(HANDLE hProcess, LPVOID DisassmAddress); __declspec(dllexport) long TITCALL LengthDisassemble(LPVOID DisassmAddress); + +// Internal variants used when the current thread mode is known. The exported +// static APIs keep their historical build-architecture behavior. +void* EngineStaticDisassembleEx(ULONG_PTR DisassmStart, LPVOID DisassmAddress, bool Is32Bit); +void* EngineDisassembleEx(HANDLE hProcess, LPVOID DisassmAddress, bool ReturnInstructionType, bool Is32Bit); +long EngineStaticLengthDisassemble(LPVOID DisassmAddress, bool Is32Bit); +long EngineLengthDisassembleEx(HANDLE hProcess, LPVOID DisassmAddress, bool Is32Bit); + __declspec(dllexport) void* TITCALL InitDebug(char* szFileName, char* szCommandLine, char* szCurrentFolder); __declspec(dllexport) void* TITCALL InitDebugW(wchar_t* szFileName, wchar_t* szCommandLine, wchar_t* szCurrentFolder); __declspec(dllexport) void* TITCALL InitNativeDebug(char* szFileName, char* szCommandLine, char* szCurrentFolder); From 69ed2e093cb6f126bf37ad647db8b0b8965ad9ef Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Sun, 2 Aug 2026 13:12:47 +0200 Subject: [PATCH 2/2] Support disabling ASLR for WoW64 targets --- TitanEngine/TitanEngine.Debugger.cpp | 129 ++++++++++++++++++++------- 1 file changed, 97 insertions(+), 32 deletions(-) diff --git a/TitanEngine/TitanEngine.Debugger.cpp b/TitanEngine/TitanEngine.Debugger.cpp index be18b2f..c39e2ac 100644 --- a/TitanEngine/TitanEngine.Debugger.cpp +++ b/TitanEngine/TitanEngine.Debugger.cpp @@ -43,15 +43,12 @@ __declspec(dllexport) void* TITCALL InitDebug(char* szFileName, char* szCommandL } } -static bool ProcessRelocations(char* imageCopy, ULONG_PTR imageSize, ULONG_PTR newImageBase, ULONG_PTR & oldImageBase) +template +static bool ProcessRelocationsForArchitecture(char* imageCopy, NtHeaders* pnth, ULONG_PTR newImageBase, ULONG_PTR & oldImageBase) { - auto pnth = RtlImageNtHeader(imageCopy); - if(pnth == nullptr) - return false; - - // Put the new base in the header - oldImageBase = pnth->OptionalHeader.ImageBase; - pnth->OptionalHeader.ImageBase = newImageBase; + // Put the new base in the header using the image's pointer width. + oldImageBase = ULONG_PTR(pnth->OptionalHeader.ImageBase); + pnth->OptionalHeader.ImageBase = decltype(pnth->OptionalHeader.ImageBase)(newImageBase); // Nothing to do if relocations are stripped if(pnth->FileHeader.Characteristics & IMAGE_FILE_RELOCS_STRIPPED) @@ -63,7 +60,7 @@ static bool ProcessRelocations(char* imageCopy, ULONG_PTR imageSize, ULONG_PTR n return true; // Process the relocations - auto delta = newImageBase - oldImageBase; + auto delta = LONG_PTR(newImageBase) - LONG_PTR(oldImageBase); auto relocationItr = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)imageCopy + relocDir.VirtualAddress); auto relocationEnd = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)relocationItr + relocDir.Size); @@ -80,6 +77,20 @@ static bool ProcessRelocations(char* imageCopy, ULONG_PTR imageSize, ULONG_PTR n return true; } +static bool ProcessRelocations(char* imageCopy, ULONG_PTR newImageBase, ULONG_PTR & oldImageBase) +{ + auto pnth = RtlImageNtHeader(imageCopy); + if(pnth == nullptr) + return false; + + auto magic = ((PIMAGE_NT_HEADERS32)pnth)->OptionalHeader.Magic; + if(magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) + return ProcessRelocationsForArchitecture(imageCopy, (PIMAGE_NT_HEADERS32)pnth, newImageBase, oldImageBase); + if(magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) + return ProcessRelocationsForArchitecture(imageCopy, (PIMAGE_NT_HEADERS64)pnth, newImageBase, oldImageBase); + return false; +} + static bool RelocateImage(HANDLE hProcess, PVOID imageBase, SIZE_T imageSize) { constexpr auto pageSize = 0x1000; @@ -100,7 +111,7 @@ static bool RelocateImage(HANDLE hProcess, PVOID imageBase, SIZE_T imageSize) // perform the actual relocations ULONG_PTR oldImageBase = 0; - auto success = ProcessRelocations(imageCopy, imageSize, (ULONG_PTR)imageBase, oldImageBase); + auto success = ProcessRelocations(imageCopy, (ULONG_PTR)imageBase, oldImageBase); // write back the pages auto memWrite = [hProcess](PVOID ptr, LPCVOID data, SIZE_T size) @@ -165,28 +176,45 @@ static bool HollowProcessWithoutASLR(const wchar_t* szFileName, PROCESS_INFORMAT auto hMapping = CreateFileMappingW(hFile, nullptr, SEC_IMAGE | PAGE_READONLY, 0, 0, nullptr); if(hMapping) { - CONTEXT ctx; + CONTEXT ctx = {}; ctx.ContextFlags = CONTEXT_ALL; if(GetThreadContext(pi.hThread, &ctx)) { - PVOID imageBase; - // TODO: support wow64 processes + bool isWow64 = false; + ULONG_PTR pebAddress = 0; + SIZE_T imageBaseOffset = 0; + SIZE_T imageBaseSize = 0; #ifdef _WIN64 - auto & pebRegister = ctx.Rdx; - auto & entryPointRegister = ctx.Rcx; -#else - auto & pebRegister = ctx.Ebx; - auto & entryPointRegister = ctx.Eax; -#endif // _WIN64 - if(ReadProcessMemory(pi.hProcess, (char*)pebRegister + offsetof(PEB, ImageBaseAddress), &imageBase, sizeof(PVOID), nullptr)) + ULONG returnLength = 0; + if(NT_SUCCESS(NtQueryInformationProcess(pi.hProcess, ProcessWow64Information, &pebAddress, sizeof(pebAddress), &returnLength)) && pebAddress != 0) { - if(ULONG_PTR(imageBase) == DebugModuleImageBase) + isWow64 = true; + imageBaseOffset = offsetof(PEB32, ImageBaseAddress); + imageBaseSize = sizeof(DWORD); + } + else + { + pebAddress = ctx.Rdx; + imageBaseOffset = offsetof(PEB64, ImageBaseAddress); + imageBaseSize = sizeof(DWORD64); + } +#else + pebAddress = ctx.Ebx; + imageBaseOffset = offsetof(PEB32, ImageBaseAddress); + imageBaseSize = sizeof(DWORD); +#endif // _WIN64 + + ULONG_PTR imageBaseValue = 0; + if(ReadProcessMemory(pi.hProcess, (char*)pebAddress + imageBaseOffset, &imageBaseValue, imageBaseSize, nullptr)) + { + if(imageBaseValue == DebugModuleImageBase) { // Already at the right base success = true; } else { + auto imageBase = PVOID(imageBaseValue); auto status = NtUnmapViewOfSection(pi.hProcess, imageBase); if(status == STATUS_SUCCESS) { @@ -201,22 +229,59 @@ static bool HollowProcessWithoutASLR(const wchar_t* szFileName, PROCESS_INFORMAT } if(status == STATUS_SUCCESS || status == STATUS_IMAGE_NOT_AT_BASE) { - auto pebOk = WriteProcessMemory(pi.hProcess, (char*)pebRegister + offsetof(PEB, ImageBaseAddress), &imageBase, sizeof(PVOID), nullptr); + imageBaseValue = ULONG_PTR(imageBase); + auto pebOk = WriteProcessMemory(pi.hProcess, (char*)pebAddress + imageBaseOffset, &imageBaseValue, imageBaseSize, nullptr); +#ifdef _WIN64 + if(pebOk && isWow64) + { + PROCESS_BASIC_INFORMATION processInfo = {}; + if(NT_SUCCESS(NtQueryInformationProcess(pi.hProcess, ProcessBasicInformation, &processInfo, sizeof(processInfo), &returnLength))) + { + DWORD64 imageBase64 = imageBaseValue; + pebOk = WriteProcessMemory(pi.hProcess, (char*)processInfo.PebBaseAddress + offsetof(PEB64, ImageBaseAddress), &imageBase64, sizeof(imageBase64), nullptr); + } + else + { + pebOk = false; + } + } +#else + if(pebOk && IsThisProcessWow64()) + { + auto peb64 = GetPEBLocation64(pi.hProcess); + DWORD64 imageBase64 = imageBaseValue; + pebOk = peb64 != nullptr && WriteProcessMemory(pi.hProcess, (char*)peb64 + offsetof(PEB64, ImageBaseAddress), &imageBase64, sizeof(imageBase64), nullptr); + } +#endif // _WIN64 auto relocatedOk = RelocateImage(pi.hProcess, imageBase, viewSize); if(pebOk && relocatedOk) { - auto expectedBase = DebugModuleImageBase == ULONG_PTR(imageBase); - DebugModuleImageBase = ULONG_PTR(imageBase); - entryPointRegister = DebugModuleImageBase + DebugModuleEntryPoint; - if(SetThreadContext(pi.hThread, &ctx)) + auto expectedBase = DebugModuleImageBase == imageBaseValue; + DebugModuleImageBase = imageBaseValue; + auto entryPoint = DebugModuleImageBase + DebugModuleEntryPoint; + bool contextOk = false; +#ifdef _WIN64 + if(isWow64) { - success = expectedBase; -#ifndef _WIN64 - // For Wow64 processes, also adjust the 64-bit PEB - if(IsThisProcessWow64() && !WriteProcessMemory(pi.hProcess, (char*)pebRegister - 0x1000 + 0x10, &imageBase, sizeof(PVOID), nullptr)) - success = false; -#endif // _WIN64 + WOW64_CONTEXT ctx32 = {}; + ctx32.ContextFlags = WOW64_CONTEXT_INTEGER; + if(Wow64GetThreadContext(pi.hThread, &ctx32)) + { + ctx32.Eax = DWORD(entryPoint); + contextOk = Wow64SetThreadContext(pi.hThread, &ctx32) != FALSE; + } } + else + { + ctx.Rcx = entryPoint; + contextOk = SetThreadContext(pi.hThread, &ctx) != FALSE; + } +#else + ctx.Eax = DWORD(entryPoint); + contextOk = SetThreadContext(pi.hThread, &ctx) != FALSE; +#endif // _WIN64 + if(contextOk) + success = expectedBase; } } }