mirror of https://github.com/x64dbg/GleeBug
pretty much implemented setting/deleting memory breakpoints
This commit is contained in:
parent
ca254d1e1f
commit
83854fb4a7
|
|
@ -44,9 +44,9 @@ namespace GleeBug
|
|||
|
||||
enum class MemoryType
|
||||
{
|
||||
Acess,
|
||||
Write,
|
||||
Execute
|
||||
Access = 1,
|
||||
Write = 2,
|
||||
Execute = 4
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -88,6 +88,17 @@ namespace GleeBug
|
|||
BreakpointType type;
|
||||
BreakpointInternalInfo internal;
|
||||
};
|
||||
|
||||
/**
|
||||
\brief Structure for memory breakpoint management.
|
||||
*/
|
||||
struct MemoryBreakpointData
|
||||
{
|
||||
uint32 Refcount;
|
||||
uint32 Type;
|
||||
DWORD OldProtect;
|
||||
DWORD NewProtect;
|
||||
};
|
||||
};
|
||||
|
||||
#endif //DEBUGGER_BREAKPOINT_H
|
||||
|
|
@ -16,6 +16,7 @@ namespace GleeBug
|
|||
class Thread;
|
||||
enum class BreakpointType;
|
||||
struct BreakpointInfo;
|
||||
struct MemoryBreakpointData;
|
||||
|
||||
//constants
|
||||
const int HWBP_COUNT = GLEEBUG_HWBP_COUNT;
|
||||
|
|
@ -36,6 +37,7 @@ namespace GleeBug
|
|||
typedef std::map<BreakpointKey, BreakpointCallback> BreakpointCallbackMap;
|
||||
typedef std::unordered_map<ptr, BreakpointMap::iterator> SoftwareBreakpointMap;
|
||||
typedef std::set<Range, RangeCompare> MemoryBreakpointSet;
|
||||
typedef std::unordered_map<ptr, MemoryBreakpointData> MemoryBreakpointMap;
|
||||
|
||||
//vector typedefs
|
||||
typedef std::vector<StepCallback> StepCallbackVector;
|
||||
|
|
|
|||
|
|
@ -176,6 +176,227 @@ namespace GleeBug
|
|||
return success;
|
||||
}
|
||||
|
||||
#define PAGE_SHIFT (12)
|
||||
#define PAGE_ALIGN(Va) ((ULONG_PTR)((ULONG_PTR)(Va) & ~(PAGE_SIZE - 1)))
|
||||
#define BYTES_TO_PAGES(Size) (((Size) >> PAGE_SHIFT) + (((Size) & (PAGE_SIZE - 1)) != 0))
|
||||
#define ROUND_TO_PAGES(Size) (((ULONG_PTR)(Size) + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1))
|
||||
|
||||
/*
|
||||
#define PAGE_NOACCESS 0x01
|
||||
#define PAGE_READONLY 0x02
|
||||
#define PAGE_READWRITE 0x04
|
||||
#define PAGE_WRITECOPY 0x08 <- not supported
|
||||
|
||||
#define PAGE_EXECUTE 0x10
|
||||
#define PAGE_EXECUTE_READ 0x20
|
||||
#define PAGE_EXECUTE_READWRITE 0x40
|
||||
#define PAGE_EXECUTE_WRITECOPY 0x80 <- not supported
|
||||
|
||||
#define PAGE_GUARD 0x100 <- not supported with PAGE_NOACCESS
|
||||
#define PAGE_NOCACHE 0x200 <- not supported with PAGE_GUARD or PAGE_WRITECOMBINE
|
||||
#define PAGE_WRITECOMBINE 0x400 <- not supported with PAGE_GUARD or PAGE_NOCACHE
|
||||
*/
|
||||
|
||||
static DWORD RemoveExecuteAccess(DWORD dwAccess)
|
||||
{
|
||||
DWORD dwBase = dwAccess & 0xFF;
|
||||
DWORD dwHigh = dwAccess & 0xFFFFFF00;
|
||||
switch (dwBase)
|
||||
{
|
||||
case PAGE_EXECUTE:
|
||||
return dwHigh | PAGE_READONLY;
|
||||
case PAGE_EXECUTE_READ:
|
||||
case PAGE_EXECUTE_READWRITE:
|
||||
case PAGE_EXECUTE_WRITECOPY:
|
||||
return dwHigh | (dwBase >> 4);
|
||||
default:
|
||||
return dwAccess;
|
||||
}
|
||||
}
|
||||
|
||||
static DWORD RemoveWriteAccess(DWORD dwAccess)
|
||||
{
|
||||
DWORD dwBase = dwAccess & 0xFF;
|
||||
switch (dwBase)
|
||||
{
|
||||
case PAGE_READWRITE:
|
||||
case PAGE_EXECUTE_READWRITE:
|
||||
return (dwAccess & 0xFFFFFF00) | (dwBase >> 1);
|
||||
default:
|
||||
return dwAccess;
|
||||
}
|
||||
}
|
||||
|
||||
bool Process::SetNewPageProtection(ptr page, MemoryBreakpointData & data, MemoryType type)
|
||||
{
|
||||
//TODO: handle PAGE_NOACCESS and such correctly (since it cannot be combined with PAGE_GUARD)
|
||||
|
||||
auto found = memoryBreakpointPages.find(page);
|
||||
if (found == memoryBreakpointPages.end())
|
||||
{
|
||||
data.Refcount = 1;
|
||||
switch (type)
|
||||
{
|
||||
case MemoryType::Access:
|
||||
data.NewProtect = data.OldProtect | PAGE_GUARD;
|
||||
break;
|
||||
case MemoryType::Write:
|
||||
data.NewProtect = RemoveWriteAccess(data.OldProtect);
|
||||
break;
|
||||
case MemoryType::Execute:
|
||||
data.NewProtect = permanentDep ? RemoveExecuteAccess(data.OldProtect) : data.OldProtect | PAGE_GUARD;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto & oldData = found->second;
|
||||
data.Type = oldData.Type | uint32(type);
|
||||
data.OldProtect = oldData.OldProtect;
|
||||
data.Refcount = oldData.Refcount + 1;
|
||||
if (data.Type & uint32(MemoryType::Access)) //Access always becomes PAGE_GUARD
|
||||
data.NewProtect = data.OldProtect | PAGE_GUARD;
|
||||
else if (data.Type & (uint32(MemoryType::Write) | uint32(MemoryType::Execute))) //Write + Execute becomes either PAGE_GUARD or both write and execute flags removed
|
||||
data.NewProtect = permanentDep ? RemoveExecuteAccess(RemoveWriteAccess(data.OldProtect)) : data.OldProtect | PAGE_GUARD;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Process::SetMemoryBreakpoint(ptr address, ptr size, MemoryType type, bool singleshoot)
|
||||
{
|
||||
//TODO: error reporting
|
||||
|
||||
//basic checks
|
||||
if (!MemIsValidPtr(address) || !size)
|
||||
return false;
|
||||
|
||||
//check if the range is unused for any previous memory breakpoints
|
||||
auto range = Range(address, address + size - 1);
|
||||
if (memoryBreakpointRanges.find(range) != memoryBreakpointRanges.end())
|
||||
return false;
|
||||
|
||||
//change page protections
|
||||
bool success = true;
|
||||
struct TempMemoryBreakpointData
|
||||
{
|
||||
ptr addr;
|
||||
DWORD OldProtect;
|
||||
MemoryBreakpointData data;
|
||||
};
|
||||
std::vector<TempMemoryBreakpointData> breakpointData;
|
||||
{
|
||||
breakpointData.reserve(size / PAGE_SIZE);
|
||||
TempMemoryBreakpointData tempData;
|
||||
MemoryBreakpointData data;
|
||||
data.Type = uint32(type);
|
||||
auto alignedAddress = PAGE_ALIGN(address);
|
||||
for (auto page = alignedAddress; page < alignedAddress + BYTES_TO_PAGES(size); page += PAGE_SIZE)
|
||||
{
|
||||
MEMORY_BASIC_INFORMATION mbi;
|
||||
if (!VirtualQueryEx(hProcess, LPCVOID(page), &mbi, sizeof(mbi)))
|
||||
{
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
data.OldProtect = mbi.Protect;
|
||||
if (!SetNewPageProtection(page, data, type))
|
||||
{
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
tempData.addr = page;
|
||||
tempData.OldProtect = mbi.Protect;
|
||||
tempData.data = data;
|
||||
breakpointData.push_back(tempData);
|
||||
}
|
||||
}
|
||||
|
||||
//if changing the page protections failed, attempt to revert all protection changes
|
||||
if (!success)
|
||||
{
|
||||
for (const auto & page : breakpointData)
|
||||
{
|
||||
DWORD oldProtect;
|
||||
VirtualProtectEx(hProcess, LPVOID(page.addr), PAGE_SIZE, page.OldProtect, &oldProtect);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//set the page data
|
||||
for (const auto & page : breakpointData)
|
||||
memoryBreakpointPages[page.addr] = page.data;
|
||||
|
||||
//setup the breakpoint information struct
|
||||
BreakpointInfo info = {};
|
||||
info.address = address;
|
||||
info.enabled = true;
|
||||
info.singleshoot = singleshoot;
|
||||
info.type = BreakpointType::Memory;
|
||||
info.internal.memory.type = type;
|
||||
info.internal.memory.size = size;
|
||||
|
||||
//insert in the breakpoint map
|
||||
breakpoints.insert({ { info.type, info.address }, info });
|
||||
memoryBreakpointRanges.insert(range);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Process::SetMemoryBreakpoint(ptr address, ptr size, const BreakpointCallback & cbBreakpoint, MemoryType type, bool singleshoot)
|
||||
{
|
||||
//check if a callback on this address was already found
|
||||
if (breakpointCallbacks.find({ BreakpointType::Memory, address }) != breakpointCallbacks.end())
|
||||
return false;
|
||||
//set the memory breakpoint
|
||||
if (!SetMemoryBreakpoint(address, size, type, singleshoot))
|
||||
return false;
|
||||
//insert the callback
|
||||
breakpointCallbacks.insert({ { BreakpointType::Memory, address }, cbBreakpoint });
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Process::DeleteMemoryBreakpoint(ptr address)
|
||||
{
|
||||
//find the hardware breakpoint
|
||||
auto found = breakpoints.find({ BreakpointType::Hardware, address });
|
||||
if (found == breakpoints.end())
|
||||
return false;
|
||||
const auto & info = found->second;
|
||||
|
||||
//delete the memory breakpoint from the pages
|
||||
bool success = true;
|
||||
auto alignedAddress = PAGE_ALIGN(info.address);
|
||||
for (auto page = alignedAddress; page < alignedAddress + BYTES_TO_PAGES(info.internal.memory.size); page += PAGE_SIZE)
|
||||
{
|
||||
auto foundData = memoryBreakpointPages.find(page);
|
||||
if (foundData == memoryBreakpointPages.end())
|
||||
continue; //TODO: error reporting
|
||||
auto & data = foundData->second;
|
||||
DWORD Protect;
|
||||
data.Refcount--;
|
||||
if (data.Refcount)
|
||||
{
|
||||
//TODO: properly determine the new protection flag
|
||||
if (data.Type & ~uint32(info.type))
|
||||
data.NewProtect = data.OldProtect | PAGE_GUARD;
|
||||
Protect = data.NewProtect;
|
||||
}
|
||||
else
|
||||
Protect = data.OldProtect;
|
||||
DWORD oldProtect;
|
||||
if (!VirtualProtectEx(hProcess, LPVOID(page), PAGE_SIZE, Protect, &oldProtect))
|
||||
success = false;
|
||||
if (!data.Refcount)
|
||||
memoryBreakpointPages.erase(foundData);
|
||||
}
|
||||
|
||||
//delete the breakpoint from the maps
|
||||
breakpoints.erase(found);
|
||||
breakpointCallbacks.erase({ BreakpointType::Hardware, address });
|
||||
memoryBreakpointRanges.erase(Range(address, address));
|
||||
return success;
|
||||
}
|
||||
|
||||
bool Process::DeleteGenericBreakpoint(const BreakpointInfo & info)
|
||||
{
|
||||
switch (info.type)
|
||||
|
|
@ -185,7 +406,7 @@ namespace GleeBug
|
|||
case BreakpointType::Hardware:
|
||||
return DeleteHardwareBreakpoint(info.address);
|
||||
case BreakpointType::Memory:
|
||||
return false; //TODO implement this
|
||||
return DeleteMemoryBreakpoint(info.address);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,10 +8,30 @@ namespace GleeBug
|
|||
dwMainThreadId(dwMainThreadId),
|
||||
createProcessInfo(createProcessInfo),
|
||||
thread(nullptr),
|
||||
systemBreakpoint(false)
|
||||
systemBreakpoint(false),
|
||||
permanentDep(false)
|
||||
{
|
||||
for (int i = 0; i < HWBP_COUNT; i++)
|
||||
hardwareBreakpoints[i].enabled = false;
|
||||
|
||||
// DEP is disabled if lpFlagsDep == 0
|
||||
typedef BOOL(WINAPI * GETPROCESSDEPPOLICY)(
|
||||
_In_ HANDLE /*hProcess*/,
|
||||
_Out_ LPDWORD /*lpFlags*/,
|
||||
_Out_ PBOOL /*lpPermanent*/
|
||||
);
|
||||
static auto GPDP = GETPROCESSDEPPOLICY(GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "GetProcessDEPPolicy"));
|
||||
if (GPDP)
|
||||
{
|
||||
DWORD lpFlags;
|
||||
BOOL bPermanent;
|
||||
if (GPDP(hProcess, &lpFlags, &bPermanent))
|
||||
permanentDep = lpFlags && bPermanent;
|
||||
#ifdef _WIN64
|
||||
else if (GetLastError() == ERROR_NOT_SUPPORTED)
|
||||
permanentDep = true;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void Process::StepOver(const StepCallback & cbStep)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ namespace GleeBug
|
|||
|
||||
Thread* thread;
|
||||
bool systemBreakpoint;
|
||||
bool permanentDep;
|
||||
|
||||
ThreadMap threads; //DO NOT COPY THESE OBJECTS!
|
||||
DllMap dlls;
|
||||
|
|
@ -30,7 +31,8 @@ namespace GleeBug
|
|||
SoftwareBreakpointMap softwareBreakpointReferences;
|
||||
BreakpointCallbackMap breakpointCallbacks;
|
||||
BreakpointInfo hardwareBreakpoints[4];
|
||||
MemoryBreakpointSet memoryBreakpoints;
|
||||
MemoryBreakpointSet memoryBreakpointRanges;
|
||||
MemoryBreakpointMap memoryBreakpointPages;
|
||||
|
||||
/**
|
||||
\brief Constructor.
|
||||
|
|
@ -301,6 +303,61 @@ namespace GleeBug
|
|||
*/
|
||||
bool DeleteHardwareBreakpoint(ptr address);
|
||||
|
||||
/**
|
||||
\brief Sets new page protection to trigger an exception for certain memory breakpoint types.
|
||||
\param page The page address.
|
||||
\param data The current protection of the page.
|
||||
\param type The memory breakpoint type to trigger an exception for.
|
||||
\return true if it succeeds, false if it fails.
|
||||
*/
|
||||
bool SetNewPageProtection(ptr page, MemoryBreakpointData & data, MemoryType type);
|
||||
|
||||
/**
|
||||
\brief Sets a memory breakpoint.
|
||||
\param address The address to set the memory breakpoint on.
|
||||
\param size Size of the memory breakpoint (in bytes).
|
||||
\param type (Optional) The memory breakpoint type.
|
||||
\param singleshoot (Optional) True to remove the breakpoint after the first hit.
|
||||
\return true if the memory breakpoint was set, false otherwise.
|
||||
*/
|
||||
bool SetMemoryBreakpoint(ptr address, ptr size, MemoryType type = MemoryType::Access, bool singleshoot = false);
|
||||
|
||||
/**
|
||||
\brief Sets a memory breakpoint.
|
||||
\param address The address to set the memory breakpoint on.
|
||||
\param size Size of the memory breakpoint (in bytes).
|
||||
\param cbBreakpoint The breakpoint callback. Can be written using BIND1(this, MyDebugger::cb).
|
||||
\param type (Optional) The memory breakpoint type.
|
||||
\param singleshoot (Optional) True to remove the breakpoint after the first hit.
|
||||
\return true if the memory breakpoint was set, false otherwise.
|
||||
*/
|
||||
bool SetMemoryBreakpoint(ptr address, ptr size, const BreakpointCallback & cbBreakpoint, MemoryType type = MemoryType::Access, bool singleshoot = false);
|
||||
|
||||
/**
|
||||
\brief Sets a hardware breakpoint.
|
||||
\tparam T Generic type parameter. Must be a subclass of Debugger.
|
||||
\param address The address to set the hardware breakpoint on.
|
||||
\param size Size of the memory breakpoint (in bytes).
|
||||
\param debugger This pointer to a subclass of Debugger.
|
||||
\param callback Pointer to the callback. Written like: &MyDebugger::cb
|
||||
\param type (Optional) The memory breakpoint type.
|
||||
\param singleshoot (Optional) True to remove the breakpoint after the first hit.
|
||||
\return true if the memory breakpoint was set, false otherwise.
|
||||
*/
|
||||
template <typename T>
|
||||
bool SetMemoryBreakpoint(ptr address, ptr size, T* debugger, void(T::*callback)(const BreakpointInfo & info), MemoryType type = MemoryType::Access, bool singleshoot = false)
|
||||
{
|
||||
static_cast<void>(static_cast<Debugger*>(debugger));
|
||||
return SetMemoryBreakpoint(address, size, std::bind(callback, debugger, std::placeholders::_1), type, singleshoot);
|
||||
}
|
||||
|
||||
/**
|
||||
\brief Deletes a hardware breakpoint.
|
||||
\param address The address the hardware breakpoint is set on.
|
||||
\return true if the hardware breakpoint was deleted, false otherwise.
|
||||
*/
|
||||
bool DeleteMemoryBreakpoint(ptr address);
|
||||
|
||||
/**
|
||||
\brief Deletes a breakpoint.
|
||||
\param info The breakpoint information.
|
||||
|
|
|
|||
Loading…
Reference in New Issue