mirror of https://github.com/x64dbg/GleeBug
427 lines
16 KiB
C++
427 lines
16 KiB
C++
#include "Debugger.Process.h"
|
|
|
|
namespace GleeBug
|
|
{
|
|
bool Process::SetBreakpoint(ptr address, bool singleshoot, SoftwareType type)
|
|
{
|
|
//check the address
|
|
if(!MemIsValidPtr(address) ||
|
|
breakpoints.find({ BreakpointType::Software, address }) != breakpoints.end())
|
|
return false;
|
|
|
|
//setup the breakpoint information struct
|
|
BreakpointInfo info = {};
|
|
info.address = address;
|
|
info.singleshoot = singleshoot;
|
|
info.type = BreakpointType::Software;
|
|
|
|
//determine breakpoint byte and size from the type
|
|
switch(type)
|
|
{
|
|
case SoftwareType::ShortInt3:
|
|
info.internal.software.newbytes[0] = 0xCC;
|
|
info.internal.software.size = 1;
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
//read/write the breakpoint
|
|
if(!MemReadUnsafe(address, info.internal.software.oldbytes, info.internal.software.size))
|
|
return false;
|
|
|
|
if(!MemWriteUnsafe(address, info.internal.software.newbytes, info.internal.software.size))
|
|
return false;
|
|
FlushInstructionCache(hProcess, nullptr, 0);
|
|
|
|
//insert in the breakpoint map
|
|
auto itr = breakpoints.insert({ { info.type, info.address }, info });
|
|
softwareBreakpointReferences[info.address] = itr.first;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Process::SetBreakpoint(ptr address, const BreakpointCallback & cbBreakpoint, bool singleshoot, SoftwareType type)
|
|
{
|
|
//check if a callback on this address was already found
|
|
if(breakpointCallbacks.find({ BreakpointType::Software, address }) != breakpointCallbacks.end())
|
|
return false;
|
|
//set the breakpoint
|
|
if(!SetBreakpoint(address, singleshoot, type))
|
|
return false;
|
|
//insert the callback
|
|
breakpointCallbacks.insert({ { BreakpointType::Software, address }, cbBreakpoint });
|
|
return true;
|
|
}
|
|
|
|
bool Process::DeleteBreakpoint(ptr address)
|
|
{
|
|
//find the breakpoint
|
|
auto found = breakpoints.find({ BreakpointType::Software, address });
|
|
if(found == breakpoints.end())
|
|
return false;
|
|
const auto & info = found->second;
|
|
|
|
//restore the breakpoint bytes
|
|
if(!MemWriteUnsafe(address, info.internal.software.oldbytes, info.internal.software.size))
|
|
return false;
|
|
FlushInstructionCache(hProcess, nullptr, 0);
|
|
|
|
recentlyDeletedSwbp.push_back(address);
|
|
|
|
//remove the breakpoint from the maps
|
|
softwareBreakpointReferences.erase(info.address);
|
|
breakpoints.erase(found);
|
|
breakpointCallbacks.erase({ BreakpointType::Software, address });
|
|
return true;
|
|
}
|
|
|
|
bool Process::GetFreeHardwareBreakpointSlot(HardwareSlot & slot) const
|
|
{
|
|
//find a free hardware breakpoint slot
|
|
for(int i = 0; i < HWBP_COUNT; i++)
|
|
{
|
|
if(!hardwareBreakpoints[i].internal.hardware.enabled)
|
|
{
|
|
slot = HardwareSlot(i);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Process::SetHardwareBreakpoint(ptr address, HardwareSlot slot, HardwareType type, HardwareSize size, bool singleshoot)
|
|
{
|
|
//check the address
|
|
if(!MemIsValidPtr(address) ||
|
|
breakpoints.find({ BreakpointType::Hardware, address }) != breakpoints.end())
|
|
return false;
|
|
|
|
//attempt to set the hardware breakpoint in every thread
|
|
bool success = true;
|
|
for(auto & thread : threads)
|
|
{
|
|
if(!thread.second->SetHardwareBreakpoint(address, slot, type, size))
|
|
{
|
|
success = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//if setting failed, unset all
|
|
if(!success)
|
|
{
|
|
for(auto & thread : threads)
|
|
thread.second->DeleteHardwareBreakpoint(slot);
|
|
return false;
|
|
}
|
|
|
|
//setup the breakpoint information struct
|
|
BreakpointInfo info = {};
|
|
info.address = address;
|
|
info.singleshoot = singleshoot;
|
|
info.type = BreakpointType::Hardware;
|
|
info.internal.hardware.slot = slot;
|
|
info.internal.hardware.type = type;
|
|
info.internal.hardware.size = size;
|
|
info.internal.hardware.enabled = true;
|
|
|
|
//insert in the breakpoint map
|
|
breakpoints.insert({ { info.type, info.address }, info });
|
|
|
|
//insert in the hardware breakpoint cache
|
|
hardwareBreakpoints[int(slot)] = info;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Process::SetHardwareBreakpoint(ptr address, HardwareSlot slot, const BreakpointCallback & cbBreakpoint, HardwareType type, HardwareSize size, bool singleshoot)
|
|
{
|
|
//check if a callback on this address was already found
|
|
if(breakpointCallbacks.find({ BreakpointType::Hardware, address }) != breakpointCallbacks.end())
|
|
return false;
|
|
//set the hardware breakpoint
|
|
if(!SetHardwareBreakpoint(address, slot, type, size, singleshoot))
|
|
return false;
|
|
//insert the callback
|
|
breakpointCallbacks.insert({ { BreakpointType::Hardware, address }, cbBreakpoint });
|
|
return true;
|
|
}
|
|
|
|
bool Process::DeleteHardwareBreakpoint(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 hardware breakpoint from the internal buffer
|
|
hardwareBreakpoints[int(info.internal.hardware.slot)].internal.hardware.enabled = false;
|
|
|
|
//delete the hardware breakpoint from the registers
|
|
bool success = true;
|
|
for(auto & thread : threads)
|
|
{
|
|
if(!thread.second->DeleteHardwareBreakpoint(info.internal.hardware.slot))
|
|
success = false;
|
|
}
|
|
|
|
//delete the breakpoint from the maps
|
|
breakpoints.erase(found);
|
|
breakpointCallbacks.erase({ BreakpointType::Hardware, address });
|
|
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)
|
|
{
|
|
//These settings can trigger access violation.
|
|
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); //This removes execute in deed; https://msdn.microsoft.com/en-us/library/windows/desktop/aa366786(v=vs.85).aspx - 0x1337 tricks
|
|
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)
|
|
{
|
|
DPRINTF();
|
|
//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:
|
|
case MemoryType::Read:
|
|
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); //combines new protection
|
|
data.OldProtect = oldData.OldProtect; // old protection remains the same
|
|
data.Refcount = oldData.Refcount + 1; //increment reference count
|
|
if(oldData.Type == uint32(type)) // Edge case for when you need to set a mem bpx on a same page with the same type, you just leave newProtect = OldProtect.
|
|
{
|
|
data.NewProtect = data.OldProtect;
|
|
}
|
|
else if(data.Type & uint32(MemoryType::Access) || data.Type & uint32(MemoryType::Read)) // Access/Read always becomes PAGE_GUARD ; This page cannot access or Read?
|
|
data.NewProtect = data.OldProtect | PAGE_GUARD; //as before
|
|
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;
|
|
}
|
|
|
|
dprintf("SetNewPageProtection(%p, %X)\n", page, data.NewProtect);
|
|
return MemProtect(page, PAGE_SIZE, data.NewProtect);
|
|
}
|
|
|
|
bool Process::SetMemoryBreakpoint(ptr address, ptr size, MemoryType type, bool singleshoot)
|
|
{
|
|
DPRINTF();
|
|
dprintf("SetMemoryBreakpoint(%p, %p, %d, %d)\n", address, size, type, 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 + ROUND_TO_PAGES(size); page += PAGE_SIZE)
|
|
{
|
|
MEMORY_BASIC_INFORMATION mbi;
|
|
if(!VirtualQueryEx(hProcess, LPCVOID(page), &mbi, sizeof(mbi)))
|
|
{
|
|
success = false;
|
|
dprintf("!VirtualQueryEx\n");
|
|
break;
|
|
}
|
|
data.OldProtect = mbi.Protect;
|
|
if(!SetNewPageProtection(page, data, type))
|
|
{
|
|
success = false;
|
|
dprintf("!SetNewPageProtection\n");
|
|
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)
|
|
MemProtect(page.addr, PAGE_SIZE, page.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.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 memory breakpoint range
|
|
auto range = memoryBreakpointRanges.find(Range(address, address));
|
|
if(range == memoryBreakpointRanges.end())
|
|
return false;
|
|
|
|
//find the memory breakpoint
|
|
auto found = breakpoints.find({ BreakpointType::Memory, range->first });
|
|
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 + ROUND_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
|
|
//Are there any other protections left?
|
|
//If so add the guard
|
|
if(data.Type & ~uint32(info.internal.memory.type))
|
|
data.NewProtect = data.OldProtect | PAGE_GUARD;
|
|
Protect = data.NewProtect;
|
|
}
|
|
else
|
|
Protect = data.OldProtect;
|
|
if(!MemProtect(page, PAGE_SIZE, Protect))
|
|
success = false;
|
|
if(!data.Refcount)
|
|
memoryBreakpointPages.erase(foundData);
|
|
}
|
|
|
|
//delete the breakpoint from the maps
|
|
breakpoints.erase(found);
|
|
breakpointCallbacks.erase({ BreakpointType::Memory, address });
|
|
memoryBreakpointRanges.erase(Range(address, address));
|
|
return success;
|
|
}
|
|
|
|
bool Process::DeleteGenericBreakpoint(const BreakpointInfo & info)
|
|
{
|
|
switch(info.type)
|
|
{
|
|
case BreakpointType::Software:
|
|
return DeleteBreakpoint(info.address);
|
|
case BreakpointType::Hardware:
|
|
return DeleteHardwareBreakpoint(info.address);
|
|
case BreakpointType::Memory:
|
|
return DeleteMemoryBreakpoint(info.address);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
}; |