From 4276bfa2f72103f1bd31754e71c0d851cb762116 Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Mon, 7 Sep 2026 02:41:27 +0200 Subject: [PATCH] Clean up TTD APIs --- StaticEngine/TitanEngine.h | 17 +- StaticEngine/TitanEngineEmulator.cpp | 13 +- TitanEngineEmulator/Emulator.h | 214 +++++++++++++++++++- TitanEngineEmulator/TitanEngine.h | 17 +- TitanEngineEmulator/TitanEngineEmulator.cpp | 9 +- 5 files changed, 256 insertions(+), 14 deletions(-) diff --git a/StaticEngine/TitanEngine.h b/StaticEngine/TitanEngine.h index e189d0e..2894b15 100644 --- a/StaticEngine/TitanEngine.h +++ b/StaticEngine/TitanEngine.h @@ -11,6 +11,7 @@ #include typedef void(TITCALL* TITANCBSTEP)(); +typedef void(TITCALL* TITANCBPAUSE)(); #pragma pack(push, 1) @@ -63,6 +64,8 @@ enum TitanSessionCapability : uint64_t UE_SESSION_CAP_NATIVE_HANDLES = 1ull << 12, UE_SESSION_CAP_EXCEPTION_CONTINUE = 1ull << 13, UE_SESSION_CAP_TIMELINE_STATE = 1ull << 14, + UE_SESSION_CAP_PAUSE_EXECUTION = 1ull << 16, + UE_SESSION_CAP_NAVIGABLE_PROCESS_EXIT = 1ull << 17, }; typedef struct @@ -103,6 +106,14 @@ typedef struct #define UE_ENGINE_RESET_CUSTOM_HANDLER 7 #define UE_ENGINE_CALL_PLUGIN_DEBUG_CALLBACK 8 #define UE_ENGINE_SET_DEBUG_PRIVILEGE 9 +#define UE_ENGINE_WOW64_SINGLE_STEP_WORKAROUND 14 + +enum TitanPausePolicy +{ + UE_PAUSE_POLICY_NONINVASIVE = 0, + UE_PAUSE_POLICY_STANDARD = 1, + UE_PAUSE_POLICY_AGGRESSIVE = 2, +}; #define UE_OPTION_REMOVEALL 1 #define UE_OPTION_DISABLEALL 2 @@ -896,8 +907,8 @@ __declspec(dllexport) bool TITCALL GetSessionInfo(TITAN_SESSION_INFO* SessionInf __declspec(dllexport) bool TITCALL ReplayGetPosition(TITAN_REPLAY_POSITION* Position); __declspec(dllexport) bool TITCALL ReplayGetExtent(TITAN_REPLAY_POSITION* First, TITAN_REPLAY_POSITION* Last); __declspec(dllexport) bool TITCALL ReplaySetPosition(const TITAN_REPLAY_POSITION* Position); -__declspec(dllexport) bool TITCALL ReplayRun(bool Reverse); -__declspec(dllexport) bool TITCALL ReplayStep(bool Reverse, bool StepOver, TITANCBSTEP StepCallBack); +__declspec(dllexport) bool TITCALL ReplayRunBack(); +__declspec(dllexport) bool TITCALL ReplayStepBack(TITANCBSTEP StepCallBack); __declspec(dllexport) void* TITCALL InitDebugEx(const char* szFileName, const char* szCommandLine, const char* szCurrentFolder, LPVOID EntryCallBack); __declspec(dllexport) void* TITCALL InitDebugExW(const wchar_t* szFileName, const wchar_t* szCommandLine, const wchar_t* szCurrentFolder, LPVOID EntryCallBack); __declspec(dllexport) void* TITCALL InitDLLDebug(const char* szFileName, bool ReserveModuleBase, const char* szCommandLine, const char* szCurrentFolder, LPVOID EntryCallBack); @@ -1096,7 +1107,7 @@ __declspec(dllexport) bool TITCALL TitanGetModulePathW(HANDLE hProcess, ULONG_PT __declspec(dllexport) bool TITCALL TitanCloseHandle(HANDLE hEngineHandle); __declspec(dllexport) bool TITCALL ProcessIsWow64(HANDLE hProcess, PBOOL isWow64); __declspec(dllexport) bool TITCALL TitanTerminateProcess(HANDLE hProcess, DWORD exitCode); -__declspec(dllexport) bool TITCALL TitanDebugBreakProcess(HANDLE hProcess); +__declspec(dllexport) bool TITCALL RequestPause(TitanPausePolicy MaximumPolicy, TITANCBPAUSE PauseCallback); __declspec(dllexport) HANDLE TITCALL TitanCreateRemoteThread(HANDLE hProcess, LPTHREAD_START_ROUTINE start, LPVOID argument, DWORD creationFlags, LPDWORD threadId); __declspec(dllexport) DWORD TITCALL TitanSuspendThread(HANDLE hThread); __declspec(dllexport) DWORD TITCALL TitanResumeThread(HANDLE hThread); diff --git a/StaticEngine/TitanEngineEmulator.cpp b/StaticEngine/TitanEngineEmulator.cpp index 1cc6435..752b8d2 100644 --- a/StaticEngine/TitanEngineEmulator.cpp +++ b/StaticEngine/TitanEngineEmulator.cpp @@ -69,13 +69,13 @@ __declspec(dllexport) bool TITCALL ReplaySetPosition(const TITAN_REPLAY_POSITION return false; } -__declspec(dllexport) bool TITCALL ReplayRun(bool Reverse) +__declspec(dllexport) bool TITCALL ReplayRunBack() { SetLastError(ERROR_NOT_SUPPORTED); return false; } -__declspec(dllexport) bool TITCALL ReplayStep(bool Reverse, bool StepOver, TITANCBSTEP StepCallBack) +__declspec(dllexport) bool TITCALL ReplayStepBack(TITANCBSTEP StepCallBack) { SetLastError(ERROR_NOT_SUPPORTED); return false; @@ -241,7 +241,14 @@ __declspec(dllexport) bool TITCALL ProcessIsWow64(HANDLE hProcess, PBOOL isWow64 } __declspec(dllexport) bool TITCALL TitanTerminateProcess(HANDLE hProcess, DWORD exitCode) { return !!TerminateProcess(hProcess, exitCode); } -__declspec(dllexport) bool TITCALL TitanDebugBreakProcess(HANDLE hProcess) { return !!DebugBreakProcess(hProcess); } +__declspec(dllexport) bool TITCALL RequestPause(TitanPausePolicy MaximumPolicy, TITANCBPAUSE PauseCallback) +{ + if(MaximumPolicy < UE_PAUSE_POLICY_NONINVASIVE || MaximumPolicy > UE_PAUSE_POLICY_AGGRESSIVE || !PauseCallback) + SetLastError(ERROR_INVALID_PARAMETER); + else + SetLastError(ERROR_NOT_SUPPORTED); + return false; +} __declspec(dllexport) HANDLE TITCALL TitanCreateRemoteThread(HANDLE hProcess, LPTHREAD_START_ROUTINE start, LPVOID argument, DWORD creationFlags, LPDWORD threadId) { return CreateRemoteThread(hProcess, nullptr, 0, start, argument, creationFlags, threadId); } __declspec(dllexport) DWORD TITCALL TitanSuspendThread(HANDLE hThread) { return SuspendThread(hThread); } __declspec(dllexport) DWORD TITCALL TitanResumeThread(HANDLE hThread) { return ResumeThread(hThread); } diff --git a/TitanEngineEmulator/Emulator.h b/TitanEngineEmulator/Emulator.h index 93bb5a1..0f56a81 100644 --- a/TitanEngineEmulator/Emulator.h +++ b/TitanEngineEmulator/Emulator.h @@ -8,6 +8,7 @@ #include "PEB.h" #include "Global.Engine.Context.h" #include "Hider.h" +#include // Related to floating x87 registers #define GetSTInTOPStackFromStatusWord(StatusWord) ((StatusWord & 0x3800) >> 11) @@ -26,6 +27,7 @@ public: //Debugger PROCESS_INFORMATION* InitDebugW(const wchar_t* szFileName, const wchar_t* szCommandLine, const wchar_t* szCurrentFolder) { + cancelPause(); mCbATTACHBREAKPOINT = nullptr; if(!Init(szFileName, szCommandLine, szCurrentFolder)) return nullptr; @@ -62,11 +64,13 @@ public: bool StopDebug() { + cancelPause(); return Stop(); } bool AttachDebugger(DWORD ProcessId, bool KillOnExit, LPVOID DebugInfo, LPVOID CallBack) { + cancelPause(); if(!Attach(ProcessId)) return false; mCbATTACHBREAKPOINT = STEPCALLBACK(CallBack); @@ -191,6 +195,84 @@ public: } } + bool RequestPause(TitanPausePolicy MaximumPolicy, TITANCBPAUSE PauseCallback) + { + if(MaximumPolicy < UE_PAUSE_POLICY_NONINVASIVE || MaximumPolicy > UE_PAUSE_POLICY_AGGRESSIVE || + !PauseCallback || !mProcess || !mThread || !mMainProcess.hProcess) + { + SetLastError(ERROR_INVALID_PARAMETER); + return false; + } + + bool escalate = false; + { + std::lock_guard lock(mPauseMutex); + if(mPauseCallback) + { + escalate = MaximumPolicy == UE_PAUSE_POLICY_AGGRESSIVE && !mPauseBreakInExpected && + GetTickCount64() - mPauseRequestTime >= 2000; + if(!escalate) + return true; + } + else + { + if(MaximumPolicy == UE_PAUSE_POLICY_NONINVASIVE) + { + SetLastError(ERROR_NOT_SUPPORTED); + return false; + } + mPauseCallback = PauseCallback; + mPauseRequestTime = GetTickCount64(); + } + } + if(escalate) + { + requestPauseBreakIn(); // The current-IP request remains pending on failure. + return true; + } + + auto thread = mAttachedToProcess ? mMainProcess.hThread : mThread->hThread; + auto threadId = mAttachedToProcess ? mMainProcess.dwThreadId : mThread->dwThreadId; + auto previousSuspendCount = SuspendThread(thread); + if(previousSuspendCount != 0) + { + if(previousSuspendCount != (DWORD)-1) + ResumeThread(thread); + if(MaximumPolicy == UE_PAUSE_POLICY_AGGRESSIVE && requestPauseBreakIn()) + return true; + cancelPause(); + SetLastError(ERROR_BUSY); + return false; + } + + CONTEXT context = {}; + context.ContextFlags = CONTEXT_CONTROL; + bool armed = GetThreadContext(thread, &context) != FALSE; +#ifdef _WIN64 + auto cip = (ULONG_PTR)context.Rip; +#else + auto cip = (ULONG_PTR)context.Eip; +#endif + if(armed) + { + armed = mProcess->SetBreakpoint(cip, [this](const BreakpointInfo &) + { + completePause(); + }, true); + } + if(armed) + PostThreadMessageW(threadId, WM_NULL, 0, 0); + auto resumed = ResumeThread(thread) != (DWORD)-1; + if(armed && resumed) + return true; + if(armed) + mProcess->DeleteBreakpoint(cip); + if(MaximumPolicy == UE_PAUSE_POLICY_AGGRESSIVE && requestPauseBreakIn()) + return true; + cancelPause(); + return false; + } + void SetEngineVariable(DWORD VariableId, bool VariableSet) { switch(VariableId) @@ -207,6 +289,9 @@ public: case UE_ENGINE_NO_CONSOLE_WINDOW: mNoConsoleWindow = VariableSet; break; + case UE_ENGINE_WOW64_SINGLE_STEP_WORKAROUND: + mWow64SingleStepWorkaround = VariableSet; + break; } } @@ -334,8 +419,24 @@ public: void StepInto(LPVOID CallBack) { - if(!mThread || !CallBack) + if(!mThread || !mProcess || !CallBack) return; +#ifndef _WIN64 + if(mWow64SingleStepWorkaround) + { + unsigned char data[7] = {}; + auto cip = GetContextDataEx(mThread->hThread, UE_CIP); + if(mProcess->MemReadSafe(cip, data, sizeof(data), nullptr) && + data[0] == 0xEA && data[5] == 0x33 && data[6] == 0x00) + { + ULONG_PTR returnAddress = 0; + auto csp = GetContextDataEx(mThread->hThread, UE_CSP); + if(mProcess->MemReadSafe(csp, &returnAddress, sizeof(returnAddress), nullptr) && + SetBPX(returnAddress, UE_SINGLESHOOT, CallBack)) + return; + } + } +#endif mThread->StepInto(STEPCALLBACK(CallBack)); } @@ -910,6 +1011,12 @@ protected: void cbCreateThreadEvent(const CREATE_THREAD_DEBUG_INFO & createThread, const Thread & thread) override { + { + std::lock_guard lock(mPauseMutex); + if(mPauseBreakInExpected && mPauseBreakInStart && + (ULONG_PTR)createThread.lpStartAddress == mPauseBreakInStart) + mPauseBreakInThreadId = thread.dwThreadId; + } if(mCbCREATETHREAD) mCbCREATETHREAD(&createThread); } @@ -932,6 +1039,23 @@ protected: mCbUNLOADDLL(&unloadDll); } + void cbExceptionEvent(const EXCEPTION_DEBUG_INFO & exceptionInfo) override + { + bool pauseBreakIn = false; + { + std::lock_guard lock(mPauseMutex); + pauseBreakIn = mPauseBreakInExpected && mThread && mPauseBreakInThreadId && + mThread->dwThreadId == mPauseBreakInThreadId && + (exceptionInfo.ExceptionRecord.ExceptionCode == STATUS_BREAKPOINT || + exceptionInfo.ExceptionRecord.ExceptionCode == STATUS_WX86_BREAKPOINT); + } + if(pauseBreakIn) + { + mContinueStatus = DBG_CONTINUE; + completePause(); + } + } + void cbUnhandledException(const EXCEPTION_RECORD & exceptionRecord, bool firstChance) override { if(mCbUNHANDLEDEXCEPTION) @@ -967,6 +1091,87 @@ protected: } private: //functions + ULONG_PTR pauseBreakInStartAddress() + { + auto localNtdll = GetModuleHandleW(L"ntdll.dll"); + auto localStart = localNtdll ? GetProcAddress(localNtdll, "DbgUiRemoteBreakin") : nullptr; + if(!localStart || !mMainProcess.dwProcessId) + return 0; + auto snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, + mMainProcess.dwProcessId); + if(snapshot == INVALID_HANDLE_VALUE) + return 0; + MODULEENTRY32W module = {}; + module.dwSize = sizeof(module); + ULONG_PTR result = 0; + if(Module32FirstW(snapshot, &module)) + { + do + { + if(!_wcsicmp(module.szModule, L"ntdll.dll")) + { + result = (ULONG_PTR)module.modBaseAddr + + (ULONG_PTR)localStart - (ULONG_PTR)localNtdll; + break; + } + } + while(Module32NextW(snapshot, &module)); + } + CloseHandle(snapshot); + return result; + } + + bool requestPauseBreakIn() + { + auto startAddress = pauseBreakInStartAddress(); + if(!startAddress) + { + SetLastError(ERROR_PROC_NOT_FOUND); + return false; + } + { + std::lock_guard lock(mPauseMutex); + mPauseBreakInExpected = true; + mPauseBreakInStart = startAddress; + mPauseBreakInThreadId = 0; + } + if(DebugBreakProcess(mMainProcess.hProcess)) + return true; + { + std::lock_guard lock(mPauseMutex); + mPauseBreakInExpected = false; + mPauseBreakInStart = 0; + mPauseBreakInThreadId = 0; + } + return false; + } + + void completePause() + { + TITANCBPAUSE callback = nullptr; + { + std::lock_guard lock(mPauseMutex); + callback = mPauseCallback; + mPauseCallback = nullptr; + mPauseRequestTime = 0; + mPauseBreakInExpected = false; + mPauseBreakInStart = 0; + mPauseBreakInThreadId = 0; + } + if(callback) + callback(); + } + + void cancelPause() + { + std::lock_guard lock(mPauseMutex); + mPauseCallback = nullptr; + mPauseRequestTime = 0; + mPauseBreakInExpected = false; + mPauseBreakInStart = 0; + mPauseBreakInThreadId = 0; + } + inline Registers::R registerFromDword(DWORD IndexOfRegister) { switch(IndexOfRegister) @@ -1343,6 +1548,13 @@ private: //functions private: //variables bool mSetDebugPrivilege = false; + bool mWow64SingleStepWorkaround = true; + std::mutex mPauseMutex; + TITANCBPAUSE mPauseCallback = nullptr; + ULONGLONG mPauseRequestTime = 0; + bool mPauseBreakInExpected = false; + ULONG_PTR mPauseBreakInStart = 0; + DWORD mPauseBreakInThreadId = 0; typedef void(*CUSTOMHANDLER)(const void*); typedef void(*STEPCALLBACK)(); typedef STEPCALLBACK BPCALLBACK; diff --git a/TitanEngineEmulator/TitanEngine.h b/TitanEngineEmulator/TitanEngine.h index f9412b6..284dd1d 100644 --- a/TitanEngineEmulator/TitanEngine.h +++ b/TitanEngineEmulator/TitanEngine.h @@ -11,6 +11,7 @@ #include typedef void(TITCALL* TITANCBSTEP)(); +typedef void(TITCALL* TITANCBPAUSE)(); #pragma pack(push, 1) @@ -63,6 +64,8 @@ enum TitanSessionCapability : uint64_t UE_SESSION_CAP_NATIVE_HANDLES = 1ull << 12, UE_SESSION_CAP_EXCEPTION_CONTINUE = 1ull << 13, UE_SESSION_CAP_TIMELINE_STATE = 1ull << 14, + UE_SESSION_CAP_PAUSE_EXECUTION = 1ull << 16, + UE_SESSION_CAP_NAVIGABLE_PROCESS_EXIT = 1ull << 17, }; typedef struct @@ -107,6 +110,14 @@ typedef struct #define UE_ENGINE_MEMBP_ALT 11 #define UE_ENGINE_DISABLE_ASLR 12 #define UE_ENGINE_SAFE_STEP 13 +#define UE_ENGINE_WOW64_SINGLE_STEP_WORKAROUND 14 + +enum TitanPausePolicy +{ + UE_PAUSE_POLICY_NONINVASIVE = 0, + UE_PAUSE_POLICY_STANDARD = 1, + UE_PAUSE_POLICY_AGGRESSIVE = 2, +}; #define UE_OPTION_REMOVEALL 1 #define UE_OPTION_DISABLEALL 2 @@ -900,8 +911,8 @@ __declspec(dllexport) bool TITCALL GetSessionInfo(TITAN_SESSION_INFO* SessionInf __declspec(dllexport) bool TITCALL ReplayGetPosition(TITAN_REPLAY_POSITION* Position); __declspec(dllexport) bool TITCALL ReplayGetExtent(TITAN_REPLAY_POSITION* First, TITAN_REPLAY_POSITION* Last); __declspec(dllexport) bool TITCALL ReplaySetPosition(const TITAN_REPLAY_POSITION* Position); -__declspec(dllexport) bool TITCALL ReplayRun(bool Reverse); -__declspec(dllexport) bool TITCALL ReplayStep(bool Reverse, bool StepOver, TITANCBSTEP StepCallBack); +__declspec(dllexport) bool TITCALL ReplayRunBack(); +__declspec(dllexport) bool TITCALL ReplayStepBack(TITANCBSTEP StepCallBack); __declspec(dllexport) void* TITCALL InitNativeDebug(char* szFileName, char* szCommandLine, char* szCurrentFolder); __declspec(dllexport) void* TITCALL InitNativeDebugW(const wchar_t* szFileName, const wchar_t* szCommandLine, const wchar_t* szCurrentFolder); __declspec(dllexport) void* TITCALL InitDebugEx(const char* szFileName, const char* szCommandLine, const char* szCurrentFolder, LPVOID EntryCallBack); @@ -1102,7 +1113,7 @@ __declspec(dllexport) bool TITCALL TitanGetModulePathW(HANDLE hProcess, ULONG_PT __declspec(dllexport) bool TITCALL TitanCloseHandle(HANDLE hEngineHandle); __declspec(dllexport) bool TITCALL ProcessIsWow64(HANDLE hProcess, PBOOL isWow64); __declspec(dllexport) bool TITCALL TitanTerminateProcess(HANDLE hProcess, DWORD exitCode); -__declspec(dllexport) bool TITCALL TitanDebugBreakProcess(HANDLE hProcess); +__declspec(dllexport) bool TITCALL RequestPause(TitanPausePolicy MaximumPolicy, TITANCBPAUSE PauseCallback); __declspec(dllexport) HANDLE TITCALL TitanCreateRemoteThread(HANDLE hProcess, LPTHREAD_START_ROUTINE start, LPVOID argument, DWORD creationFlags, LPDWORD threadId); __declspec(dllexport) DWORD TITCALL TitanSuspendThread(HANDLE hThread); __declspec(dllexport) DWORD TITCALL TitanResumeThread(HANDLE hThread); diff --git a/TitanEngineEmulator/TitanEngineEmulator.cpp b/TitanEngineEmulator/TitanEngineEmulator.cpp index 03f015b..d7e4091 100644 --- a/TitanEngineEmulator/TitanEngineEmulator.cpp +++ b/TitanEngineEmulator/TitanEngineEmulator.cpp @@ -36,7 +36,8 @@ __declspec(dllexport) bool TITCALL GetSessionInfo(TITAN_SESSION_INFO* SessionInf UE_SESSION_CAP_CONTEXT_READ | UE_SESSION_CAP_FORWARD_EXECUTION | UE_SESSION_CAP_MEMORY_WRITE | UE_SESSION_CAP_CONTEXT_WRITE | UE_SESSION_CAP_PROCESS_CONTROL | UE_SESSION_CAP_THREAD_CONTROL | - UE_SESSION_CAP_NATIVE_HANDLES | UE_SESSION_CAP_EXCEPTION_CONTINUE; + UE_SESSION_CAP_NATIVE_HANDLES | UE_SESSION_CAP_EXCEPTION_CONTINUE | + UE_SESSION_CAP_PAUSE_EXECUTION; #ifdef _WIN64 SessionInfo->machineType = IMAGE_FILE_MACHINE_AMD64; #else @@ -69,13 +70,13 @@ __declspec(dllexport) bool TITCALL ReplaySetPosition(const TITAN_REPLAY_POSITION return false; } -__declspec(dllexport) bool TITCALL ReplayRun(bool Reverse) +__declspec(dllexport) bool TITCALL ReplayRunBack() { SetLastError(ERROR_NOT_SUPPORTED); return false; } -__declspec(dllexport) bool TITCALL ReplayStep(bool Reverse, bool StepOver, TITANCBSTEP StepCallBack) +__declspec(dllexport) bool TITCALL ReplayStepBack(TITANCBSTEP StepCallBack) { SetLastError(ERROR_NOT_SUPPORTED); return false; @@ -241,7 +242,7 @@ __declspec(dllexport) bool TITCALL ProcessIsWow64(HANDLE hProcess, PBOOL isWow64 } __declspec(dllexport) bool TITCALL TitanTerminateProcess(HANDLE hProcess, DWORD exitCode) { return !!TerminateProcess(hProcess, exitCode); } -__declspec(dllexport) bool TITCALL TitanDebugBreakProcess(HANDLE hProcess) { return !!DebugBreakProcess(hProcess); } +__declspec(dllexport) bool TITCALL RequestPause(TitanPausePolicy MaximumPolicy, TITANCBPAUSE PauseCallback) { return emu.RequestPause(MaximumPolicy, PauseCallback); } __declspec(dllexport) HANDLE TITCALL TitanCreateRemoteThread(HANDLE hProcess, LPTHREAD_START_ROUTINE start, LPVOID argument, DWORD creationFlags, LPDWORD threadId) { return CreateRemoteThread(hProcess, nullptr, 0, start, argument, creationFlags, threadId); } __declspec(dllexport) DWORD TITCALL TitanSuspendThread(HANDLE hThread) { return SuspendThread(hThread); } __declspec(dllexport) DWORD TITCALL TitanResumeThread(HANDLE hThread) { return ResumeThread(hThread); }