Skip to content

Commit

Permalink
Implement memory breakpoints that are not page-aligned
Browse files Browse the repository at this point in the history
  • Loading branch information
shocoman committed Oct 20, 2023
1 parent 882bc1b commit 76c1b86
Show file tree
Hide file tree
Showing 7 changed files with 451 additions and 451 deletions.
108 changes: 107 additions & 1 deletion TitanEngine/Global.Breakpoints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "Global.Breakpoints.h"

std::vector<BreakPointDetail> BreakPointBuffer;
std::unordered_map<ULONG_PTR, MemoryBreakpointPageDetail> MemoryBreakpointPages;

ULONG_PTR dr7uint(DR7* dr7)
{
Expand Down Expand Up @@ -181,4 +182,109 @@ 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;
}
}
10 changes: 10 additions & 0 deletions TitanEngine/Global.Breakpoints.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,24 @@
#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);
void BreakPointPostReadFilter(ULONG_PTR lpBaseAddress, unsigned char* lpBuffer, SIZE_T nSize);
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
1 change: 1 addition & 0 deletions TitanEngine/Global.Debugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Loading

0 comments on commit 76c1b86

Please sign in to comment.