mirror of https://github.com/x64dbg/TitanEngine
Implement memory breakpoints that are not page-aligned
This commit is contained in:
parent
882bc1bc30
commit
76c1b86250
|
|
@ -3,6 +3,7 @@
|
|||
#include "Global.Breakpoints.h"
|
||||
|
||||
std::vector<BreakPointDetail> BreakPointBuffer;
|
||||
std::unordered_map<ULONG_PTR, MemoryBreakpointPageDetail> MemoryBreakpointPages;
|
||||
|
||||
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
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "Global.Engine.Threading.h"
|
||||
#include "Global.Engine.h"
|
||||
#include "Global.Debugger.h"
|
||||
|
||||
|
||||
extern std::vector<BreakPointDetail> BreakPointBuffer;
|
||||
extern std::unordered_map<ULONG_PTR, MemoryBreakpointPageDetail> MemoryBreakpointPages;
|
||||
|
||||
void uintdr7(ULONG_PTR dr7, DR7* ret);
|
||||
ULONG_PTR dr7uint(DR7* dr7);
|
||||
|
|
@ -12,4 +18,8 @@ void BreakPointPostReadFilter(ULONG_PTR lpBaseAddress, unsigned char* lpBuffer,
|
|||
void BreakPointPreWriteFilter(ULONG_PTR lpBaseAddress, SIZE_T nSize);
|
||||
void BreakPointPostWriteFilter(ULONG_PTR lpBaseAddress, SIZE_T nSize);
|
||||
|
||||
bool IsDepEnabled(bool* outPermanent = nullptr);
|
||||
DWORD GetPageProtectionForMemoryBreakpoint(const MemoryBreakpointPageDetail & page);
|
||||
bool IsMemoryAccessAllowed(DWORD memProtect, ULONG_PTR accessType);
|
||||
|
||||
#endif //_GLOBAL_BREAKPOINTS_H
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ void DebuggerReset()
|
|||
RtlZeroMemory(&myDBGCustomHandler, sizeof CustomHandler);
|
||||
}
|
||||
std::vector<BreakPointDetail>().swap(BreakPointBuffer);
|
||||
std::unordered_map<ULONG_PTR, MemoryBreakpointPageDetail>().swap(MemoryBreakpointPages);
|
||||
}
|
||||
|
||||
void ClearProcessList()
|
||||
|
|
|
|||
|
|
@ -33,7 +33,8 @@ __declspec(dllexport) bool TITCALL IsBPXEnabled(ULONG_PTR bpxAddress)
|
|||
int bpcount = (int)BreakPointBuffer.size();
|
||||
for(int i = 0; i < bpcount; i++)
|
||||
{
|
||||
if(BreakPointBuffer.at(i).BreakPointAddress == bpxAddress)
|
||||
const bool isSoftwareBpx = BreakPointBuffer.at(i).BreakPointType == UE_SINGLESHOOT || BreakPointBuffer.at(i).BreakPointType == UE_BREAKPOINT;
|
||||
if(isSoftwareBpx && BreakPointBuffer.at(i).BreakPointAddress == bpxAddress)
|
||||
{
|
||||
if(BreakPointBuffer.at(i).BreakPointActive != UE_BPXINACTIVE)
|
||||
{
|
||||
|
|
@ -440,64 +441,115 @@ __declspec(dllexport) bool TITCALL SetMemoryBPX(ULONG_PTR MemoryStart, SIZE_T Si
|
|||
|
||||
__declspec(dllexport) bool TITCALL SetMemoryBPXEx(ULONG_PTR MemoryStart, SIZE_T SizeOfMemory, DWORD BreakPointType, bool RestoreOnHit, LPVOID bpxCallBack)
|
||||
{
|
||||
if(MemoryStart % TITANENGINE_PAGESIZE || !SizeOfMemory || SizeOfMemory % TITANENGINE_PAGESIZE) //ensure the data is aligned with the page size
|
||||
return false;
|
||||
struct TempMemoryBreakpointDetails
|
||||
{
|
||||
ULONG_PTR addr;
|
||||
DWORD currentPageProtect;
|
||||
MemoryBreakpointPageDetail data;
|
||||
};
|
||||
|
||||
CriticalSectionLocker lock(LockBreakPointBuffer);
|
||||
MEMORY_BASIC_INFORMATION MemInfo;
|
||||
ULONG_PTR NumberOfBytesReadWritten = 0;
|
||||
bool isSuccess = true;
|
||||
DWORD oldProtect;
|
||||
|
||||
// Note: memory breakpoints cannot intersect.
|
||||
// Check that there are no other MemBPs in the address range [MemoryStart, MemoryStart+SizeOfMemory)
|
||||
int bpcount = (int)BreakPointBuffer.size();
|
||||
DWORD OldProtect = 0;
|
||||
//search for breakpoint
|
||||
for(int i = 0; i < bpcount; i++)
|
||||
{
|
||||
if(BreakPointBuffer.at(i).BreakPointAddress == MemoryStart &&
|
||||
(BreakPointBuffer.at(i).BreakPointType == UE_MEMORY ||
|
||||
BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_READ ||
|
||||
BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_WRITE ||
|
||||
BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_EXECUTE)
|
||||
)
|
||||
auto bpAddr = BreakPointBuffer.at(i).BreakPointAddress;
|
||||
auto bpSize = BreakPointBuffer.at(i).BreakPointSize;
|
||||
auto bpType = BreakPointBuffer.at(i).BreakPointType;
|
||||
bool isMem = bpType == UE_MEMORY || bpType == UE_MEMORY_READ || bpType == UE_MEMORY_WRITE || bpType == UE_MEMORY_EXECUTE;
|
||||
|
||||
if (isMem && bpAddr < (MemoryStart + SizeOfMemory) && bpAddr + bpSize > MemoryStart)
|
||||
{
|
||||
return false;
|
||||
return false; // the place is taken
|
||||
}
|
||||
}
|
||||
//set PAGE_GUARD on all the pages separately
|
||||
size_t pages = SizeOfMemory / TITANENGINE_PAGESIZE;
|
||||
|
||||
for(size_t i = 0; i < pages; i++)
|
||||
// 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);
|
||||
|
||||
VirtualQueryEx(dbgProcessInformation.hProcess, curPage, &MemInfo, sizeof(MEMORY_BASIC_INFORMATION));
|
||||
|
||||
if(OldProtect == 0)
|
||||
OldProtect = MemInfo.Protect;
|
||||
|
||||
// Check if the alternative memory breakpoint method should be used
|
||||
if(engineMembpAlt)
|
||||
// Save the current page protection in case of a failure
|
||||
MEMORY_BASIC_INFORMATION memInfo;
|
||||
if(!VirtualQueryEx(dbgProcessInformation.hProcess, (LPCVOID)page, &memInfo, sizeof(memInfo)))
|
||||
{
|
||||
if(!(MemInfo.Protect & PAGE_NOACCESS))
|
||||
{
|
||||
VirtualProtectEx(dbgProcessInformation.hProcess, curPage, TITANENGINE_PAGESIZE, PAGE_NOACCESS, &MemInfo.Protect);
|
||||
isSuccess = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// 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
|
||||
{
|
||||
// Default to using PAGE_GUARD memory breakpoint
|
||||
if(!(MemInfo.Protect & PAGE_GUARD))
|
||||
// There are other memory BPs on this page
|
||||
pageData = found->second; // original protection stays the same
|
||||
}
|
||||
|
||||
switch(BreakPointType)
|
||||
{
|
||||
DWORD NewProtect = MemInfo.Protect ^ PAGE_GUARD;
|
||||
VirtualProtectEx(dbgProcessInformation.hProcess, curPage, TITANENGINE_PAGESIZE, NewProtect, &MemInfo.Protect);
|
||||
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;
|
||||
memset(&NewBreakPoint, 0, sizeof(BreakPointDetail));
|
||||
NewBreakPoint.BreakPointActive = UE_BPXACTIVE;
|
||||
NewBreakPoint.BreakPointAddress = MemoryStart;
|
||||
NewBreakPoint.BreakPointType = BreakPointType;
|
||||
NewBreakPoint.BreakPointSize = SizeOfMemory;
|
||||
NewBreakPoint.OldProtect = OldProtect;
|
||||
NewBreakPoint.BreakPointType = BreakPointType;
|
||||
NewBreakPoint.MemoryBpxRestoreOnHit = (BYTE)RestoreOnHit;
|
||||
NewBreakPoint.ExecuteCallBack = (ULONG_PTR)bpxCallBack;
|
||||
BreakPointBuffer.push_back(NewBreakPoint);
|
||||
|
|
@ -506,67 +558,84 @@ __declspec(dllexport) bool TITCALL SetMemoryBPXEx(ULONG_PTR MemoryStart, SIZE_T
|
|||
|
||||
__declspec(dllexport) bool TITCALL RemoveMemoryBPX(ULONG_PTR MemoryStart, SIZE_T SizeOfMemory)
|
||||
{
|
||||
if(MemoryStart % TITANENGINE_PAGESIZE || SizeOfMemory % TITANENGINE_PAGESIZE) //ensure the data is aligned with the page size
|
||||
return false;
|
||||
CriticalSectionLocker lock(LockBreakPointBuffer);
|
||||
MEMORY_BASIC_INFORMATION MemInfo;
|
||||
ULONG_PTR NumberOfBytesReadWritten = 0;
|
||||
int bpcount = (int)BreakPointBuffer.size();
|
||||
int found = -1;
|
||||
//search for breakpoint
|
||||
for(int i = 0; i < bpcount; i++)
|
||||
bool isSuccess = true;
|
||||
|
||||
// find the breakpoint
|
||||
int nFoundBp = -1;
|
||||
size_t bpcount = BreakPointBuffer.size();
|
||||
for(size_t i = 0; i < bpcount; i++)
|
||||
{
|
||||
if(BreakPointBuffer.at(i).BreakPointAddress == MemoryStart &&
|
||||
(BreakPointBuffer.at(i).BreakPointType == UE_MEMORY ||
|
||||
BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_READ ||
|
||||
BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_WRITE ||
|
||||
BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_EXECUTE)
|
||||
)
|
||||
auto bpAddr = BreakPointBuffer.at(i).BreakPointAddress;
|
||||
auto bpType = BreakPointBuffer.at(i).BreakPointType;
|
||||
bool isMem = bpType == UE_MEMORY || bpType == UE_MEMORY_READ || bpType == UE_MEMORY_WRITE || bpType == UE_MEMORY_EXECUTE;
|
||||
|
||||
if(isMem && bpAddr == MemoryStart)
|
||||
{
|
||||
found = i;
|
||||
nFoundBp = (int)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(found == -1) //not found
|
||||
return false;
|
||||
if(nFoundBp == -1)
|
||||
return false; // not found
|
||||
|
||||
if(!SizeOfMemory)
|
||||
SizeOfMemory = BreakPointBuffer.at(found).BreakPointSize;
|
||||
int memBpType = BreakPointBuffer.at(nFoundBp).BreakPointType;
|
||||
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
|
||||
size_t pages = SizeOfMemory / TITANENGINE_PAGESIZE;
|
||||
|
||||
for(size_t i = 0; i < pages; i++)
|
||||
//delete the memory breakpoint from the pages
|
||||
auto pageStart = ALIGN_DOWN_BY(MemoryStart, TITANENGINE_PAGESIZE);
|
||||
auto pageEnd = ALIGN_UP_BY(MemoryStart + SizeOfMemory, TITANENGINE_PAGESIZE);
|
||||
for(ULONG_PTR pageAddr = pageStart; pageAddr < pageEnd; pageAddr += TITANENGINE_PAGESIZE)
|
||||
{
|
||||
const LPVOID curPage = (LPVOID)(MemoryStart + i * TITANENGINE_PAGESIZE);
|
||||
auto foundPageData = MemoryBreakpointPages.find(pageAddr);
|
||||
if(foundPageData == MemoryBreakpointPages.end())
|
||||
continue; // should not happen
|
||||
|
||||
VirtualQueryEx(dbgProcessInformation.hProcess, curPage, &MemInfo, sizeof(MEMORY_BASIC_INFORMATION));
|
||||
|
||||
// Check if the alternative memory breakpoint method is being used
|
||||
if(engineMembpAlt)
|
||||
// Decrement a BP counter
|
||||
auto & pageData = foundPageData->second;
|
||||
switch(memBpType)
|
||||
{
|
||||
if(MemInfo.Protect & PAGE_NOACCESS)
|
||||
{
|
||||
VirtualProtectEx(dbgProcessInformation.hProcess, curPage, TITANENGINE_PAGESIZE,
|
||||
BreakPointBuffer.at(found).OldProtect, &MemInfo.Protect);
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
if(MemInfo.Protect & PAGE_GUARD)
|
||||
{
|
||||
DWORD NewProtect = MemInfo.Protect ^ PAGE_GUARD;
|
||||
// Some BPs are still here. According to their types, reapply page protection.
|
||||
pageData.newProtect = GetPageProtectionForMemoryBreakpoint(pageData);
|
||||
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
|
||||
BreakPointBuffer.erase(BreakPointBuffer.begin() + found);
|
||||
BreakPointBuffer.erase(BreakPointBuffer.begin() + nFoundBp);
|
||||
|
||||
return true;
|
||||
return isSuccess;
|
||||
}
|
||||
|
||||
__declspec(dllexport) bool TITCALL GetUnusedHardwareBreakPointRegister(LPDWORD RegisterIndex)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include "Global.Librarian.h"
|
||||
#include "Global.TLS.h"
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
|
||||
#define UE_MODULEx86 0x2000;
|
||||
#define UE_MODULEx64 0x2000;
|
||||
|
|
@ -51,7 +52,6 @@ __declspec(dllexport) void TITCALL DebugLoop()
|
|||
bool hListProcessFirst = true;
|
||||
bool hListThreadFirst = true;
|
||||
bool hListLibraryFirst = true;
|
||||
bool MemoryBpxFound = false;
|
||||
PLIBRARY_ITEM_DATAW hLoadedLibData = NULL;
|
||||
PLIBRARY_BREAK_DATA ptrLibrarianData = NULL;
|
||||
typedef void(TITCALL * fCustomBreakPoint)(void);
|
||||
|
|
@ -59,16 +59,12 @@ __declspec(dllexport) void TITCALL DebugLoop()
|
|||
typedef void(TITCALL * fFindOEPHandler)(LPPROCESS_INFORMATION fProcessInfo, LPVOID fCallBack);
|
||||
fCustomHandler myCustomHandler;
|
||||
fCustomBreakPoint myCustomBreakPoint;
|
||||
ULONG_PTR MemoryBpxCallBack = 0;
|
||||
SIZE_T ResetBPXSize = 0;
|
||||
ULONG_PTR ResetBPXAddressTo = 0;
|
||||
ULONG_PTR ResetMemBPXAddress = 0;
|
||||
SIZE_T ResetMemBPXSize = 0;
|
||||
std::function<void()> ResetMemBpxCallback;
|
||||
ULONG_PTR NumberOfBytesReadWritten = 0;
|
||||
MEMORY_BASIC_INFORMATION MemInfo;
|
||||
HANDLE hActiveThread;
|
||||
DWORD OldProtect;
|
||||
DWORD NewProtect;
|
||||
DWORD DebugRegisterXId = NULL;
|
||||
HARDWARE_DATA DebugRegisterX;
|
||||
wchar_t DLLDebugFileName[512];
|
||||
|
|
@ -684,36 +680,7 @@ __declspec(dllexport) void TITCALL DebugLoop()
|
|||
if(ResetMemBPX) //restore memory breakpoint
|
||||
{
|
||||
ResetMemBPX = false;
|
||||
|
||||
// Check if the alternative memory breakpoint method should be used
|
||||
if(engineMembpAlt)
|
||||
{
|
||||
// Check if the breakpoint is still enabled/present and has not been removed
|
||||
for(size_t i = 0; i < BreakPointBuffer.size(); i++)
|
||||
{
|
||||
if(BreakPointBuffer.at(i).BreakPointAddress == ResetMemBPXAddress &&
|
||||
(BreakPointBuffer.at(i).BreakPointType == UE_MEMORY ||
|
||||
BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_READ ||
|
||||
BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_WRITE ||
|
||||
BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_EXECUTE) &&
|
||||
BreakPointBuffer.at(i).BreakPointActive == UE_BPXACTIVE)
|
||||
{
|
||||
// Restore the breakpoint
|
||||
VirtualProtectEx(dbgProcessInformation.hProcess, (LPVOID)ResetMemBPXAddress,
|
||||
ResetMemBPXSize, PAGE_NOACCESS, &OldProtect);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
VirtualQueryEx(dbgProcessInformation.hProcess, (LPCVOID)ResetMemBPXAddress, &MemInfo, sizeof(MEMORY_BASIC_INFORMATION));
|
||||
OldProtect = MemInfo.Protect;
|
||||
NewProtect = OldProtect | PAGE_GUARD; //guard page protection
|
||||
VirtualProtectEx(dbgProcessInformation.hProcess, (LPVOID)ResetMemBPXAddress, ResetMemBPXSize, NewProtect, &OldProtect);
|
||||
}
|
||||
|
||||
ResetMemBpxCallback();
|
||||
engineStep();
|
||||
}
|
||||
}
|
||||
|
|
@ -876,385 +843,215 @@ __declspec(dllexport) void TITCALL DebugLoop()
|
|||
break;
|
||||
|
||||
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:
|
||||
{
|
||||
ULONG_PTR bpaddr;
|
||||
bool bFoundBreakPoint = false;
|
||||
bool bCallCustomHandler = false;
|
||||
BreakPointDetail FoundBreakPoint;
|
||||
int bpcount = (int)BreakPointBuffer.size();
|
||||
// Plan (making sure the breakpoint is valid):
|
||||
// 1) Check if one of our BPs falls into the access address
|
||||
// 2) Check if this breakpoint is of the right type (READ, WRITE, etc)
|
||||
// 3) Somehow check if the exception wasn't maliciosly caused by the debugged program
|
||||
// 4) If all are true (i.e. the BP is ours):
|
||||
// call the user callback, restore the original protection, single-step, put our protection back
|
||||
// if not:
|
||||
// - don't call the user callback
|
||||
// - restore the protection if there are still our BPs on this page OR pass the exception to the debuggee
|
||||
|
||||
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;
|
||||
bpaddr = (ULONG_PTR)DBGEvent.u.Exception.ExceptionRecord.ExceptionInformation[1]; //page accessed
|
||||
if(bpaddr >= addr && bpaddr < (addr + BreakPointBuffer.at(i).BreakPointSize) &&
|
||||
(BreakPointBuffer.at(i).BreakPointType == UE_MEMORY ||
|
||||
BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_READ ||
|
||||
BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_WRITE ||
|
||||
BreakPointBuffer.at(i).BreakPointType == UE_MEMORY_EXECUTE) &&
|
||||
BreakPointBuffer.at(i).BreakPointActive == UE_BPXACTIVE)
|
||||
ULONG_PTR bpAddr = BreakPointBuffer.at(i).BreakPointAddress;
|
||||
auto bpType = BreakPointBuffer.at(i).BreakPointType;
|
||||
bool isMemBp = bpType == UE_MEMORY || bpType == UE_MEMORY_READ || bpType == UE_MEMORY_WRITE || bpType == UE_MEMORY_EXECUTE;
|
||||
bool isActive = BreakPointBuffer.at(i).BreakPointActive == UE_BPXACTIVE;
|
||||
|
||||
if(isActive && isMemBp && accessAddr >= bpAddr && accessAddr < (bpAddr + BreakPointBuffer.at(i).BreakPointSize))
|
||||
{
|
||||
FoundBreakPoint = BreakPointBuffer.at(i);
|
||||
foundBreakPoint = BreakPointBuffer.at(i);
|
||||
bFoundBreakPoint = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Most of the logic has been copied from the STATUS_GUARD_PAGE_VIOLATION handler
|
||||
|
||||
if(bFoundBreakPoint && engineMembpAlt) //found memory breakpoint
|
||||
auto hitPage = MemoryBreakpointPages.find(currentPageAddr);
|
||||
if(!bFoundBreakPoint)
|
||||
{
|
||||
// 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);
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
else //no memory breakpoint found
|
||||
{
|
||||
DBGCode = DBG_EXCEPTION_NOT_HANDLED;
|
||||
}
|
||||
if(ResetMemBPX) //memory breakpoint hit
|
||||
{
|
||||
|
||||
// Prevent the trap flag from leaking to the stack (by erasing it right after executing PUSHF)
|
||||
ULONG_PTR ueCurrentPosition = GetContextData(UE_CIP);
|
||||
unsigned char instr[16];
|
||||
MemoryReadSafe(dbgProcessInformation.hProcess, (void*)ueCurrentPosition, instr, sizeof(instr), 0);
|
||||
MemoryReadSafe(dbgProcessInformation.hProcess, (void*)ueCurrentPosition, instr, sizeof(instr), nullptr);
|
||||
char* DisassembledString = (char*)StaticDisassembleEx(ueCurrentPosition, (LPVOID)instr);
|
||||
if(strstr(DisassembledString, "PUSHF"))
|
||||
PushfBPX = true;
|
||||
}
|
||||
|
||||
// Debuggee generated access violation exception
|
||||
|
||||
// Debuggee generated the GUARD_PAGE or ACCESS_VIOLATION exception
|
||||
if(DBGCode == DBG_EXCEPTION_NOT_HANDLED)
|
||||
{
|
||||
if(isAccessViolation)
|
||||
{
|
||||
if(DBGCustomHandler->chAccessViolation != NULL)
|
||||
{
|
||||
|
|
@ -1262,6 +1059,15 @@ __declspec(dllexport) void TITCALL DebugLoop()
|
|||
myCustomHandler(&DBGEvent.u.Exception.ExceptionRecord);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(DBGCustomHandler->chPageGuard != NULL)
|
||||
{
|
||||
myCustomHandler = (fCustomHandler)((LPVOID)DBGCustomHandler->chPageGuard);
|
||||
myCustomHandler(&DBGEvent.u.Exception.ExceptionRecord);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
|
|||
|
|
@ -400,7 +400,7 @@ __declspec(dllexport) bool TITCALL MemoryWriteSafe(HANDLE hProcess, LPVOID lpBas
|
|||
pNumBytes = lpNumberOfBytesWritten;
|
||||
}
|
||||
|
||||
if(!WriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, pNumBytes))
|
||||
if(!WriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, pNumBytes) || *pNumBytes < nSize)
|
||||
{
|
||||
if(VirtualProtectEx(hProcess, lpBaseAddress, nSize, PAGE_EXECUTE_READWRITE, &dwProtect))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -245,9 +245,17 @@ typedef struct
|
|||
int AdvancedBreakPointType;
|
||||
int MemoryBpxRestoreOnHit;
|
||||
ULONG_PTR ExecuteCallBack;
|
||||
DWORD OldProtect;
|
||||
} BreakPointDetail, *PBreakPointDetail;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
// Numbers of active BPs the page contains for each type
|
||||
uint16_t accessBps, readBps, writeBps, executeBps;
|
||||
|
||||
DWORD origProtect; // original protection before any BPs were applied
|
||||
DWORD newProtect; // current protection including all enabled BPs
|
||||
} MemoryBreakpointPageDetail;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ULONG_PTR DrxBreakAddress;
|
||||
|
|
|
|||
Loading…
Reference in New Issue