mirror of https://github.com/x64dbg/GleeBug
Merge pull request #80 from mrexodia/wow64-support
Add CS-aware WoW64 debugging support
This commit is contained in:
commit
e87f862600
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -262,15 +262,12 @@ retry_no_aslr:
|
|||
}
|
||||
#endif
|
||||
|
||||
static bool ProcessRelocations(char* imageCopy, ULONG_PTR imageSize, ULONG_PTR newImageBase, ULONG_PTR & oldImageBase)
|
||||
template<typename NtHeaders>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue