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
|
enum class MemoryType
|
||||||
{
|
{
|
||||||
Acess,
|
Access = 1,
|
||||||
Write,
|
Write = 2,
|
||||||
Execute
|
Execute = 4
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -88,6 +88,17 @@ namespace GleeBug
|
||||||
BreakpointType type;
|
BreakpointType type;
|
||||||
BreakpointInternalInfo internal;
|
BreakpointInternalInfo internal;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Structure for memory breakpoint management.
|
||||||
|
*/
|
||||||
|
struct MemoryBreakpointData
|
||||||
|
{
|
||||||
|
uint32 Refcount;
|
||||||
|
uint32 Type;
|
||||||
|
DWORD OldProtect;
|
||||||
|
DWORD NewProtect;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //DEBUGGER_BREAKPOINT_H
|
#endif //DEBUGGER_BREAKPOINT_H
|
||||||
|
|
@ -16,6 +16,7 @@ namespace GleeBug
|
||||||
class Thread;
|
class Thread;
|
||||||
enum class BreakpointType;
|
enum class BreakpointType;
|
||||||
struct BreakpointInfo;
|
struct BreakpointInfo;
|
||||||
|
struct MemoryBreakpointData;
|
||||||
|
|
||||||
//constants
|
//constants
|
||||||
const int HWBP_COUNT = GLEEBUG_HWBP_COUNT;
|
const int HWBP_COUNT = GLEEBUG_HWBP_COUNT;
|
||||||
|
|
@ -36,6 +37,7 @@ namespace GleeBug
|
||||||
typedef std::map<BreakpointKey, BreakpointCallback> BreakpointCallbackMap;
|
typedef std::map<BreakpointKey, BreakpointCallback> BreakpointCallbackMap;
|
||||||
typedef std::unordered_map<ptr, BreakpointMap::iterator> SoftwareBreakpointMap;
|
typedef std::unordered_map<ptr, BreakpointMap::iterator> SoftwareBreakpointMap;
|
||||||
typedef std::set<Range, RangeCompare> MemoryBreakpointSet;
|
typedef std::set<Range, RangeCompare> MemoryBreakpointSet;
|
||||||
|
typedef std::unordered_map<ptr, MemoryBreakpointData> MemoryBreakpointMap;
|
||||||
|
|
||||||
//vector typedefs
|
//vector typedefs
|
||||||
typedef std::vector<StepCallback> StepCallbackVector;
|
typedef std::vector<StepCallback> StepCallbackVector;
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,227 @@ namespace GleeBug
|
||||||
return success;
|
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)
|
bool Process::DeleteGenericBreakpoint(const BreakpointInfo & info)
|
||||||
{
|
{
|
||||||
switch (info.type)
|
switch (info.type)
|
||||||
|
|
@ -185,7 +406,7 @@ namespace GleeBug
|
||||||
case BreakpointType::Hardware:
|
case BreakpointType::Hardware:
|
||||||
return DeleteHardwareBreakpoint(info.address);
|
return DeleteHardwareBreakpoint(info.address);
|
||||||
case BreakpointType::Memory:
|
case BreakpointType::Memory:
|
||||||
return false; //TODO implement this
|
return DeleteMemoryBreakpoint(info.address);
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,30 @@ namespace GleeBug
|
||||||
dwMainThreadId(dwMainThreadId),
|
dwMainThreadId(dwMainThreadId),
|
||||||
createProcessInfo(createProcessInfo),
|
createProcessInfo(createProcessInfo),
|
||||||
thread(nullptr),
|
thread(nullptr),
|
||||||
systemBreakpoint(false)
|
systemBreakpoint(false),
|
||||||
|
permanentDep(false)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < HWBP_COUNT; i++)
|
for (int i = 0; i < HWBP_COUNT; i++)
|
||||||
hardwareBreakpoints[i].enabled = false;
|
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)
|
void Process::StepOver(const StepCallback & cbStep)
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ namespace GleeBug
|
||||||
|
|
||||||
Thread* thread;
|
Thread* thread;
|
||||||
bool systemBreakpoint;
|
bool systemBreakpoint;
|
||||||
|
bool permanentDep;
|
||||||
|
|
||||||
ThreadMap threads; //DO NOT COPY THESE OBJECTS!
|
ThreadMap threads; //DO NOT COPY THESE OBJECTS!
|
||||||
DllMap dlls;
|
DllMap dlls;
|
||||||
|
|
@ -30,7 +31,8 @@ namespace GleeBug
|
||||||
SoftwareBreakpointMap softwareBreakpointReferences;
|
SoftwareBreakpointMap softwareBreakpointReferences;
|
||||||
BreakpointCallbackMap breakpointCallbacks;
|
BreakpointCallbackMap breakpointCallbacks;
|
||||||
BreakpointInfo hardwareBreakpoints[4];
|
BreakpointInfo hardwareBreakpoints[4];
|
||||||
MemoryBreakpointSet memoryBreakpoints;
|
MemoryBreakpointSet memoryBreakpointRanges;
|
||||||
|
MemoryBreakpointMap memoryBreakpointPages;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
\brief Constructor.
|
\brief Constructor.
|
||||||
|
|
@ -301,6 +303,61 @@ namespace GleeBug
|
||||||
*/
|
*/
|
||||||
bool DeleteHardwareBreakpoint(ptr address);
|
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.
|
\brief Deletes a breakpoint.
|
||||||
\param info The breakpoint information.
|
\param info The breakpoint information.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue