diff --git a/GleeBug/Debugger.Global.h b/GleeBug/Debugger.Global.h index cd54981..b31c12c 100644 --- a/GleeBug/Debugger.Global.h +++ b/GleeBug/Debugger.Global.h @@ -12,6 +12,14 @@ #define PAGE_SIZE 0x1000 #endif // PAGE_SIZE +#ifndef STATUS_WX86_SINGLE_STEP +#define STATUS_WX86_SINGLE_STEP ((DWORD)0x4000001EL) +#endif // STATUS_WX86_SINGLE_STEP + +#ifndef STATUS_WX86_BREAKPOINT +#define STATUS_WX86_BREAKPOINT ((DWORD)0x4000001FL) +#endif // STATUS_WX86_BREAKPOINT + namespace GleeBug { //forward declarations diff --git a/GleeBug/Debugger.Loop.Exception.cpp b/GleeBug/Debugger.Loop.Exception.cpp index 1a430d5..fb28d39 100644 --- a/GleeBug/Debugger.Loop.Exception.cpp +++ b/GleeBug/Debugger.Loop.Exception.cpp @@ -560,9 +560,11 @@ namespace GleeBug switch(exceptionInfo.ExceptionRecord.ExceptionCode) { case STATUS_BREAKPOINT: + case STATUS_WX86_BREAKPOINT: exceptionBreakpoint(exceptionRecord, firstChance); break; case STATUS_SINGLE_STEP: + case STATUS_WX86_SINGLE_STEP: exceptionSingleStep(exceptionRecord, firstChance); break; case STATUS_GUARD_PAGE_VIOLATION: diff --git a/GleeBug/Debugger.Loop.cpp b/GleeBug/Debugger.Loop.cpp index 3b089e2..74a6be0 100644 --- a/GleeBug/Debugger.Loop.cpp +++ b/GleeBug/Debugger.Loop.cpp @@ -185,7 +185,9 @@ namespace GleeBug unloadDllEvent(mDebugEvent.u.UnloadDll); break; case EXCEPTION_DEBUG_EVENT: - if(IsDbgReplyLaterSupported && mDebugEvent.u.Exception.ExceptionRecord.ExceptionCode == STATUS_SINGLE_STEP) + { + auto exceptionCode = mDebugEvent.u.Exception.ExceptionRecord.ExceptionCode; + if(IsDbgReplyLaterSupported && (exceptionCode == STATUS_SINGLE_STEP || exceptionCode == STATUS_WX86_SINGLE_STEP)) { // Resume the other threads since we are done processing the single step for(auto & itr : SuspendedThreads) @@ -196,6 +198,7 @@ namespace GleeBug } exceptionEvent(mDebugEvent.u.Exception); break; + } case OUTPUT_DEBUG_STRING_EVENT: debugStringEvent(mDebugEvent.u.DebugString); break; diff --git a/GleeBug/Debugger.Process.cpp b/GleeBug/Debugger.Process.cpp index 19e8b45..19ae33e 100644 --- a/GleeBug/Debugger.Process.cpp +++ b/GleeBug/Debugger.Process.cpp @@ -62,15 +62,26 @@ namespace GleeBug } } + static ZydisMachineMode GetMachineMode(const Registers & registers) + { +#ifdef _WIN64 + return registers.Is32BitMode() ? ZYDIS_MACHINE_MODE_LONG_COMPAT_32 : ZYDIS_MACHINE_MODE_LONG_64; +#else + return ZYDIS_MACHINE_MODE_LEGACY_32; +#endif // _WIN64 + } + void Process::StepOver(const StepCallback & cbStep) { - auto gip = Registers(thread->hThread, CONTEXT_CONTROL).Gip(); + Registers registers(thread->hThread, CONTEXT_CONTROL); + auto gip = registers.Gip(); + auto machineMode = GetMachineMode(registers); unsigned char data[16]; if(MemReadSafe(gip, data, sizeof(data))) { ZydisDisassembledInstruction instruction; if(ZYAN_SUCCESS(ZydisDisassembleIntel( - GleeArchValue(ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_MACHINE_MODE_LONG_COMPAT_32), + machineMode, gip, data, sizeof(data), @@ -111,6 +122,7 @@ namespace GleeBug // Check if we're currently stepping on a pushf instruction auto isPushf = false; + auto pointerSize = registers.PointerSize(); { auto gip = registers.Gip(); unsigned char data[16]; @@ -118,7 +130,7 @@ namespace GleeBug { ZydisDisassembledInstruction instruction; if(ZYAN_SUCCESS(ZydisDisassembleIntel( - GleeArchValue(ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_MACHINE_MODE_LONG_COMPAT_32), + GetMachineMode(registers), gip, data, sizeof(data), @@ -141,15 +153,16 @@ namespace GleeBug if(isPushf) { - thread->cbInternalStep = [this, cbStep]() + thread->cbInternalStep = [this, cbStep, pointerSize]() { - // Remove the trap flag from the stack + // Remove the trap flag from the stack using the execution + // mode's pointer width (PUSHFD is four bytes under WoW64). auto gsp = Registers(this->thread->hThread).Gsp(); - GleeBug::ptr data; - if(MemReadUnsafe(gsp, &data, sizeof(data))) + uint64 data = 0; + if(MemReadUnsafe(gsp, &data, pointerSize)) { - data &= ~(int)Registers::F::Trap; - MemWriteUnsafe(gsp, &data, sizeof(data)); + data &= ~uint64(Registers::F::Trap); + MemWriteUnsafe(gsp, &data, pointerSize); } cbStep(); diff --git a/GleeBug/Debugger.Thread.Registers.cpp b/GleeBug/Debugger.Thread.Registers.cpp index b4d6bb4..e7f4811 100644 --- a/GleeBug/Debugger.Thread.Registers.cpp +++ b/GleeBug/Debugger.Thread.Registers.cpp @@ -106,6 +106,12 @@ namespace GleeBug TrapFlag(this), ResumeFlag(this) { +#ifdef _WIN64 + // Preserve hardware-breakpoint state across control-context updates, + // including transitions between WoW64 compatibility and native mode. + if((ContextFlags & CONTEXT_CONTROL) == CONTEXT_CONTROL) + ContextFlags |= CONTEXT_DEBUG_REGISTERS; +#endif // _WIN64 memset(&mContext, 0, sizeof(CONTEXT)); mContext.ContextFlags = ContextFlags; if(!!GetThreadContext(hThread, &mContext)) @@ -129,4 +135,18 @@ namespace GleeBug { return &mContext; } -}; \ No newline at end of file + + bool Registers::Is32BitMode() const + { +#ifdef _WIN64 + return mContext.SegCs == 0x23; +#else + return true; +#endif // _WIN64 + } + + size_t Registers::PointerSize() const + { + return Is32BitMode() ? sizeof(uint32) : sizeof(ptr); + } +}; diff --git a/GleeBug/Debugger.Thread.Registers.h b/GleeBug/Debugger.Thread.Registers.h index 4fd81c5..78d8d72 100644 --- a/GleeBug/Debugger.Thread.Registers.h +++ b/GleeBug/Debugger.Thread.Registers.h @@ -159,6 +159,16 @@ namespace GleeBug */ PCONTEXT GetContext(); + /** + \brief Returns true when the thread is currently executing 32-bit code. + */ + bool Is32BitMode() const; + + /** + \brief Gets the pointer size for the thread's current execution mode. + */ + size_t PointerSize() const; + private: HANDLE hThread; CONTEXT mContext; diff --git a/GleeBug/Debugger.cpp b/GleeBug/Debugger.cpp index a987302..e6d2d02 100644 --- a/GleeBug/Debugger.cpp +++ b/GleeBug/Debugger.cpp @@ -262,15 +262,12 @@ retry_no_aslr: } #endif - 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) @@ -282,7 +279,7 @@ retry_no_aslr: 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); @@ -299,6 +296,20 @@ retry_no_aslr: 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; @@ -319,7 +330,7 @@ retry_no_aslr: // 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) @@ -386,28 +397,47 @@ retry_no_aslr: 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 + constexpr SIZE_T Peb32ImageBaseOffset = 0x8; + constexpr SIZE_T Peb64ImageBaseOffset = 0x10; + 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) == mDebugModuleImageBase) + isWow64 = true; + imageBaseOffset = Peb32ImageBaseOffset; + imageBaseSize = sizeof(DWORD); + } + else + { + pebAddress = ctx.Rdx; + imageBaseOffset = Peb64ImageBaseOffset; + imageBaseSize = sizeof(DWORD64); + } +#else + pebAddress = ctx.Ebx; + imageBaseOffset = Peb32ImageBaseOffset; + imageBaseSize = sizeof(DWORD); +#endif // _WIN64 + + ULONG_PTR imageBaseValue = 0; + if(ReadProcessMemory(pi.hProcess, (char*)pebAddress + imageBaseOffset, &imageBaseValue, imageBaseSize, nullptr)) + { + if(imageBaseValue == mDebugModuleImageBase) { // Already at the right base success = true; } else { + auto imageBase = PVOID(imageBaseValue); auto status = NtUnmapViewOfSection(pi.hProcess, imageBase); if(status == STATUS_SUCCESS) { @@ -422,22 +452,58 @@ retry_no_aslr: } 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 + Peb64ImageBaseOffset, &imageBase64, sizeof(imageBase64), nullptr); + } + else + { + pebOk = false; + } + } +#else + if(pebOk && isThisProcessWow64()) + { + DWORD64 imageBase64 = imageBaseValue; + pebOk = WriteProcessMemory(pi.hProcess, (char*)pebAddress - 0x1000 + Peb64ImageBaseOffset, &imageBase64, sizeof(imageBase64), nullptr); + } +#endif // _WIN64 auto relocatedOk = RelocateImage(pi.hProcess, imageBase, viewSize); if(pebOk && relocatedOk) { - auto expectedBase = mDebugModuleImageBase == ULONG_PTR(imageBase); - mDebugModuleImageBase = ULONG_PTR(imageBase); - entryPointRegister = mDebugModuleImageBase + debugModuleEntryPoint; - if(SetThreadContext(pi.hThread, &ctx)) + auto expectedBase = mDebugModuleImageBase == imageBaseValue; + mDebugModuleImageBase = imageBaseValue; + auto entryPoint = mDebugModuleImageBase + 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; } } }