From 76c1b862503f1ff4e381eddf6f57fd8d8e0a62f0 Mon Sep 17 00:00:00 2001 From: shocoman Date: Fri, 20 Oct 2023 17:40:03 +0700 Subject: [PATCH] Implement memory breakpoints that are not page-aligned --- TitanEngine/Global.Breakpoints.cpp | 108 +++- TitanEngine/Global.Breakpoints.h | 10 + TitanEngine/Global.Debugger.cpp | 1 + TitanEngine/TitanEngine.Breakpoints.cpp | 225 +++++--- .../TitanEngine.Debugger.DebugLoop.cpp | 546 ++++++------------ TitanEngine/TitanEngine.Debugger.Memory.cpp | 2 +- TitanEngine/stdafx.h | 10 +- 7 files changed, 451 insertions(+), 451 deletions(-) diff --git a/TitanEngine/Global.Breakpoints.cpp b/TitanEngine/Global.Breakpoints.cpp index 3f19ed2..2ceb7d0 100644 --- a/TitanEngine/Global.Breakpoints.cpp +++ b/TitanEngine/Global.Breakpoints.cpp @@ -3,6 +3,7 @@ #include "Global.Breakpoints.h" std::vector BreakPointBuffer; +std::unordered_map MemoryBreakpointPages; ULONG_PTR dr7uint(DR7* dr7) { @@ -181,4 +182,109 @@ void BreakPointPostWriteFilter(ULONG_PTR lpBaseAddress, SIZE_T nSize) } } } -} \ No newline at end of file +} + +bool IsDepEnabled(bool* outPermanent) +{ + bool isEnabled = false; + bool isPermanent = false; + +#ifndef _WIN64 + ULONG depFlags = 0; + NTSTATUS status = NtQueryInformationProcess(dbgProcessInformation.hProcess, ProcessExecuteFlags, &depFlags, sizeof(depFlags), nullptr); + if(status == STATUS_SUCCESS) + { + isEnabled = (depFlags & 0x1) != 0; // 0x1 is MEM_EXECUTE_OPTION_DISABLE + isPermanent = (depFlags & 0x8) != 0; // 0x8 is MEM_EXECUTE_OPTION_PERMANENT + } +#else + isEnabled = true; + isPermanent = true; +#endif //_WIN64 + + if(outPermanent != nullptr) + *outPermanent = isPermanent; + + return isEnabled; +} + +DWORD GetPageProtectionForMemoryBreakpoint(const MemoryBreakpointPageDetail & page) +{ + // Memory Protection Constants: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366786(v=vs.85).aspx + + // If DEP is disabled or enabled but not permanent (i.e. may be disabled unpredictably in the future), + // we cannot rely on "PAGE_EXECUTE_*" protection options for BPs on execution + // and should use PAGE_GUARD (or PAGE_NOACCESS) instead, a much slower approach: + bool isDepPermanent = false; + bool isDepPermanentlyEnabled = IsDepEnabled(&isDepPermanent) && isDepPermanent; + + // for ACCESS and READ breakpoints, apply the "lowest" protection: GUARD_PAGE or PAGE_NOACCESS + if(page.accessBps > 0 || page.readBps > 0 || (page.executeBps > 0 && !isDepPermanentlyEnabled)) + { + // GUARD_PAGE is incompatible with PAGE_NOACCESS + if((page.origProtect & 0xFF) == PAGE_NOACCESS || engineMembpAlt) + return (page.origProtect & ~0x7FF) | PAGE_NOACCESS; + else + // erase PAGE_NOCACHE and PAGE_WRITECOMBINE (cannot be used with the PAGE_GUARD) + return (page.origProtect & ~0x700) | PAGE_GUARD; + } + + int newProtect = page.origProtect & ~PAGE_GUARD; // erase guard page, just in case + if(page.executeBps > 0 && isDepPermanentlyEnabled) + { + // Remove execute access e.g. PAGE_EXECUTE_READWRITE => PAGE_READWRITE + DWORD dwBase = newProtect & 0xFF; + DWORD dwHigh = newProtect & 0xFFFFFF00; + switch(dwBase) + { + case PAGE_EXECUTE: + newProtect = dwHigh | PAGE_READONLY; + break; + case PAGE_EXECUTE_READ: + case PAGE_EXECUTE_READWRITE: + case PAGE_EXECUTE_WRITECOPY: + newProtect = dwHigh | (dwBase >> 4); + break; + } + } + + if(page.writeBps > 0) + { + // Remove write access e.g. PAGE_EXECUTE_READWRITE => PAGE_EXECUTE + DWORD dwBase = newProtect & 0xFF; + switch(dwBase) + { + case PAGE_READWRITE: + case PAGE_EXECUTE_READWRITE: + newProtect = (newProtect & 0xFFFFFF00) | (dwBase >> 1); + break; + } + } + + return newProtect; +} + +bool IsMemoryAccessAllowed(DWORD memProtect, ULONG_PTR accessType /*0 (READ), 1 (WRITE), or 8 (EXECUTE)*/) +{ + const bool isRead = accessType == 0; + const bool isWrite = accessType == 1; + const bool isExecute = accessType == 8; + + switch(memProtect & 0xFF) + { + case PAGE_EXECUTE: + case PAGE_EXECUTE_READ: + return isRead || isExecute; + case PAGE_EXECUTE_READWRITE: + case PAGE_EXECUTE_WRITECOPY: + return true; + case PAGE_READONLY: + return isRead || (isExecute && !IsDepEnabled()); + case PAGE_READWRITE: + case PAGE_WRITECOPY: + return isRead || isWrite || (isExecute && !IsDepEnabled()); + default: + case PAGE_NOACCESS: + return false; + } +} diff --git a/TitanEngine/Global.Breakpoints.h b/TitanEngine/Global.Breakpoints.h index ef594ce..b93d562 100644 --- a/TitanEngine/Global.Breakpoints.h +++ b/TitanEngine/Global.Breakpoints.h @@ -2,9 +2,15 @@ #define _GLOBAL_BREAKPOINTS_H #include +#include + #include "Global.Engine.Threading.h" +#include "Global.Engine.h" +#include "Global.Debugger.h" + extern std::vector BreakPointBuffer; +extern std::unordered_map MemoryBreakpointPages; void uintdr7(ULONG_PTR dr7, DR7* ret); ULONG_PTR dr7uint(DR7* dr7); @@ -12,4 +18,8 @@ void BreakPointPostReadFilter(ULONG_PTR lpBaseAddress, unsigned char* lpBuffer, void BreakPointPreWriteFilter(ULONG_PTR lpBaseAddress, SIZE_T nSize); void BreakPointPostWriteFilter(ULONG_PTR lpBaseAddress, SIZE_T nSize); +bool IsDepEnabled(bool* outPermanent = nullptr); +DWORD GetPageProtectionForMemoryBreakpoint(const MemoryBreakpointPageDetail & page); +bool IsMemoryAccessAllowed(DWORD memProtect, ULONG_PTR accessType); + #endif //_GLOBAL_BREAKPOINTS_H diff --git a/TitanEngine/Global.Debugger.cpp b/TitanEngine/Global.Debugger.cpp index b4b3d69..95d09a4 100644 --- a/TitanEngine/Global.Debugger.cpp +++ b/TitanEngine/Global.Debugger.cpp @@ -88,6 +88,7 @@ void DebuggerReset() RtlZeroMemory(&myDBGCustomHandler, sizeof CustomHandler); } std::vector().swap(BreakPointBuffer); + std::unordered_map().swap(MemoryBreakpointPages); } void ClearProcessList() diff --git a/TitanEngine/TitanEngine.Breakpoints.cpp b/TitanEngine/TitanEngine.Breakpoints.cpp index 9d7b2be..be299db 100644 --- a/TitanEngine/TitanEngine.Breakpoints.cpp +++ b/TitanEngine/TitanEngine.Breakpoints.cpp @@ -33,7 +33,8 @@ __declspec(dllexport) bool TITCALL IsBPXEnabled(ULONG_PTR bpxAddress) int bpcount = (int)BreakPointBuffer.size(); for(int i = 0; i < bpcount; i++) { - if(BreakPointBuffer.at(i).BreakPointAddress == bpxAddress) + const bool isSoftwareBpx = BreakPointBuffer.at(i).BreakPointType == UE_SINGLESHOOT || BreakPointBuffer.at(i).BreakPointType == UE_BREAKPOINT; + if(isSoftwareBpx && BreakPointBuffer.at(i).BreakPointAddress == bpxAddress) { if(BreakPointBuffer.at(i).BreakPointActive != UE_BPXINACTIVE) { @@ -440,64 +441,115 @@ __declspec(dllexport) bool TITCALL SetMemoryBPX(ULONG_PTR MemoryStart, SIZE_T Si __declspec(dllexport) bool TITCALL SetMemoryBPXEx(ULONG_PTR MemoryStart, SIZE_T SizeOfMemory, DWORD BreakPointType, bool RestoreOnHit, LPVOID bpxCallBack) { - if(MemoryStart % TITANENGINE_PAGESIZE || !SizeOfMemory || SizeOfMemory % TITANENGINE_PAGESIZE) //ensure the data is aligned with the page size - return false; + struct TempMemoryBreakpointDetails + { + ULONG_PTR addr; + DWORD currentPageProtect; + MemoryBreakpointPageDetail data; + }; + CriticalSectionLocker lock(LockBreakPointBuffer); - MEMORY_BASIC_INFORMATION MemInfo; - ULONG_PTR NumberOfBytesReadWritten = 0; + bool isSuccess = true; + DWORD oldProtect; + + // Note: memory breakpoints cannot intersect. + // Check that there are no other MemBPs in the address range [MemoryStart, MemoryStart+SizeOfMemory) int bpcount = (int)BreakPointBuffer.size(); - DWORD OldProtect = 0; - //search for breakpoint for(int i = 0; i < bpcount; i++) { - if(BreakPointBuffer.at(i).BreakPointAddress == MemoryStart && - (BreakPointBuffer.at(i).BreakPointType == UE_MEMORY || - BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_READ || - BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_WRITE || - BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_EXECUTE) - ) + auto bpAddr = BreakPointBuffer.at(i).BreakPointAddress; + auto bpSize = BreakPointBuffer.at(i).BreakPointSize; + auto bpType = BreakPointBuffer.at(i).BreakPointType; + bool isMem = bpType == UE_MEMORY || bpType == UE_MEMORY_READ || bpType == UE_MEMORY_WRITE || bpType == UE_MEMORY_EXECUTE; + + if (isMem && bpAddr < (MemoryStart + SizeOfMemory) && bpAddr + bpSize > MemoryStart) { - return false; + return false; // the place is taken } } - //set PAGE_GUARD on all the pages separately - size_t pages = SizeOfMemory / TITANENGINE_PAGESIZE; - - for(size_t i = 0; i < pages; i++) - { - const LPVOID curPage = (LPVOID)(MemoryStart + i * TITANENGINE_PAGESIZE); - VirtualQueryEx(dbgProcessInformation.hProcess, curPage, &MemInfo, sizeof(MEMORY_BASIC_INFORMATION)); + // Set a proper protection (e.g. PAGE_GUARD) for all pages in the range + std::vector breakpointInfos; + MemoryBreakpointPageDetail pageData; - if(OldProtect == 0) - OldProtect = MemInfo.Protect; + auto pageStart = ALIGN_DOWN_BY(MemoryStart, TITANENGINE_PAGESIZE); + auto pageEnd = ALIGN_UP_BY(MemoryStart + SizeOfMemory, TITANENGINE_PAGESIZE); + for(ULONG_PTR page = pageStart; page < pageEnd; page += TITANENGINE_PAGESIZE) + { + // Save the current page protection in case of a failure + MEMORY_BASIC_INFORMATION memInfo; + if(!VirtualQueryEx(dbgProcessInformation.hProcess, (LPCVOID)page, &memInfo, sizeof(memInfo))) + { + isSuccess = false; + break; + } - // Check if the alternative memory breakpoint method should be used - if(engineMembpAlt) + // Update page data and increment a BP counter + auto found = MemoryBreakpointPages.find(page); + if(found == MemoryBreakpointPages.end()) { - if(!(MemInfo.Protect & PAGE_NOACCESS)) - { - VirtualProtectEx(dbgProcessInformation.hProcess, curPage, TITANENGINE_PAGESIZE, PAGE_NOACCESS, &MemInfo.Protect); - } + // It's the first memory BP on this page + pageData.origProtect = memInfo.Protect; + pageData.accessBps = pageData.readBps = pageData.writeBps = pageData.executeBps = 0; } else { - // Default to using PAGE_GUARD memory breakpoint - if(!(MemInfo.Protect & PAGE_GUARD)) - { - DWORD NewProtect = MemInfo.Protect ^ PAGE_GUARD; - VirtualProtectEx(dbgProcessInformation.hProcess, curPage, TITANENGINE_PAGESIZE, NewProtect, &MemInfo.Protect); - } + // There are other memory BPs on this page + pageData = found->second; // original protection stays the same } + + switch(BreakPointType) + { + case UE_MEMORY: // READ + WRITE + EXECUTE + pageData.accessBps += 1; + break; + case UE_MEMORY_READ: + pageData.readBps += 1; + break; + case UE_MEMORY_WRITE: + pageData.writeBps += 1; + break; + case UE_MEMORY_EXECUTE: + pageData.executeBps += 1; + break; + default: // unreachable + break; + } + + // Get a proper MemBp page protection option and apply it + pageData.newProtect = GetPageProtectionForMemoryBreakpoint(pageData); + if(!VirtualProtectEx(dbgProcessInformation.hProcess, (LPVOID)page, TITANENGINE_PAGESIZE, pageData.newProtect, &oldProtect)) + { + isSuccess = false; + break; + } + + TempMemoryBreakpointDetails tempInfo; + tempInfo.addr = page; + tempInfo.currentPageProtect = memInfo.Protect; + tempInfo.data = pageData; + breakpointInfos.push_back(tempInfo); + } + + // If changing the page protections failed, attempt to revert the applied protections back + if(!isSuccess) + { + for(const auto & page : breakpointInfos) + VirtualProtectEx(dbgProcessInformation.hProcess, (LPVOID)page.addr, TITANENGINE_PAGESIZE, page.currentPageProtect, &oldProtect); + return false; } - //add new breakpoint + + // Save the page data + for(const auto & page : breakpointInfos) + MemoryBreakpointPages[page.addr] = page.data; + + // Add a new breakpoint BreakPointDetail NewBreakPoint; memset(&NewBreakPoint, 0, sizeof(BreakPointDetail)); NewBreakPoint.BreakPointActive = UE_BPXACTIVE; NewBreakPoint.BreakPointAddress = MemoryStart; - NewBreakPoint.BreakPointType = BreakPointType; NewBreakPoint.BreakPointSize = SizeOfMemory; - NewBreakPoint.OldProtect = OldProtect; + NewBreakPoint.BreakPointType = BreakPointType; NewBreakPoint.MemoryBpxRestoreOnHit = (BYTE)RestoreOnHit; NewBreakPoint.ExecuteCallBack = (ULONG_PTR)bpxCallBack; BreakPointBuffer.push_back(NewBreakPoint); @@ -506,67 +558,84 @@ __declspec(dllexport) bool TITCALL SetMemoryBPXEx(ULONG_PTR MemoryStart, SIZE_T __declspec(dllexport) bool TITCALL RemoveMemoryBPX(ULONG_PTR MemoryStart, SIZE_T SizeOfMemory) { - if(MemoryStart % TITANENGINE_PAGESIZE || SizeOfMemory % TITANENGINE_PAGESIZE) //ensure the data is aligned with the page size - return false; CriticalSectionLocker lock(LockBreakPointBuffer); - MEMORY_BASIC_INFORMATION MemInfo; - ULONG_PTR NumberOfBytesReadWritten = 0; - int bpcount = (int)BreakPointBuffer.size(); - int found = -1; - //search for breakpoint - for(int i = 0; i < bpcount; i++) + bool isSuccess = true; + + // find the breakpoint + int nFoundBp = -1; + size_t bpcount = BreakPointBuffer.size(); + for(size_t i = 0; i < bpcount; i++) { - if(BreakPointBuffer.at(i).BreakPointAddress == MemoryStart && - (BreakPointBuffer.at(i).BreakPointType == UE_MEMORY || - BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_READ || - BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_WRITE || - BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_EXECUTE) - ) + auto bpAddr = BreakPointBuffer.at(i).BreakPointAddress; + auto bpType = BreakPointBuffer.at(i).BreakPointType; + bool isMem = bpType == UE_MEMORY || bpType == UE_MEMORY_READ || bpType == UE_MEMORY_WRITE || bpType == UE_MEMORY_EXECUTE; + + if(isMem && bpAddr == MemoryStart) { - found = i; + nFoundBp = (int)i; break; } } - if(found == -1) //not found - return false; - - if(!SizeOfMemory) - SizeOfMemory = BreakPointBuffer.at(found).BreakPointSize; + if(nFoundBp == -1) + return false; // not found - // Revert to the original permission on all the pages in the range - size_t pages = SizeOfMemory / TITANENGINE_PAGESIZE; + int memBpType = BreakPointBuffer.at(nFoundBp).BreakPointType; + SizeOfMemory = BreakPointBuffer.at(nFoundBp).BreakPointSize; // ignore the given size, x64dbg may be lying - for(size_t i = 0; i < pages; i++) + //delete the memory breakpoint from the pages + auto pageStart = ALIGN_DOWN_BY(MemoryStart, TITANENGINE_PAGESIZE); + auto pageEnd = ALIGN_UP_BY(MemoryStart + SizeOfMemory, TITANENGINE_PAGESIZE); + for(ULONG_PTR pageAddr = pageStart; pageAddr < pageEnd; pageAddr += TITANENGINE_PAGESIZE) { - const LPVOID curPage = (LPVOID)(MemoryStart + i * TITANENGINE_PAGESIZE); + auto foundPageData = MemoryBreakpointPages.find(pageAddr); + if(foundPageData == MemoryBreakpointPages.end()) + continue; // should not happen - VirtualQueryEx(dbgProcessInformation.hProcess, curPage, &MemInfo, sizeof(MEMORY_BASIC_INFORMATION)); + // Decrement a BP counter + auto & pageData = foundPageData->second; + switch(memBpType) + { + case UE_MEMORY: // READ + WRITE + EXECUTE + pageData.accessBps -= 1; + break; + case UE_MEMORY_READ: + pageData.readBps -= 1; + break; + case UE_MEMORY_WRITE: + pageData.writeBps -= 1; + break; + case UE_MEMORY_EXECUTE: + pageData.executeBps -= 1; + break; + default: // unreachable + break; + } - // Check if the alternative memory breakpoint method is being used - if(engineMembpAlt) + DWORD newProtect; + const bool noMoreBps = 0 == (pageData.accessBps + pageData.readBps + pageData.writeBps + pageData.executeBps); + if(noMoreBps) { - if(MemInfo.Protect & PAGE_NOACCESS) - { - VirtualProtectEx(dbgProcessInformation.hProcess, curPage, TITANENGINE_PAGESIZE, - BreakPointBuffer.at(found).OldProtect, &MemInfo.Protect); - } + // There are no more BPs on this page. Remove the page data. + newProtect = pageData.origProtect; + MemoryBreakpointPages.erase(foundPageData); } else { - if(MemInfo.Protect & PAGE_GUARD) - { - DWORD NewProtect = MemInfo.Protect ^ PAGE_GUARD; - - VirtualProtectEx(dbgProcessInformation.hProcess, curPage, TITANENGINE_PAGESIZE, NewProtect, &MemInfo.Protect); - } + // Some BPs are still here. According to their types, reapply page protection. + pageData.newProtect = GetPageProtectionForMemoryBreakpoint(pageData); + newProtect = pageData.newProtect; } + + DWORD oldProtect; + if(!VirtualProtectEx(dbgProcessInformation.hProcess, (LPVOID)pageAddr, TITANENGINE_PAGESIZE, newProtect, &oldProtect)) + isSuccess = false; } //remove breakpoint from list - BreakPointBuffer.erase(BreakPointBuffer.begin() + found); + BreakPointBuffer.erase(BreakPointBuffer.begin() + nFoundBp); - return true; + return isSuccess; } __declspec(dllexport) bool TITCALL GetUnusedHardwareBreakPointRegister(LPDWORD RegisterIndex) diff --git a/TitanEngine/TitanEngine.Debugger.DebugLoop.cpp b/TitanEngine/TitanEngine.Debugger.DebugLoop.cpp index b36e7d2..024fdc5 100644 --- a/TitanEngine/TitanEngine.Debugger.DebugLoop.cpp +++ b/TitanEngine/TitanEngine.Debugger.DebugLoop.cpp @@ -8,6 +8,7 @@ #include "Global.Librarian.h" #include "Global.TLS.h" #include +#include #define UE_MODULEx86 0x2000; #define UE_MODULEx64 0x2000; @@ -51,7 +52,6 @@ __declspec(dllexport) void TITCALL DebugLoop() bool hListProcessFirst = true; bool hListThreadFirst = true; bool hListLibraryFirst = true; - bool MemoryBpxFound = false; PLIBRARY_ITEM_DATAW hLoadedLibData = NULL; PLIBRARY_BREAK_DATA ptrLibrarianData = NULL; typedef void(TITCALL * fCustomBreakPoint)(void); @@ -59,16 +59,12 @@ __declspec(dllexport) void TITCALL DebugLoop() typedef void(TITCALL * fFindOEPHandler)(LPPROCESS_INFORMATION fProcessInfo, LPVOID fCallBack); fCustomHandler myCustomHandler; fCustomBreakPoint myCustomBreakPoint; - ULONG_PTR MemoryBpxCallBack = 0; SIZE_T ResetBPXSize = 0; ULONG_PTR ResetBPXAddressTo = 0; - ULONG_PTR ResetMemBPXAddress = 0; - SIZE_T ResetMemBPXSize = 0; + std::function ResetMemBpxCallback; ULONG_PTR NumberOfBytesReadWritten = 0; - MEMORY_BASIC_INFORMATION MemInfo; HANDLE hActiveThread; DWORD OldProtect; - DWORD NewProtect; DWORD DebugRegisterXId = NULL; HARDWARE_DATA DebugRegisterX; wchar_t DLLDebugFileName[512]; @@ -684,36 +680,7 @@ __declspec(dllexport) void TITCALL DebugLoop() if(ResetMemBPX) //restore memory breakpoint { ResetMemBPX = false; - - // Check if the alternative memory breakpoint method should be used - if(engineMembpAlt) - { - // Check if the breakpoint is still enabled/present and has not been removed - for(size_t i = 0; i < BreakPointBuffer.size(); i++) - { - if(BreakPointBuffer.at(i).BreakPointAddress == ResetMemBPXAddress && - (BreakPointBuffer.at(i).BreakPointType == UE_MEMORY || - BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_READ || - BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_WRITE || - BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_EXECUTE) && - BreakPointBuffer.at(i).BreakPointActive == UE_BPXACTIVE) - { - // Restore the breakpoint - VirtualProtectEx(dbgProcessInformation.hProcess, (LPVOID)ResetMemBPXAddress, - ResetMemBPXSize, PAGE_NOACCESS, &OldProtect); - - break; - } - } - } - else - { - VirtualQueryEx(dbgProcessInformation.hProcess, (LPCVOID)ResetMemBPXAddress, &MemInfo, sizeof(MEMORY_BASIC_INFORMATION)); - OldProtect = MemInfo.Protect; - NewProtect = OldProtect | PAGE_GUARD; //guard page protection - VirtualProtectEx(dbgProcessInformation.hProcess, (LPVOID)ResetMemBPXAddress, ResetMemBPXSize, NewProtect, &OldProtect); - } - + ResetMemBpxCallback(); engineStep(); } } @@ -876,390 +843,229 @@ __declspec(dllexport) void TITCALL DebugLoop() break; case STATUS_GUARD_PAGE_VIOLATION: + case STATUS_ACCESS_VIOLATION: { - ULONG_PTR bpaddr; + // Plan (making sure the breakpoint is valid): + // 1) Check if one of our BPs falls into the access address + // 2) Check if this breakpoint is of the right type (READ, WRITE, etc) + // 3) Somehow check if the exception wasn't maliciosly caused by the debugged program + // 4) If all are true (i.e. the BP is ours): + // call the user callback, restore the original protection, single-step, put our protection back + // if not: + // - don't call the user callback + // - restore the protection if there are still our BPs on this page OR pass the exception to the debuggee + + DBGCode = DBG_EXCEPTION_NOT_HANDLED; + ResetMemBPX = false; + bool bCallUserCallback = false; // when we hit a correct BP + + // Access Types: 0 - read, 1 - write, 8 - execute (dep violation) + ULONG_PTR accessType = DBGEvent.u.Exception.ExceptionRecord.ExceptionInformation[0]; + ULONG_PTR accessAddr = DBGEvent.u.Exception.ExceptionRecord.ExceptionInformation[1]; + ULONG_PTR currentPageAddr = ALIGN_DOWN_BY(accessAddr, TITANENGINE_PAGESIZE); + bool isAccessViolation = DBGEvent.u.Exception.ExceptionRecord.ExceptionCode == STATUS_ACCESS_VIOLATION; + + + // Part 1. + // Find the breakpoint which was hit (if any) bool bFoundBreakPoint = false; - BreakPointDetail FoundBreakPoint; - int bpcount = (int)BreakPointBuffer.size(); - for(int i = 0; i < bpcount; i++) + BreakPointDetail foundBreakPoint; + size_t bpcount = BreakPointBuffer.size(); + for(size_t i = 0; i < bpcount; i++) { - ULONG_PTR addr = BreakPointBuffer.at(i).BreakPointAddress; - bpaddr = (ULONG_PTR)DBGEvent.u.Exception.ExceptionRecord.ExceptionInformation[1]; //page accessed - if(bpaddr >= addr && bpaddr < (addr + BreakPointBuffer.at(i).BreakPointSize) && - (BreakPointBuffer.at(i).BreakPointType == UE_MEMORY || - BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_READ || - BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_WRITE || - BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_EXECUTE) && - BreakPointBuffer.at(i).BreakPointActive == UE_BPXACTIVE) + ULONG_PTR bpAddr = BreakPointBuffer.at(i).BreakPointAddress; + auto bpType = BreakPointBuffer.at(i).BreakPointType; + bool isMemBp = bpType == UE_MEMORY || bpType == UE_MEMORY_READ || bpType == UE_MEMORY_WRITE || bpType == UE_MEMORY_EXECUTE; + bool isActive = BreakPointBuffer.at(i).BreakPointActive == UE_BPXACTIVE; + + if(isActive && isMemBp && accessAddr >= bpAddr && accessAddr < (bpAddr + BreakPointBuffer.at(i).BreakPointSize)) { - FoundBreakPoint = BreakPointBuffer.at(i); + foundBreakPoint = BreakPointBuffer.at(i); bFoundBreakPoint = true; break; } } - if(bFoundBreakPoint) //found memory breakpoint + + auto hitPage = MemoryBreakpointPages.find(currentPageAddr); + if(!bFoundBreakPoint) { - hActiveThread = EngineOpenThread(THREAD_GETSETSUSPEND, false, DBGEvent.dwThreadId); - CONTEXT myDBGContext; - myDBGContext.ContextFlags = ContextControlFlags; - GetThreadContext(hActiveThread, &myDBGContext); - DBGCode = DBG_CONTINUE; //debugger handled the exception - MemoryBpxCallBack = FoundBreakPoint.ExecuteCallBack; - if(FoundBreakPoint.BreakPointType == UE_MEMORY) //READ|WRITE|EXECUTE - { - if(FoundBreakPoint.MemoryBpxRestoreOnHit != 1) - { - RemoveMemoryBPX(FoundBreakPoint.BreakPointAddress, FoundBreakPoint.BreakPointSize); - } - else - { - { - myDBGContext.EFlags |= UE_TRAP_FLAG; - synchronizedStep = true; - SetThreadContext(hActiveThread, &myDBGContext); - } - ResetMemBPXAddress = FoundBreakPoint.BreakPointAddress; - ResetMemBPXSize = FoundBreakPoint.BreakPointSize; - ResetMemBPX = true; - } - myCustomHandler = (fCustomHandler)(MemoryBpxCallBack); - myCustomHandler((void*)bpaddr); - } - else if(FoundBreakPoint.BreakPointType == UE_MEMORY_READ) //READ + // There were no BPs at the accessed address. + // But this page may still contain our BPs somewhere else + if(hitPage != MemoryBreakpointPages.end()) { - if(FoundBreakPoint.MemoryBpxRestoreOnHit != 1) //do not restore the memory breakpoint - { - if(DBGEvent.u.Exception.ExceptionRecord.ExceptionInformation[0] == 0) //read operation - RemoveMemoryBPX(FoundBreakPoint.BreakPointAddress, FoundBreakPoint.BreakPointSize); - } - else //restore the memory breakpoint - { - { - myDBGContext.EFlags |= UE_TRAP_FLAG; - synchronizedStep = true; - SetThreadContext(hActiveThread, &myDBGContext); - } - ResetMemBPXAddress = FoundBreakPoint.BreakPointAddress; - ResetMemBPXSize = FoundBreakPoint.BreakPointSize; - ResetMemBPX = true; - } - if(DBGEvent.u.Exception.ExceptionRecord.ExceptionInformation[0] == 0) //read operation - { - myCustomHandler = (fCustomHandler)(MemoryBpxCallBack); - myCustomHandler((void*)bpaddr); - } - else //no read operation, restore breakpoint - { - { - myDBGContext.EFlags |= UE_TRAP_FLAG; - synchronizedStep = true; - SetThreadContext(hActiveThread, &myDBGContext); - } - ResetMemBPXAddress = FoundBreakPoint.BreakPointAddress; - ResetMemBPXSize = FoundBreakPoint.BreakPointSize; - ResetMemBPX = true; - } + // There is a breakpoint! Maybe it caused this exception? + // We should restore the page protection and continue execution. + ResetMemBPX = true; } - else if(FoundBreakPoint.BreakPointType == UE_MEMORY_WRITE) //WRITE - { - if(FoundBreakPoint.MemoryBpxRestoreOnHit != 1) //remove breakpoint - { - if(DBGEvent.u.Exception.ExceptionRecord.ExceptionInformation[0] == 1) //write operation - RemoveMemoryBPX(FoundBreakPoint.BreakPointAddress, FoundBreakPoint.BreakPointSize); - } - else //restore breakpoint after trap flag - { - { - myDBGContext.EFlags |= UE_TRAP_FLAG; - synchronizedStep = true; - SetThreadContext(hActiveThread, &myDBGContext); - } - ResetMemBPXAddress = FoundBreakPoint.BreakPointAddress; - ResetMemBPXSize = FoundBreakPoint.BreakPointSize; - ResetMemBPX = true; - } - if(DBGEvent.u.Exception.ExceptionRecord.ExceptionInformation[0] == 1) //write operation - { - myCustomHandler = (fCustomHandler)(MemoryBpxCallBack); - myCustomHandler((void*)bpaddr); - } - else //no write operation, restore breakpoint - { - { - myDBGContext.EFlags |= UE_TRAP_FLAG; - synchronizedStep = true; - SetThreadContext(hActiveThread, &myDBGContext); - } - ResetMemBPXAddress = FoundBreakPoint.BreakPointAddress; - ResetMemBPXSize = FoundBreakPoint.BreakPointSize; - ResetMemBPX = true; - } - } - else if(FoundBreakPoint.BreakPointType == UE_MEMORY_EXECUTE) //EXECUTE + else { - if(FoundBreakPoint.MemoryBpxRestoreOnHit != 1) - { - if((DBGEvent.u.Exception.ExceptionRecord.ExceptionInformation[0] == 8 || DBGEvent.u.Exception.ExceptionRecord.ExceptionInformation[0] == 0) && //data execution prevention (DEP) violation - (ULONG_PTR)DBGEvent.u.Exception.ExceptionRecord.ExceptionAddress == DBGEvent.u.Exception.ExceptionRecord.ExceptionInformation[1]) //exception address == read address - RemoveMemoryBPX(FoundBreakPoint.BreakPointAddress, FoundBreakPoint.BreakPointSize); - } - else - { - { - myDBGContext.EFlags |= UE_TRAP_FLAG; - synchronizedStep = true; - SetThreadContext(hActiveThread, &myDBGContext); - } - ResetMemBPXAddress = FoundBreakPoint.BreakPointAddress; - ResetMemBPXSize = FoundBreakPoint.BreakPointSize; - ResetMemBPX = true; - } - if((DBGEvent.u.Exception.ExceptionRecord.ExceptionInformation[0] == 8 || DBGEvent.u.Exception.ExceptionRecord.ExceptionInformation[0] == 0) && //data execution prevention (DEP) violation - (ULONG_PTR)DBGEvent.u.Exception.ExceptionRecord.ExceptionAddress == DBGEvent.u.Exception.ExceptionRecord.ExceptionInformation[1]) //exception address == read address - { - myCustomHandler = (fCustomHandler)(MemoryBpxCallBack); - myCustomHandler((void*)bpaddr); - } - else //no execute operation, restore breakpoint - { - { - myDBGContext.EFlags |= UE_TRAP_FLAG; - synchronizedStep = true; - SetThreadContext(hActiveThread, &myDBGContext); - } - ResetMemBPXAddress = FoundBreakPoint.BreakPointAddress; - ResetMemBPXSize = FoundBreakPoint.BreakPointSize; - ResetMemBPX = true; - } + // There are no breakpoints (our BP could not cause this exception). + // So don't do anything at all and pass the exception to the debuggee. } - EngineCloseHandle(hActiveThread); } - else //no memory breakpoint found + else if(hitPage == MemoryBreakpointPages.end()) { - DBGCode = DBG_EXCEPTION_NOT_HANDLED; + // Inconsistent page data; should never happen } - if(ResetMemBPX) //memory breakpoint hit + else { - 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); - if(strstr(DisassembledString, "PUSHF")) - PushfBPX = true; - } + // The debuggee actually hit one of our breakpoints + MemoryBreakpointPageDetail pageData = hitPage->second; - //debuggee generated GUARD_PAGE exception - if(DBGCode == DBG_EXCEPTION_NOT_HANDLED) - { - //TODO: restore memory breakpoint? - if(DBGCustomHandler->chPageGuard != NULL) + // Part 2. + // Ensure that the access type was correct. + bool isCorrectAccessType = false; + switch(foundBreakPoint.BreakPointType) { - myCustomHandler = (fCustomHandler)((LPVOID)DBGCustomHandler->chPageGuard); - myCustomHandler(&DBGEvent.u.Exception.ExceptionRecord); + case UE_MEMORY: // READ | WRITE | EXECUTE + isCorrectAccessType = true; // all access types are fine + break; + case UE_MEMORY_READ: + isCorrectAccessType = accessType == 0; // READ + break; + case UE_MEMORY_WRITE: + isCorrectAccessType = accessType == 1; // WRITE + break; + case UE_MEMORY_EXECUTE: + isCorrectAccessType = (accessType == 8 || accessType == 0) // EXECUTE or READ (when DEP is disabled/unsupported?) + && accessAddr == (ULONG_PTR)DBGEvent.u.Exception.ExceptionRecord.ExceptionAddress; + break; + default: + isCorrectAccessType = false; // unreachable + break; } - } - } - break; - - case STATUS_ACCESS_VIOLATION: - { - ULONG_PTR bpaddr; - bool bFoundBreakPoint = false; - bool bCallCustomHandler = false; - BreakPointDetail FoundBreakPoint; - int bpcount = (int)BreakPointBuffer.size(); - for(int i = 0; i < bpcount; i++) - { - ULONG_PTR addr = BreakPointBuffer.at(i).BreakPointAddress; - bpaddr = (ULONG_PTR)DBGEvent.u.Exception.ExceptionRecord.ExceptionInformation[1]; //page accessed - if(bpaddr >= addr && bpaddr < (addr + BreakPointBuffer.at(i).BreakPointSize) && - (BreakPointBuffer.at(i).BreakPointType == UE_MEMORY || - BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_READ || - BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_WRITE || - BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_EXECUTE) && - BreakPointBuffer.at(i).BreakPointActive == UE_BPXACTIVE) + // Part 2.5. + // Maybe the debuggee intentially generated this exception OR changed the page protection? + // In that case we shouldn't handle the exception. + // + // Sanity checks: the type of the exception loosely corresponds to the page protection we originally set. + bool bpTypeIsGuardPage = (pageData.newProtect & PAGE_GUARD) != 0; + if(bpTypeIsGuardPage && isAccessViolation || !bpTypeIsGuardPage && !isAccessViolation) { - FoundBreakPoint = BreakPointBuffer.at(i); - bFoundBreakPoint = true; - break; + // We wouldn't make a BP with this kind of protection. Pass the exception to the debuggee. } - } - - // Most of the logic has been copied from the STATUS_GUARD_PAGE_VIOLATION handler - - if(bFoundBreakPoint && engineMembpAlt) //found memory breakpoint - { - hActiveThread = EngineOpenThread(THREAD_GETSETSUSPEND, false, DBGEvent.dwThreadId); - CONTEXT myDBGContext; - myDBGContext.ContextFlags = ContextControlFlags; - GetThreadContext(hActiveThread, &myDBGContext); - DBGCode = DBG_CONTINUE; //debugger handled the exception - MemoryBpxCallBack = FoundBreakPoint.ExecuteCallBack; - - if(FoundBreakPoint.BreakPointType == UE_MEMORY) //READ|WRITE|EXECUTE + else if(isAccessViolation // STATUS_ACCESS_VIOLATION + && (accessType == 1 /*WRITE*/ && pageData.writeBps == 0 || accessType == 8 /*EXECUTE*/ && pageData.executeBps == 0) + && (pageData.newProtect & 0xFF) != PAGE_NOACCESS) { - if(FoundBreakPoint.MemoryBpxRestoreOnHit != 1) - { - RemoveMemoryBPX(FoundBreakPoint.BreakPointAddress, FoundBreakPoint.BreakPointSize); - } - else - { - { - myDBGContext.EFlags |= UE_TRAP_FLAG; - synchronizedStep = true; - SetThreadContext(hActiveThread, &myDBGContext); - } - ResetMemBPXAddress = FoundBreakPoint.BreakPointAddress; - ResetMemBPXSize = FoundBreakPoint.BreakPointSize; - ResetMemBPX = true; - } - - bCallCustomHandler = true; + // The STATUS_ACCESS_VIOLATION exception was on Write (or Execute), but there is no BP on Write (or Execute). + // Probably the debuggee directly caused the exception. Don't handle it. } - else if(FoundBreakPoint.BreakPointType == UE_MEMORY_READ) //READ + else if(!isAccessViolation // STATUS_GUARD_PAGE_VIOLATION + && pageData.accessBps == 0 && pageData.readBps == 0 // no ACCESS and READ bps + && (pageData.executeBps == 0 || !bpTypeIsGuardPage)) // no EXECUTE bps (when implemented via guard pages) { - if(FoundBreakPoint.MemoryBpxRestoreOnHit != 1) //do not restore the memory breakpoint - { - if(DBGEvent.u.Exception.ExceptionRecord.ExceptionInformation[0] == 0) //read operation - RemoveMemoryBPX(FoundBreakPoint.BreakPointAddress, FoundBreakPoint.BreakPointSize); - } - else //restore the memory breakpoint - { - { - myDBGContext.EFlags |= UE_TRAP_FLAG; - synchronizedStep = true; - SetThreadContext(hActiveThread, &myDBGContext); - } - ResetMemBPXAddress = FoundBreakPoint.BreakPointAddress; - ResetMemBPXSize = FoundBreakPoint.BreakPointSize; - ResetMemBPX = true; - } - if(DBGEvent.u.Exception.ExceptionRecord.ExceptionInformation[0] == 0) //read operation - { - bCallCustomHandler = true; - } - else //no read operation, restore breakpoint - { - { - myDBGContext.EFlags |= UE_TRAP_FLAG; - synchronizedStep = true; - SetThreadContext(hActiveThread, &myDBGContext); - } - ResetMemBPXAddress = FoundBreakPoint.BreakPointAddress; - ResetMemBPXSize = FoundBreakPoint.BreakPointSize; - ResetMemBPX = true; - } + // The STATUS_GUARD_PAGE_VIOLATION exception was within a page that had no BPs on READ, ACCESS, + // and EXECUTE (and DEP is disabled, otherwise we wouldn't use the guard pages). Pass it on. } - else if(FoundBreakPoint.BreakPointType == UE_MEMORY_WRITE) //WRITE + else if(!isCorrectAccessType) { - if(FoundBreakPoint.MemoryBpxRestoreOnHit != 1) //remove breakpoint - { - if(DBGEvent.u.Exception.ExceptionRecord.ExceptionInformation[0] == 1) //write operation - RemoveMemoryBPX(FoundBreakPoint.BreakPointAddress, FoundBreakPoint.BreakPointSize); - } - else //restore breakpoint after trap flag - { - { - myDBGContext.EFlags |= UE_TRAP_FLAG; - synchronizedStep = true; - SetThreadContext(hActiveThread, &myDBGContext); - } - ResetMemBPXAddress = FoundBreakPoint.BreakPointAddress; - ResetMemBPXSize = FoundBreakPoint.BreakPointSize; - ResetMemBPX = true; - } - if(DBGEvent.u.Exception.ExceptionRecord.ExceptionInformation[0] == 1) //write operation - { - bCallCustomHandler = true; - } - else //no write operation, restore breakpoint - { - { - myDBGContext.EFlags |= UE_TRAP_FLAG; - synchronizedStep = true; - SetThreadContext(hActiveThread, &myDBGContext); - } - ResetMemBPXAddress = FoundBreakPoint.BreakPointAddress; - ResetMemBPXSize = FoundBreakPoint.BreakPointSize; - ResetMemBPX = true; - } + // The access type was wrong, i.e. this is not "exactly" our breakpoint. + // Potentially, we could get here from our BP (e.g. by writing into a page with only a READ bp) + // Restore the protection and move on. + ResetMemBPX = true; } - else if(FoundBreakPoint.BreakPointType == UE_MEMORY_EXECUTE) //EXECUTE + else { - if(FoundBreakPoint.MemoryBpxRestoreOnHit != 1) - { - if((DBGEvent.u.Exception.ExceptionRecord.ExceptionInformation[0] == 8 || DBGEvent.u.Exception.ExceptionRecord.ExceptionInformation[0] == 0) && //data execution prevention (DEP) violation - (ULONG_PTR)DBGEvent.u.Exception.ExceptionRecord.ExceptionAddress == DBGEvent.u.Exception.ExceptionRecord.ExceptionInformation[1]) //exception address == read address - RemoveMemoryBPX(FoundBreakPoint.BreakPointAddress, FoundBreakPoint.BreakPointSize); - } - else - { - { - myDBGContext.EFlags |= UE_TRAP_FLAG; - synchronizedStep = true; - SetThreadContext(hActiveThread, &myDBGContext); - } - ResetMemBPXAddress = FoundBreakPoint.BreakPointAddress; - ResetMemBPXSize = FoundBreakPoint.BreakPointSize; - ResetMemBPX = true; - } - if((DBGEvent.u.Exception.ExceptionRecord.ExceptionInformation[0] == 8 || DBGEvent.u.Exception.ExceptionRecord.ExceptionInformation[0] == 0) && //data execution prevention (DEP) violation - (ULONG_PTR)DBGEvent.u.Exception.ExceptionRecord.ExceptionAddress == DBGEvent.u.Exception.ExceptionRecord.ExceptionInformation[1]) //exception address == read address - { - bCallCustomHandler = true; - } - else //no execute operation, restore breakpoint + // Part 3. + // This was indeed our breakpoint, and of the right type, too. We can call the user callback now. + bCallUserCallback = true; + + if(!foundBreakPoint.MemoryBpxRestoreOnHit) { - { - myDBGContext.EFlags |= UE_TRAP_FLAG; - synchronizedStep = true; - SetThreadContext(hActiveThread, &myDBGContext); - } - ResetMemBPXAddress = FoundBreakPoint.BreakPointAddress; - ResetMemBPXSize = FoundBreakPoint.BreakPointSize; - ResetMemBPX = true; + // BP was singleshot and should be removed + RemoveMemoryBPX(foundBreakPoint.BreakPointAddress, foundBreakPoint.BreakPointSize); } + + // Even though this breakpoint might be singleshot, we still temporarily remove the protection + // because there can be other breakpoints on this page that won't let us execute the current instruction normally + ResetMemBPX = true; } + } + + // Part 4 + // + // At this point, if we want to restore the breakpoint, we should temporarily put the original + // protection back. The problem is that the original protection might not allow us to continue execution + // (e.g. when we put a WRITE bp on a page originally marked READONLY). In some cases, it may lead to + // an infinite loop (single-stepping might fail and call this handler, which will try to automatically + // single-step again and end up at this exact place, and so on). So if we are sure that resetting the BP is not a good idea, + // we just pass the exception on. Or maybe it's better to set PAGE_EXECUTE_READWRITE and simply continue? + DWORD originalProtect = hitPage->second.origProtect; + if(ResetMemBPX && (bCallUserCallback || IsMemoryAccessAllowed(originalProtect, accessType))) + { + // Mini Plan: + // 1) Set a protection option that would allow us to normally execute the instruction that caused this exception + // 2) Single-step (execute the instruction) + // 3) Restore the previous protection (i.e. our memory breakpoint) - // If the breakpoint has to be restored... - if(ResetMemBPX) + VirtualProtectEx(dbgProcessInformation.hProcess, (LPVOID)currentPageAddr, TITANENGINE_PAGESIZE, originalProtect, &OldProtect); + + if(bCallUserCallback) { - // ...temporarily revert the PAGE_NOACCESS permission - VirtualProtectEx(dbgProcessInformation.hProcess, (LPVOID)ResetMemBPXAddress, - ResetMemBPXSize, FoundBreakPoint.OldProtect, &OldProtect); + myCustomHandler = (fCustomHandler)(foundBreakPoint.ExecuteCallBack); + myCustomHandler((void*)accessAddr); } - // Call the custom memory breakpoint handler - if(bCallCustomHandler) + ResetMemBpxCallback = [currentPageAddr] { - myCustomHandler = (fCustomHandler)(MemoryBpxCallBack); - myCustomHandler((void*)bpaddr); - } + // We have successfully executed the instruction! + // But by this point the breakpoint could have been removed in a callback. + // We should check if it's still here (or some of our other breakpoints), + // otherwise there's no need to restore the protection. + + auto hitPage = MemoryBreakpointPages.find(currentPageAddr); + if(hitPage != MemoryBreakpointPages.end()) + { + // The BP still exists OR it's been removed and a new one added + auto & pageData = hitPage->second; + DWORD oldProtect = 0; + VirtualProtectEx(dbgProcessInformation.hProcess, (LPVOID)currentPageAddr, TITANENGINE_PAGESIZE, pageData.newProtect, &oldProtect); + } + }; + + // We've handled the exception + DBGCode = DBG_CONTINUE; + // Use the trap flag to schedule the page protection restoration on the next single-step event + synchronizedStep = true; + hActiveThread = EngineOpenThread(THREAD_GETSETSUSPEND, false, DBGEvent.dwThreadId); + CONTEXT myDBGContext; + myDBGContext.ContextFlags = ContextControlFlags; + GetThreadContext(hActiveThread, &myDBGContext); + myDBGContext.EFlags |= UE_TRAP_FLAG; + SetThreadContext(hActiveThread, &myDBGContext); EngineCloseHandle(hActiveThread); - } - else //no memory breakpoint found - { - DBGCode = DBG_EXCEPTION_NOT_HANDLED; - } - if(ResetMemBPX) //memory breakpoint hit - { + + // Prevent the trap flag from leaking to the stack (by erasing it right after executing PUSHF) ULONG_PTR ueCurrentPosition = GetContextData(UE_CIP); unsigned char instr[16]; - MemoryReadSafe(dbgProcessInformation.hProcess, (void*)ueCurrentPosition, instr, sizeof(instr), 0); + MemoryReadSafe(dbgProcessInformation.hProcess, (void*)ueCurrentPosition, instr, sizeof(instr), nullptr); char* DisassembledString = (char*)StaticDisassembleEx(ueCurrentPosition, (LPVOID)instr); if(strstr(DisassembledString, "PUSHF")) PushfBPX = true; } - // Debuggee generated access violation exception + + // Debuggee generated the GUARD_PAGE or ACCESS_VIOLATION exception if(DBGCode == DBG_EXCEPTION_NOT_HANDLED) { - if(DBGCustomHandler->chAccessViolation != NULL) + if(isAccessViolation) { - myCustomHandler = (fCustomHandler)((LPVOID)DBGCustomHandler->chAccessViolation); - myCustomHandler(&DBGEvent.u.Exception.ExceptionRecord); + if(DBGCustomHandler->chAccessViolation != NULL) + { + myCustomHandler = (fCustomHandler)((LPVOID)DBGCustomHandler->chAccessViolation); + myCustomHandler(&DBGEvent.u.Exception.ExceptionRecord); + } + } + else + { + if(DBGCustomHandler->chPageGuard != NULL) + { + myCustomHandler = (fCustomHandler)((LPVOID)DBGCustomHandler->chPageGuard); + myCustomHandler(&DBGEvent.u.Exception.ExceptionRecord); + } } } } diff --git a/TitanEngine/TitanEngine.Debugger.Memory.cpp b/TitanEngine/TitanEngine.Debugger.Memory.cpp index e9deed4..5cb65cf 100644 --- a/TitanEngine/TitanEngine.Debugger.Memory.cpp +++ b/TitanEngine/TitanEngine.Debugger.Memory.cpp @@ -400,7 +400,7 @@ __declspec(dllexport) bool TITCALL MemoryWriteSafe(HANDLE hProcess, LPVOID lpBas pNumBytes = lpNumberOfBytesWritten; } - if(!WriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, pNumBytes)) + if(!WriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, pNumBytes) || *pNumBytes < nSize) { if(VirtualProtectEx(hProcess, lpBaseAddress, nSize, PAGE_EXECUTE_READWRITE, &dwProtect)) { diff --git a/TitanEngine/stdafx.h b/TitanEngine/stdafx.h index bf86613..08a3eea 100644 --- a/TitanEngine/stdafx.h +++ b/TitanEngine/stdafx.h @@ -245,9 +245,17 @@ typedef struct int AdvancedBreakPointType; int MemoryBpxRestoreOnHit; ULONG_PTR ExecuteCallBack; - DWORD OldProtect; } BreakPointDetail, *PBreakPointDetail; +typedef struct +{ + // Numbers of active BPs the page contains for each type + uint16_t accessBps, readBps, writeBps, executeBps; + + DWORD origProtect; // original protection before any BPs were applied + DWORD newProtect; // current protection including all enabled BPs +} MemoryBreakpointPageDetail; + typedef struct { ULONG_PTR DrxBreakAddress;