From 83854fb4a782f9cba1cbe234531f3dd8aee92cf2 Mon Sep 17 00:00:00 2001 From: mrexodia Date: Fri, 19 Aug 2016 00:32:23 +0200 Subject: [PATCH] pretty much implemented setting/deleting memory breakpoints --- GleeBug/Debugger.Breakpoint.h | 17 +- GleeBug/Debugger.Global.h | 2 + GleeBug/Debugger.Process.Breakpoint.cpp | 223 +++++++++++++++++++++++- GleeBug/Debugger.Process.cpp | 22 ++- GleeBug/Debugger.Process.h | 59 ++++++- 5 files changed, 317 insertions(+), 6 deletions(-) diff --git a/GleeBug/Debugger.Breakpoint.h b/GleeBug/Debugger.Breakpoint.h index 669e7b8..a6aeb6a 100644 --- a/GleeBug/Debugger.Breakpoint.h +++ b/GleeBug/Debugger.Breakpoint.h @@ -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 \ No newline at end of file diff --git a/GleeBug/Debugger.Global.h b/GleeBug/Debugger.Global.h index 64c9d2e..c3345c6 100644 --- a/GleeBug/Debugger.Global.h +++ b/GleeBug/Debugger.Global.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 BreakpointCallbackMap; typedef std::unordered_map SoftwareBreakpointMap; typedef std::set MemoryBreakpointSet; + typedef std::unordered_map MemoryBreakpointMap; //vector typedefs typedef std::vector StepCallbackVector; diff --git a/GleeBug/Debugger.Process.Breakpoint.cpp b/GleeBug/Debugger.Process.Breakpoint.cpp index 3ce95f7..9a378e8 100644 --- a/GleeBug/Debugger.Process.Breakpoint.cpp +++ b/GleeBug/Debugger.Process.Breakpoint.cpp @@ -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 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; } diff --git a/GleeBug/Debugger.Process.cpp b/GleeBug/Debugger.Process.cpp index aecaf82..0a51fd9 100644 --- a/GleeBug/Debugger.Process.cpp +++ b/GleeBug/Debugger.Process.cpp @@ -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) diff --git a/GleeBug/Debugger.Process.h b/GleeBug/Debugger.Process.h index f91e3e3..79eba6a 100644 --- a/GleeBug/Debugger.Process.h +++ b/GleeBug/Debugger.Process.h @@ -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 + bool SetMemoryBreakpoint(ptr address, ptr size, T* debugger, void(T::*callback)(const BreakpointInfo & info), MemoryType type = MemoryType::Access, bool singleshoot = false) + { + static_cast(static_cast(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.