mirror of https://github.com/x64dbg/TitanEngine
Merge pull request #18 from shocoman/proper-membp-3-public
Implement memory breakpoints that are not page-aligned
This commit is contained in:
commit
5484a49237
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(BreakPointType)
|
||||||
{
|
{
|
||||||
DWORD NewProtect = MemInfo.Protect ^ PAGE_GUARD;
|
case UE_MEMORY: // READ + WRITE + EXECUTE
|
||||||
VirtualProtectEx(dbgProcessInformation.hProcess, curPage, TITANENGINE_PAGESIZE, NewProtect, &MemInfo.Protect);
|
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)
|
||||||
|
|
|
||||||
|
|
@ -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,385 +846,215 @@ __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
|
|
||||||
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;
|
myDBGContext.EFlags |= UE_TRAP_FLAG;
|
||||||
synchronizedStep = true;
|
|
||||||
SetThreadContext(hActiveThread, &myDBGContext);
|
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(isAccessViolation)
|
||||||
{
|
{
|
||||||
if(DBGCustomHandler->chAccessViolation != NULL)
|
if(DBGCustomHandler->chAccessViolation != NULL)
|
||||||
{
|
{
|
||||||
|
|
@ -1265,6 +1062,15 @@ __declspec(dllexport) void TITCALL DebugLoop()
|
||||||
myCustomHandler(&DBGEvent.u.Exception.ExceptionRecord);
|
myCustomHandler(&DBGEvent.u.Exception.ExceptionRecord);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(DBGCustomHandler->chPageGuard != NULL)
|
||||||
|
{
|
||||||
|
myCustomHandler = (fCustomHandler)((LPVOID)DBGCustomHandler->chPageGuard);
|
||||||
|
myCustomHandler(&DBGEvent.u.Exception.ExceptionRecord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue