Merge pull request #18 from shocoman/proper-membp-3-public

Implement memory breakpoints that are not page-aligned
This commit is contained in:
Duncan Ogilvie 2023-10-28 15:18:23 +02:00 committed by GitHub
commit 5484a49237
Signed by: GitHub
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 477 additions and 477 deletions

View File

@ -3,6 +3,7 @@
#include "Global.Breakpoints.h" #include "Global.Breakpoints.h"
std::vector<BreakPointDetail> BreakPointBuffer; std::vector<BreakPointDetail> BreakPointBuffer;
std::unordered_map<ULONG_PTR, MemoryBreakpointPageDetail> MemoryBreakpointPages;
ULONG_PTR dr7uint(DR7* dr7) ULONG_PTR dr7uint(DR7* dr7)
{ {
@ -182,3 +183,108 @@ void BreakPointPostWriteFilter(ULONG_PTR lpBaseAddress, SIZE_T nSize)
} }
} }
} }
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;
}
}

View File

@ -2,9 +2,15 @@
#define _GLOBAL_BREAKPOINTS_H #define _GLOBAL_BREAKPOINTS_H
#include <vector> #include <vector>
#include <unordered_map>
#include "Global.Engine.Threading.h" #include "Global.Engine.Threading.h"
#include "Global.Engine.h"
#include "Global.Debugger.h"
extern std::vector<BreakPointDetail> BreakPointBuffer; extern std::vector<BreakPointDetail> BreakPointBuffer;
extern std::unordered_map<ULONG_PTR, MemoryBreakpointPageDetail> MemoryBreakpointPages;
void uintdr7(ULONG_PTR dr7, DR7* ret); void uintdr7(ULONG_PTR dr7, DR7* ret);
ULONG_PTR dr7uint(DR7* dr7); 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 BreakPointPreWriteFilter(ULONG_PTR lpBaseAddress, SIZE_T nSize);
void BreakPointPostWriteFilter(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 #endif //_GLOBAL_BREAKPOINTS_H

View File

@ -88,6 +88,7 @@ void DebuggerReset()
RtlZeroMemory(&myDBGCustomHandler, sizeof CustomHandler); RtlZeroMemory(&myDBGCustomHandler, sizeof CustomHandler);
} }
std::vector<BreakPointDetail>().swap(BreakPointBuffer); std::vector<BreakPointDetail>().swap(BreakPointBuffer);
std::unordered_map<ULONG_PTR, MemoryBreakpointPageDetail>().swap(MemoryBreakpointPages);
} }
void ClearProcessList() void ClearProcessList()

View File

@ -33,7 +33,8 @@ __declspec(dllexport) bool TITCALL IsBPXEnabled(ULONG_PTR bpxAddress)
int bpcount = (int)BreakPointBuffer.size(); int bpcount = (int)BreakPointBuffer.size();
for(int i = 0; i < bpcount; i++) 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) 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) __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 struct TempMemoryBreakpointDetails
return false; {
ULONG_PTR addr;
DWORD currentPageProtect;
MemoryBreakpointPageDetail data;
};
CriticalSectionLocker lock(LockBreakPointBuffer); CriticalSectionLocker lock(LockBreakPointBuffer);
MEMORY_BASIC_INFORMATION MemInfo; bool isSuccess = true;
ULONG_PTR NumberOfBytesReadWritten = 0; 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(); int bpcount = (int)BreakPointBuffer.size();
DWORD OldProtect = 0;
//search for breakpoint
for(int i = 0; i < bpcount; i++) for(int i = 0; i < bpcount; i++)
{ {
if(BreakPointBuffer.at(i).BreakPointAddress == MemoryStart && auto bpAddr = BreakPointBuffer.at(i).BreakPointAddress;
(BreakPointBuffer.at(i).BreakPointType == UE_MEMORY || auto bpSize = BreakPointBuffer.at(i).BreakPointSize;
BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_READ || auto bpType = BreakPointBuffer.at(i).BreakPointType;
BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_WRITE || bool isMem = bpType == UE_MEMORY || bpType == UE_MEMORY_READ || bpType == UE_MEMORY_WRITE || bpType == UE_MEMORY_EXECUTE;
BreakPointBuffer.at(i).BreakPointType == 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++) // Set a proper protection (e.g. PAGE_GUARD) for all pages in the range
std::vector<TempMemoryBreakpointDetails> breakpointInfos;
MemoryBreakpointPageDetail pageData;
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)
{ {
const LPVOID curPage = (LPVOID)(MemoryStart + i * TITANENGINE_PAGESIZE); // Save the current page protection in case of a failure
MEMORY_BASIC_INFORMATION memInfo;
VirtualQueryEx(dbgProcessInformation.hProcess, curPage, &MemInfo, sizeof(MEMORY_BASIC_INFORMATION)); if(!VirtualQueryEx(dbgProcessInformation.hProcess, (LPCVOID)page, &memInfo, sizeof(memInfo)))
if(OldProtect == 0)
OldProtect = MemInfo.Protect;
// Check if the alternative memory breakpoint method should be used
if(engineMembpAlt)
{ {
if(!(MemInfo.Protect & PAGE_NOACCESS)) isSuccess = false;
{ break;
VirtualProtectEx(dbgProcessInformation.hProcess, curPage, TITANENGINE_PAGESIZE, PAGE_NOACCESS, &MemInfo.Protect); }
}
// Update page data and increment a BP counter
auto found = MemoryBreakpointPages.find(page);
if(found == MemoryBreakpointPages.end())
{
// It's the first memory BP on this page
pageData.origProtect = memInfo.Protect;
pageData.accessBps = pageData.readBps = pageData.writeBps = pageData.executeBps = 0;
} }
else else
{ {
// Default to using PAGE_GUARD memory breakpoint // There are other memory BPs on this page
if(!(MemInfo.Protect & PAGE_GUARD)) pageData = found->second; // original protection stays the same
{
DWORD NewProtect = MemInfo.Protect ^ PAGE_GUARD;
VirtualProtectEx(dbgProcessInformation.hProcess, curPage, TITANENGINE_PAGESIZE, NewProtect, &MemInfo.Protect);
}
} }
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);
} }
//add new breakpoint
// 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;
}
// Save the page data
for(const auto & page : breakpointInfos)
MemoryBreakpointPages[page.addr] = page.data;
// Add a new breakpoint
BreakPointDetail NewBreakPoint; BreakPointDetail NewBreakPoint;
memset(&NewBreakPoint, 0, sizeof(BreakPointDetail)); memset(&NewBreakPoint, 0, sizeof(BreakPointDetail));
NewBreakPoint.BreakPointActive = UE_BPXACTIVE; NewBreakPoint.BreakPointActive = UE_BPXACTIVE;
NewBreakPoint.BreakPointAddress = MemoryStart; NewBreakPoint.BreakPointAddress = MemoryStart;
NewBreakPoint.BreakPointType = BreakPointType;
NewBreakPoint.BreakPointSize = SizeOfMemory; NewBreakPoint.BreakPointSize = SizeOfMemory;
NewBreakPoint.OldProtect = OldProtect; NewBreakPoint.BreakPointType = BreakPointType;
NewBreakPoint.MemoryBpxRestoreOnHit = (BYTE)RestoreOnHit; NewBreakPoint.MemoryBpxRestoreOnHit = (BYTE)RestoreOnHit;
NewBreakPoint.ExecuteCallBack = (ULONG_PTR)bpxCallBack; NewBreakPoint.ExecuteCallBack = (ULONG_PTR)bpxCallBack;
BreakPointBuffer.push_back(NewBreakPoint); 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) __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); CriticalSectionLocker lock(LockBreakPointBuffer);
MEMORY_BASIC_INFORMATION MemInfo; bool isSuccess = true;
ULONG_PTR NumberOfBytesReadWritten = 0;
int bpcount = (int)BreakPointBuffer.size(); // find the breakpoint
int found = -1; int nFoundBp = -1;
//search for breakpoint size_t bpcount = BreakPointBuffer.size();
for(int i = 0; i < bpcount; i++) for(size_t i = 0; i < bpcount; i++)
{ {
if(BreakPointBuffer.at(i).BreakPointAddress == MemoryStart && auto bpAddr = BreakPointBuffer.at(i).BreakPointAddress;
(BreakPointBuffer.at(i).BreakPointType == UE_MEMORY || auto bpType = BreakPointBuffer.at(i).BreakPointType;
BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_READ || bool isMem = bpType == UE_MEMORY || bpType == UE_MEMORY_READ || bpType == UE_MEMORY_WRITE || bpType == UE_MEMORY_EXECUTE;
BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_WRITE ||
BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_EXECUTE) if(isMem && bpAddr == MemoryStart)
)
{ {
found = i; nFoundBp = (int)i;
break; break;
} }
} }
if(found == -1) //not found if(nFoundBp == -1)
return false; return false; // not found
if(!SizeOfMemory) int memBpType = BreakPointBuffer.at(nFoundBp).BreakPointType;
SizeOfMemory = BreakPointBuffer.at(found).BreakPointSize; SizeOfMemory = BreakPointBuffer.at(nFoundBp).BreakPointSize; // ignore the given size, x64dbg may be lying
// Revert to the original permission on all the pages in the range //delete the memory breakpoint from the pages
size_t pages = SizeOfMemory / TITANENGINE_PAGESIZE; auto pageStart = ALIGN_DOWN_BY(MemoryStart, TITANENGINE_PAGESIZE);
auto pageEnd = ALIGN_UP_BY(MemoryStart + SizeOfMemory, TITANENGINE_PAGESIZE);
for(size_t i = 0; i < pages; i++) 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;
// Check if the alternative memory breakpoint method is being used switch(memBpType)
if(engineMembpAlt)
{ {
if(MemInfo.Protect & PAGE_NOACCESS) case UE_MEMORY: // READ + WRITE + EXECUTE
{ pageData.accessBps -= 1;
VirtualProtectEx(dbgProcessInformation.hProcess, curPage, TITANENGINE_PAGESIZE, break;
BreakPointBuffer.at(found).OldProtect, &MemInfo.Protect); 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;
}
DWORD newProtect;
const bool noMoreBps = 0 == (pageData.accessBps + pageData.readBps + pageData.writeBps + pageData.executeBps);
if(noMoreBps)
{
// There are no more BPs on this page. Remove the page data.
newProtect = pageData.origProtect;
MemoryBreakpointPages.erase(foundPageData);
} }
else else
{ {
if(MemInfo.Protect & PAGE_GUARD) // Some BPs are still here. According to their types, reapply page protection.
{ pageData.newProtect = GetPageProtectionForMemoryBreakpoint(pageData);
DWORD NewProtect = MemInfo.Protect ^ PAGE_GUARD; newProtect = pageData.newProtect;
VirtualProtectEx(dbgProcessInformation.hProcess, curPage, TITANENGINE_PAGESIZE, NewProtect, &MemInfo.Protect);
}
} }
DWORD oldProtect;
if(!VirtualProtectEx(dbgProcessInformation.hProcess, (LPVOID)pageAddr, TITANENGINE_PAGESIZE, newProtect, &oldProtect))
isSuccess = false;
} }
//remove breakpoint from list //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) __declspec(dllexport) bool TITCALL GetUnusedHardwareBreakPointRegister(LPDWORD RegisterIndex)

View File

@ -8,6 +8,7 @@
#include "Global.Librarian.h" #include "Global.Librarian.h"
#include "Global.TLS.h" #include "Global.TLS.h"
#include <unordered_map> #include <unordered_map>
#include <functional>
#define UE_MODULEx86 0x2000; #define UE_MODULEx86 0x2000;
#define UE_MODULEx64 0x2000; #define UE_MODULEx64 0x2000;
@ -51,7 +52,6 @@ __declspec(dllexport) void TITCALL DebugLoop()
bool hListProcessFirst = true; bool hListProcessFirst = true;
bool hListThreadFirst = true; bool hListThreadFirst = true;
bool hListLibraryFirst = true; bool hListLibraryFirst = true;
bool MemoryBpxFound = false;
PLIBRARY_ITEM_DATAW hLoadedLibData = NULL; PLIBRARY_ITEM_DATAW hLoadedLibData = NULL;
PLIBRARY_BREAK_DATA ptrLibrarianData = NULL; PLIBRARY_BREAK_DATA ptrLibrarianData = NULL;
typedef void(TITCALL * fCustomBreakPoint)(void); typedef void(TITCALL * fCustomBreakPoint)(void);
@ -59,16 +59,12 @@ __declspec(dllexport) void TITCALL DebugLoop()
typedef void(TITCALL * fFindOEPHandler)(LPPROCESS_INFORMATION fProcessInfo, LPVOID fCallBack); typedef void(TITCALL * fFindOEPHandler)(LPPROCESS_INFORMATION fProcessInfo, LPVOID fCallBack);
fCustomHandler myCustomHandler; fCustomHandler myCustomHandler;
fCustomBreakPoint myCustomBreakPoint; fCustomBreakPoint myCustomBreakPoint;
ULONG_PTR MemoryBpxCallBack = 0;
SIZE_T ResetBPXSize = 0; SIZE_T ResetBPXSize = 0;
ULONG_PTR ResetBPXAddressTo = 0; ULONG_PTR ResetBPXAddressTo = 0;
ULONG_PTR ResetMemBPXAddress = 0; std::function<void()> ResetMemBpxCallback;
SIZE_T ResetMemBPXSize = 0;
ULONG_PTR NumberOfBytesReadWritten = 0; ULONG_PTR NumberOfBytesReadWritten = 0;
MEMORY_BASIC_INFORMATION MemInfo;
HANDLE hActiveThread; HANDLE hActiveThread;
DWORD OldProtect; DWORD OldProtect;
DWORD NewProtect;
DWORD DebugRegisterXId = NULL; DWORD DebugRegisterXId = NULL;
HARDWARE_DATA DebugRegisterX; HARDWARE_DATA DebugRegisterX;
wchar_t DLLDebugFileName[512]; wchar_t DLLDebugFileName[512];
@ -687,36 +683,7 @@ __declspec(dllexport) void TITCALL DebugLoop()
if(ResetMemBPX) //restore memory breakpoint if(ResetMemBPX) //restore memory breakpoint
{ {
ResetMemBPX = false; ResetMemBPX = false;
ResetMemBpxCallback();
// 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);
}
engineStep(); engineStep();
} }
} }
@ -879,390 +846,229 @@ __declspec(dllexport) void TITCALL DebugLoop()
break; break;
case STATUS_GUARD_PAGE_VIOLATION: case STATUS_GUARD_PAGE_VIOLATION:
{
ULONG_PTR bpaddr;
bool bFoundBreakPoint = 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)
{
FoundBreakPoint = BreakPointBuffer.at(i);
bFoundBreakPoint = true;
break;
}
}
if(bFoundBreakPoint) //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
{
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
{
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;
}
}
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
{
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;
}
}
EngineCloseHandle(hActiveThread);
}
else //no memory breakpoint found
{
DBGCode = DBG_EXCEPTION_NOT_HANDLED;
}
if(ResetMemBPX) //memory breakpoint hit
{
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;
}
//debuggee generated GUARD_PAGE exception
if(DBGCode == DBG_EXCEPTION_NOT_HANDLED)
{
//TODO: restore memory breakpoint?
if(DBGCustomHandler->chPageGuard != NULL)
{
myCustomHandler = (fCustomHandler)((LPVOID)DBGCustomHandler->chPageGuard);
myCustomHandler(&DBGEvent.u.Exception.ExceptionRecord);
}
}
}
break;
case STATUS_ACCESS_VIOLATION: case STATUS_ACCESS_VIOLATION:
{ {
ULONG_PTR bpaddr; // Plan (making sure the breakpoint is valid):
bool bFoundBreakPoint = false; // 1) Check if one of our BPs falls into the access address
bool bCallCustomHandler = false; // 2) Check if this breakpoint is of the right type (READ, WRITE, etc)
BreakPointDetail FoundBreakPoint; // 3) Somehow check if the exception wasn't maliciosly caused by the debugged program
int bpcount = (int)BreakPointBuffer.size(); // 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
for(int i = 0; i < bpcount; i++) 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;
size_t bpcount = BreakPointBuffer.size();
for(size_t i = 0; i < bpcount; i++)
{ {
ULONG_PTR addr = BreakPointBuffer.at(i).BreakPointAddress; ULONG_PTR bpAddr = BreakPointBuffer.at(i).BreakPointAddress;
bpaddr = (ULONG_PTR)DBGEvent.u.Exception.ExceptionRecord.ExceptionInformation[1]; //page accessed auto bpType = BreakPointBuffer.at(i).BreakPointType;
if(bpaddr >= addr && bpaddr < (addr + BreakPointBuffer.at(i).BreakPointSize) && bool isMemBp = bpType == UE_MEMORY || bpType == UE_MEMORY_READ || bpType == UE_MEMORY_WRITE || bpType == UE_MEMORY_EXECUTE;
(BreakPointBuffer.at(i).BreakPointType == UE_MEMORY || bool isActive = BreakPointBuffer.at(i).BreakPointActive == UE_BPXACTIVE;
BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_READ ||
BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_WRITE || if(isActive && isMemBp && accessAddr >= bpAddr && accessAddr < (bpAddr + BreakPointBuffer.at(i).BreakPointSize))
BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_EXECUTE) &&
BreakPointBuffer.at(i).BreakPointActive == UE_BPXACTIVE)
{ {
FoundBreakPoint = BreakPointBuffer.at(i); foundBreakPoint = BreakPointBuffer.at(i);
bFoundBreakPoint = true; bFoundBreakPoint = true;
break; break;
} }
} }
// Most of the logic has been copied from the STATUS_GUARD_PAGE_VIOLATION handler auto hitPage = MemoryBreakpointPages.find(currentPageAddr);
if(!bFoundBreakPoint)
if(bFoundBreakPoint && engineMembpAlt) //found memory breakpoint
{ {
// There were no BPs at the accessed address.
// But this page may still contain our BPs somewhere else
if(hitPage != MemoryBreakpointPages.end())
{
// There is a breakpoint! Maybe it caused this exception?
// We should restore the page protection and continue execution.
ResetMemBPX = true;
}
else
{
// 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.
}
}
else if(hitPage == MemoryBreakpointPages.end())
{
// Inconsistent page data; should never happen
}
else
{
// The debuggee actually hit one of our breakpoints
MemoryBreakpointPageDetail pageData = hitPage->second;
// Part 2.
// Ensure that the access type was correct.
bool isCorrectAccessType = false;
switch(foundBreakPoint.BreakPointType)
{
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;
}
// 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)
{
// We wouldn't make a BP with this kind of protection. Pass the exception to the debuggee.
}
else if(isAccessViolation // STATUS_ACCESS_VIOLATION
&& (accessType == 1 /*WRITE*/ && pageData.writeBps == 0 || accessType == 8 /*EXECUTE*/ && pageData.executeBps == 0)
&& (pageData.newProtect & 0xFF) != PAGE_NOACCESS)
{
// 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(!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)
{
// 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(!isCorrectAccessType)
{
// 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
{
// 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)
{
// 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)
VirtualProtectEx(dbgProcessInformation.hProcess, (LPVOID)currentPageAddr, TITANENGINE_PAGESIZE, originalProtect, &OldProtect);
if(bCallUserCallback)
{
myCustomHandler = (fCustomHandler)(foundBreakPoint.ExecuteCallBack);
myCustomHandler((void*)accessAddr);
}
ResetMemBpxCallback = [currentPageAddr]
{
// 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); hActiveThread = EngineOpenThread(THREAD_GETSETSUSPEND, false, DBGEvent.dwThreadId);
CONTEXT myDBGContext; CONTEXT myDBGContext;
myDBGContext.ContextFlags = ContextControlFlags; myDBGContext.ContextFlags = ContextControlFlags;
GetThreadContext(hActiveThread, &myDBGContext); GetThreadContext(hActiveThread, &myDBGContext);
DBGCode = DBG_CONTINUE; //debugger handled the exception myDBGContext.EFlags |= UE_TRAP_FLAG;
MemoryBpxCallBack = FoundBreakPoint.ExecuteCallBack; SetThreadContext(hActiveThread, &myDBGContext);
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;
}
bCallCustomHandler = true;
}
else if(FoundBreakPoint.BreakPointType == UE_MEMORY_READ) //READ
{
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;
}
}
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
{
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;
}
}
else if(FoundBreakPoint.BreakPointType == UE_MEMORY_EXECUTE) //EXECUTE
{
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
{
{
myDBGContext.EFlags |= UE_TRAP_FLAG;
synchronizedStep = true;
SetThreadContext(hActiveThread, &myDBGContext);
}
ResetMemBPXAddress = FoundBreakPoint.BreakPointAddress;
ResetMemBPXSize = FoundBreakPoint.BreakPointSize;
ResetMemBPX = true;
}
}
// If the breakpoint has to be restored...
if(ResetMemBPX)
{
// ...temporarily revert the PAGE_NOACCESS permission
VirtualProtectEx(dbgProcessInformation.hProcess, (LPVOID)ResetMemBPXAddress,
ResetMemBPXSize, FoundBreakPoint.OldProtect, &OldProtect);
}
// Call the custom memory breakpoint handler
if(bCallCustomHandler)
{
myCustomHandler = (fCustomHandler)(MemoryBpxCallBack);
myCustomHandler((void*)bpaddr);
}
EngineCloseHandle(hActiveThread); EngineCloseHandle(hActiveThread);
}
else //no memory breakpoint found // Prevent the trap flag from leaking to the stack (by erasing it right after executing PUSHF)
{
DBGCode = DBG_EXCEPTION_NOT_HANDLED;
}
if(ResetMemBPX) //memory breakpoint hit
{
ULONG_PTR ueCurrentPosition = GetContextData(UE_CIP); ULONG_PTR ueCurrentPosition = GetContextData(UE_CIP);
unsigned char instr[16]; 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); char* DisassembledString = (char*)StaticDisassembleEx(ueCurrentPosition, (LPVOID)instr);
if(strstr(DisassembledString, "PUSHF")) if(strstr(DisassembledString, "PUSHF"))
PushfBPX = true; PushfBPX = true;
} }
// Debuggee generated access violation exception
// Debuggee generated the GUARD_PAGE or ACCESS_VIOLATION exception
if(DBGCode == DBG_EXCEPTION_NOT_HANDLED) if(DBGCode == DBG_EXCEPTION_NOT_HANDLED)
{ {
if(DBGCustomHandler->chAccessViolation != NULL) if(isAccessViolation)
{ {
myCustomHandler = (fCustomHandler)((LPVOID)DBGCustomHandler->chAccessViolation); if(DBGCustomHandler->chAccessViolation != NULL)
myCustomHandler(&DBGEvent.u.Exception.ExceptionRecord); {
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);
}
} }
} }
} }

View File

@ -400,7 +400,7 @@ __declspec(dllexport) bool TITCALL MemoryWriteSafe(HANDLE hProcess, LPVOID lpBas
pNumBytes = lpNumberOfBytesWritten; 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)) if(VirtualProtectEx(hProcess, lpBaseAddress, nSize, PAGE_EXECUTE_READWRITE, &dwProtect))
{ {

View File

@ -245,9 +245,17 @@ typedef struct
int AdvancedBreakPointType; int AdvancedBreakPointType;
int MemoryBpxRestoreOnHit; int MemoryBpxRestoreOnHit;
ULONG_PTR ExecuteCallBack; ULONG_PTR ExecuteCallBack;
DWORD OldProtect;
} BreakPointDetail, *PBreakPointDetail; } 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 typedef struct
{ {
ULONG_PTR DrxBreakAddress; ULONG_PTR DrxBreakAddress;