. Bugfix : If there is a page exception risen upon a page that is indeed being used by one of our breakpoints, then we simply restore the old page protections, singlestep and then apply the new page protection.

. exceptionAccessViolation implementation - the code looks almost equal to exceptionGuardPage except for the verifications made to try to spot on cases where the program uses unusual page permissions (in this case the program should then be granted the right to handle the self generated exception).
This commit is contained in:
_0xbadc0de 2017-02-08 16:24:04 +00:00
parent b57b716449
commit 9c4bab40ac
3 changed files with 148 additions and 75 deletions

View File

@ -172,56 +172,6 @@ namespace GleeBug
void Debugger::exceptionGuardPage(const EXCEPTION_RECORD & exceptionRecord, bool firstChance) void Debugger::exceptionGuardPage(const EXCEPTION_RECORD & exceptionRecord, bool firstChance)
{ {
/* old code ~Duncan~
char error[128] = "";
auto exceptionAddress = ptr(exceptionRecord.ExceptionInformation[1]);
//check if the exception address is directly in the range of a memory breakpoint
auto foundRange = mProcess->memoryBreakpointRanges.find(Range(exceptionAddress, exceptionAddress));
if (foundRange == mProcess->memoryBreakpointRanges.end())
{
//if not in range, check if a memory breakpoint is in the accessed page
auto foundPage = mProcess->memoryBreakpointPages.find(exceptionAddress & ~(PAGE_SIZE - 1));
if (foundPage != mProcess->memoryBreakpointPages.end())
{
//if the page contains a memory breakpoint we have to restore the old protection to correctly resume the debuggee
const auto & page = foundPage->second;
//TODO: single step and page protection changes
if (!mProcess->MemProtect(foundPage->first, PAGE_SIZE, foundPage->second.NewProtect))
{
sprintf_s(error, "MemProtect failed on 0x%p", foundPage->first);
cbInternalError(error);
}
}
return;
}
//find the breakpoint associated with the hit breakpoint range
auto foundInfo = mProcess->breakpoints.find({ BreakpointType::Memory, foundRange->first });
if (foundInfo == mProcess->breakpoints.end())
{
sprintf_s(error, "inconsistent memory breakpoint at 0x%p", exceptionAddress);
cbInternalError(error);
return;
}
//check if the memory breakpoint is disabled (meaning we shouldn't intercept the exception)
//TODO: think about what happens with multiple breakpoints in one page where only one is disabled
const auto info = foundInfo->second;
if (!info.enabled)
return;
printf("memory breakpoint: 0x%p (size: %d)\n", info.address, info.internal.memory.size);
//TODO: check if the right type is accessed (ExceptionInformation[0])
//TODO: execute the user callback (if present)
//TODO: single step and restore page protection
*/
//New code ~Marques~
/* 0xcc breakpoing
mThread->StepInternal(std::bind([this, info]()
{
//only restore the bytes if the breakpoint still exists
if (mProcess->breakpoints.find({ BreakpointType::Software, info.address }) != mProcess->breakpoints.end())
mProcess->MemWriteUnsafe(info.address, info.internal.software.newbytes, info.internal.software.size);
}));
*/
char error[128] = ""; char error[128] = "";
auto exceptionAddress = ptr(exceptionRecord.ExceptionInformation[1]); auto exceptionAddress = ptr(exceptionRecord.ExceptionInformation[1]);
@ -233,18 +183,21 @@ namespace GleeBug
auto foundPage = mProcess->memoryBreakpointPages.find(exceptionAddress & ~(PAGE_SIZE - 1)); auto foundPage = mProcess->memoryBreakpointPages.find(exceptionAddress & ~(PAGE_SIZE - 1));
if (foundPage != mProcess->memoryBreakpointPages.end()) if (foundPage != mProcess->memoryBreakpointPages.end())
{ {
mContinueStatus = DBG_CONTINUE;
//if the page contains a memory breakpoint we have to restore the old protection to correctly resume the debuggee //if the page contains a memory breakpoint we have to restore the old protection to correctly resume the debuggee
const auto & page = foundPage->second; const auto & page = foundPage->second;
const auto pBaseAddr = foundPage->first;
//TODO: single step and page protection changes //TODO: single step and page protection changes
//FIXED (marques): //FIXED
//restored *OLD* page protection settings
if (!mProcess->MemProtect(foundPage->first, PAGE_SIZE, foundPage->second.OldProtect)) if (!mProcess->MemProtect(foundPage->first, PAGE_SIZE, foundPage->second.OldProtect))
{ {
sprintf_s(error, "MemProtect failed on 0x%p", foundPage->first); sprintf_s(error, "MemProtect failed on 0x%p", foundPage->first);
cbInternalError(error); cbInternalError(error);
} }
mThread->StepInternal(std::bind([this]() //step + restore new protection to keep bp
mThread->StepInternal(std::bind([this, page, pBaseAddr]()
{ {
mProcess->MemProtect(pBaseAddr, PAGE_SIZE, page.NewProtect);
return; return;
})); }));
} }
@ -285,24 +238,13 @@ namespace GleeBug
Execute = 8 Execute = 8
*/ */
//Read but our bpx page is not bp on Read //Read but our bpx page is not bp on Read
if ((exceptionRecord.ExceptionInformation[0]) && (!(bpxPage->second.Type & 0x2))) //We shouldn't care about other stuff such as Write or Execute since these breakpoints are implemented with Access Violation.
if ((exceptionRecord.ExceptionInformation[0]==0) && (!(bpxPage->second.Type & 0x2)))
{ {
//perhaps the program generated such exception //perhaps the program generated such exception
return; return;
} }
//Exception on write but page bp information does not have bp on write
if ((exceptionRecord.ExceptionInformation[0] == 1) && (!(bpxPage->second.Type & 0x4)))
{
return;
}
//caused by data execution prevention but page bp information does not have bp on exec
if ((exceptionRecord.ExceptionInformation[0] == 8) && (!(bpxPage->second.Type & 0x8)))
{
return;
}
//generic breakpoint callback function. //generic breakpoint callback function.
cbBreakpoint(info); cbBreakpoint(info);
@ -347,7 +289,125 @@ namespace GleeBug
void Debugger::exceptionAccessViolation(const EXCEPTION_RECORD & exceptionRecord, bool firstChance) void Debugger::exceptionAccessViolation(const EXCEPTION_RECORD & exceptionRecord, bool firstChance)
{ {
//TODO: memory breakpoint code //Same shit as before
char error[128] = "";
auto exceptionAddress = ptr(exceptionRecord.ExceptionInformation[1]);
//check if the exception address is directly in the range of a memory breakpoint
auto foundRange = mProcess->memoryBreakpointRanges.find(Range(exceptionAddress, exceptionAddress));
if (foundRange == mProcess->memoryBreakpointRanges.end())
{
//if not in range, check if a memory breakpoint is in the accessed page
auto foundPage = mProcess->memoryBreakpointPages.find(exceptionAddress & ~(PAGE_SIZE - 1));
if (foundPage != mProcess->memoryBreakpointPages.end())
{
mContinueStatus = DBG_CONTINUE;
//if the page contains a memory breakpoint we have to restore the old protection to correctly resume the debuggee
const auto & page = foundPage->second;
const auto pBaseAddr = foundPage->first;
//TODO: single step and page protection changes
//FIXED
if (!mProcess->MemProtect(foundPage->first, PAGE_SIZE, foundPage->second.OldProtect))
{
sprintf_s(error, "MemProtect failed on 0x%p", foundPage->first);
cbInternalError(error);
}
//step + restore new protection to keep bp
mThread->StepInternal(std::bind([this, page, pBaseAddr]()
{
mProcess->MemProtect(pBaseAddr, PAGE_SIZE, page.NewProtect);
return;
}));
}
return;
}
//find the breakpoint associated with the hit breakpoint range
auto foundInfo = mProcess->breakpoints.find({ BreakpointType::Memory, foundRange->first });
if (foundInfo == mProcess->breakpoints.end())
{
sprintf_s(error, "inconsistent memory breakpoint at 0x%p", exceptionAddress);
cbInternalError(error);
return;
}
//check if the memory breakpoint is disabled (meaning we shouldn't intercept the exception)
//TODO: think about what happens with multiple breakpoints in one page where only one is disabled
//There is really no problem about this because enabled is a property of a range and ranges do not overlap.
const auto info = foundInfo->second;
if (!info.enabled)
return;
printf("memory breakpoint: 0x%p (size: %d)\n", info.address, info.internal.memory.size);
//TODO: check if the right type is accessed (ExceptionInformation[0])
//FIXED: Marques
auto bpxPage = mProcess->memoryBreakpointPages.find(exceptionAddress & ~(PAGE_SIZE - 1));
if (bpxPage == mProcess->memoryBreakpointPages.end())
{
sprintf_s(error, "Process::memoryBreakPointPages data structure is incosistent, should dump page at 0x%p", exceptionAddress & ~(PAGE_SIZE - 1));
cbInternalError(error);
return;
}
/*
Access = 1,
Read = 2,
Write = 4,
Execute = 8
*/
//Write but the bpx was not on write
//We shouldn't care about other stuff such as read or access because those were implemented with page guards.
if ((exceptionRecord.ExceptionInformation[0] == 1) && (!(bpxPage->second.Type & 0x4)))
{
//perhaps the program generated such exception
return;
}
//Exec but bpx was not on exec.
if ((exceptionRecord.ExceptionInformation[0] == 8) && (!(bpxPage->second.Type & 0x8)))
{
//perhaps the program generated such exception
return;
}
//generic breakpoint callback function.
cbBreakpoint(info);
//TODO: execute the user callback (if present)
//FIXED: Marques
auto bpxCb = mProcess->breakpointCallbacks.find({ BreakpointType::Memory, info.address });
if (bpxCb != mProcess->breakpointCallbacks.end())
{
bpxCb->second(info);
}
mContinueStatus = DBG_CONTINUE;
//TODO: single step and restore page protection
//FIXED:
if (!mProcess->MemProtect(bpxPage->first, PAGE_SIZE, bpxPage->second.OldProtect))
{
sprintf_s(error, "MemProtect failed on 0x%p", bpxPage->first);
cbInternalError(error);
}
//Pass info as well
auto pageAddr = bpxPage->first;
auto pageProperties = bpxPage->second;
mThread->StepInternal(std::bind([this, info, pageAddr, pageProperties]()
{
//Check if the bpx still exists
auto found_range = mProcess->memoryBreakpointRanges.find(Range(info.address, info.address));
if (found_range != mProcess->memoryBreakpointRanges.end())
{
mProcess->MemProtect(pageAddr, PAGE_SIZE, pageProperties.NewProtect);
}
return;
}));
if (foundInfo->second.singleshoot)
{
mProcess->DeleteMemoryBreakpoint(exceptionAddress);
}
} }
void Debugger::exceptionEvent(const EXCEPTION_DEBUG_INFO & exceptionInfo) void Debugger::exceptionEvent(const EXCEPTION_DEBUG_INFO & exceptionInfo)

View File

@ -199,6 +199,7 @@ namespace GleeBug
static DWORD RemoveExecuteAccess(DWORD dwAccess) static DWORD RemoveExecuteAccess(DWORD dwAccess)
{ {
//These settings can trigger access violation.
DWORD dwBase = dwAccess & 0xFF; DWORD dwBase = dwAccess & 0xFF;
DWORD dwHigh = dwAccess & 0xFFFFFF00; DWORD dwHigh = dwAccess & 0xFFFFFF00;
switch (dwBase) switch (dwBase)
@ -208,7 +209,7 @@ namespace GleeBug
case PAGE_EXECUTE_READ: case PAGE_EXECUTE_READ:
case PAGE_EXECUTE_READWRITE: case PAGE_EXECUTE_READWRITE:
case PAGE_EXECUTE_WRITECOPY: case PAGE_EXECUTE_WRITECOPY:
return dwHigh | (dwBase >> 4); 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: default:
return dwAccess; return dwAccess;
} }
@ -252,11 +253,15 @@ namespace GleeBug
else else
{ {
auto & oldData = found->second; auto & oldData = found->second;
data.Type = oldData.Type | uint32(type); data.Type = oldData.Type | uint32(type); //combines new protection
data.OldProtect = oldData.OldProtect; data.OldProtect = oldData.OldProtect; // old protection remains the same
data.Refcount = oldData.Refcount + 1; data.Refcount = oldData.Refcount + 1; //increment reference count
if (data.Type & uint32(MemoryType::Access) || data.Type & uint32(MemoryType::Read)) //Access/Read always becomes PAGE_GUARD 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 | PAGE_GUARD; {
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 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; data.NewProtect = permanentDep ? RemoveExecuteAccess(RemoveWriteAccess(data.OldProtect)) : data.OldProtect | PAGE_GUARD;
} }

View File

@ -10,8 +10,16 @@ class MyDebugger : public Debugger
protected: protected:
void cbMemoryBreakpoint(const BreakpointInfo & info) void cbMemoryBreakpoint(const BreakpointInfo & info)
{ {
unsigned char dataToExec[4];
printf("Reached memory breakpoint! GIP: 0x%p\n", printf("Reached memory breakpoint! GIP: 0x%p\n",
mRegisters->Gip()); mRegisters->Gip());
mProcess->MemReadUnsafe(mRegisters->Gip(), dataToExec, 4);
printf("\n You Bpxed Correctly: ");
for (int i = 0; i < 4; i++)
{
printf("%02X ", dataToExec[i]);
}
} }
void cbEntryBreakpoint(const BreakpointInfo & info) void cbEntryBreakpoint(const BreakpointInfo & info)
@ -24,7 +32,7 @@ protected:
auto addr = mRegisters->Esi(); auto addr = mRegisters->Esi();
#endif //_WIN64 #endif //_WIN64
printf("Addr: 0x%p\n", addr); printf("Addr: 0x%p\n", addr);
if (mProcess->SetMemoryBreakpoint(addr, 0x1000, this, &MyDebugger::cbMemoryBreakpoint, MemoryType::Access)) if (mProcess->SetMemoryBreakpoint(addr, 0x1000, this, &MyDebugger::cbMemoryBreakpoint, MemoryType::Execute))
puts("Memory breakpoint set!"); puts("Memory breakpoint set!");
else else
puts("Failed to set memory breakpoint..."); puts("Failed to set memory breakpoint...");