-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge unmerged commits from pull request #131 from LordNoteworthy/pag…
…e-exception-debug-checks Added check to catch CE page exception breakpoints
- Loading branch information
Showing
6 changed files
with
157 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
#include "stdafx.h" | ||
|
||
#ifdef _DEBUG | ||
#define OutputDebugStringDbgOnly(S) OutputDebugString(S) | ||
#else | ||
#define OutputDebugStringDbgOnly(S) do {} while(0); | ||
#endif | ||
|
||
std::vector<PVOID> executablePages = {}; | ||
|
||
void PageExceptionInitialEnum() | ||
{ | ||
SYSTEM_INFO sysInfo; | ||
GetSystemInfo(&sysInfo); | ||
size_t pageSize = sysInfo.dwPageSize; | ||
|
||
HMODULE hMainModule; | ||
MODULEINFO moduleInfo; | ||
|
||
MEMORY_BASIC_INFORMATION memInfo = { 0 }; | ||
|
||
// Get the main module handle from an address stored within it (pointer to this method) | ||
if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCWSTR)PageExceptionBreakpointCheck, &hMainModule) == TRUE) | ||
{ | ||
// Get information about the main module (we want to know the size of it) | ||
if (GetModuleInformation(GetCurrentProcess(), hMainModule, &moduleInfo, sizeof(MODULEINFO)) == TRUE) | ||
{ | ||
// cast the module to a pointer | ||
unsigned char* module = static_cast<unsigned char*>(moduleInfo.lpBaseOfDll); | ||
for (size_t ofs = 0; ofs < moduleInfo.SizeOfImage; ofs += pageSize) | ||
{ | ||
if (VirtualQuery(module + ofs, &memInfo, sizeof(MEMORY_BASIC_INFORMATION)) >= sizeof(MEMORY_BASIC_INFORMATION)) | ||
{ | ||
if ((memInfo.Protect & PAGE_EXECUTE) == PAGE_EXECUTE || | ||
(memInfo.Protect & PAGE_EXECUTE_READ) == PAGE_EXECUTE_READ || | ||
(memInfo.Protect & PAGE_EXECUTE_WRITECOPY) == PAGE_EXECUTE_WRITECOPY || | ||
(memInfo.Protect & PAGE_EXECUTE_READWRITE) == PAGE_EXECUTE_READWRITE) | ||
{ | ||
executablePages.push_back(module + ofs); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
BOOL PageExceptionBreakpointCheck() | ||
{ | ||
SYSTEM_INFO sysInfo; | ||
GetSystemInfo(&sysInfo); | ||
size_t pageSize = sysInfo.dwPageSize; | ||
|
||
HMODULE hMainModule; | ||
MODULEINFO moduleInfo; | ||
|
||
MEMORY_BASIC_INFORMATION memInfo = { 0 }; | ||
|
||
wchar_t buffer[512]; | ||
|
||
// first we check if any of the pages are executable+guard or noaccess | ||
|
||
// Get the main module handle from an address stored within it (pointer to this method) | ||
if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCWSTR)PageExceptionBreakpointCheck, &hMainModule) == TRUE) | ||
{ | ||
// Get information about the main module (we want to know the size of it) | ||
if (GetModuleInformation(GetCurrentProcess(), hMainModule, &moduleInfo, sizeof(MODULEINFO)) == TRUE) | ||
{ | ||
// cast the module to a pointer | ||
unsigned char* module = static_cast<unsigned char*>(moduleInfo.lpBaseOfDll); | ||
for (size_t ofs = 0; ofs < moduleInfo.SizeOfImage; ofs += pageSize) | ||
{ | ||
SecureZeroMemory(buffer, 512); | ||
wsprintf(buffer, L"Scanning %p... ", module + ofs); | ||
OutputDebugStringDbgOnly(buffer); | ||
if (VirtualQuery(module + ofs, &memInfo, sizeof(MEMORY_BASIC_INFORMATION)) >= sizeof(MEMORY_BASIC_INFORMATION)) | ||
{ | ||
if (memInfo.AllocationProtect == 0) | ||
OutputDebugStringDbgOnly(L"^ AllocationProtect is zero. Potential shenanigans."); | ||
if (memInfo.Protect == 0) | ||
OutputDebugStringDbgOnly(L"^ Protect is zero. Potential shenanigans."); | ||
|
||
if ((memInfo.Protect & PAGE_EXECUTE) == PAGE_EXECUTE || | ||
(memInfo.Protect & PAGE_EXECUTE_READ) == PAGE_EXECUTE_READ || | ||
(memInfo.Protect & PAGE_EXECUTE_WRITECOPY) == PAGE_EXECUTE_WRITECOPY || | ||
(memInfo.Protect & PAGE_EXECUTE_READWRITE) == PAGE_EXECUTE_READWRITE) | ||
{ | ||
// this is an executable page | ||
OutputDebugStringDbgOnly(L"^ is executable."); | ||
|
||
if ((memInfo.Protect & PAGE_GUARD) == PAGE_GUARD || | ||
(memInfo.AllocationProtect & PAGE_GUARD) == PAGE_GUARD) | ||
{ | ||
// this is an executable guard page, page exception debugging detected | ||
OutputDebugStringDbgOnly(L"^ is guard page !!!!!!"); | ||
return TRUE; | ||
} | ||
} | ||
|
||
if ((memInfo.Protect & PAGE_NOACCESS) == PAGE_NOACCESS) | ||
{ | ||
// this is a NOACCESS page, which shouldn't exist here (alternative way to set page exception BPs) | ||
OutputDebugStringDbgOnly(L"^ is NOACCESS !!!!!!!"); | ||
return TRUE; | ||
} | ||
} | ||
else OutputDebugStringDbgOnly(L"^ FAILED!"); | ||
} | ||
} | ||
|
||
OutputDebugStringDbgOnly(L"Moving on to delta check..."); | ||
|
||
for (PVOID page : executablePages) | ||
{ | ||
SecureZeroMemory(buffer, 512); | ||
wsprintf(buffer, L"Scanning delta for %p... ", page); | ||
OutputDebugStringDbgOnly(buffer); | ||
|
||
if (VirtualQuery(page, &memInfo, sizeof(MEMORY_BASIC_INFORMATION)) >= sizeof(MEMORY_BASIC_INFORMATION)) | ||
{ | ||
if (memInfo.AllocationProtect == 0) | ||
OutputDebugStringDbgOnly(L"^ AllocationProtect is zero. Potential shenanigans."); | ||
if (memInfo.Protect == 0) | ||
OutputDebugStringDbgOnly(L"^ Protect is zero. Potential shenanigans."); | ||
|
||
if (!((memInfo.Protect & PAGE_EXECUTE) == PAGE_EXECUTE || | ||
(memInfo.Protect & PAGE_EXECUTE_READ) == PAGE_EXECUTE_READ || | ||
(memInfo.Protect & PAGE_EXECUTE_WRITECOPY) == PAGE_EXECUTE_WRITECOPY || | ||
(memInfo.Protect & PAGE_EXECUTE_READWRITE) == PAGE_EXECUTE_READWRITE)) | ||
{ | ||
// page was executable, now isn't! | ||
OutputDebugStringDbgOnly(L"^ was executable, now isn't !!!!!!"); | ||
return TRUE; | ||
} | ||
} | ||
} | ||
} | ||
|
||
return FALSE; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
#pragma once | ||
|
||
void PageExceptionInitialEnum(); | ||
BOOL PageExceptionBreakpointCheck(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
eb59667
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should it detect cheat engine? didn't got it work yet
i did pause after PageExceptionInitialEnum(); then i attached debugger and it didn't detect it
eb59667
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You have to set the breakpoint type to page exceptions inside CE and set a breakpoint somewhere in the main module's address space.
eb59667
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah ok gonna try that
eb59667
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, almoust working!
"Find out what accesses this address" it detects
but "Find out what writes this address" it not detecting
is it possible detect also setter breakpoints?
eb59667
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check is designed for regular breakpoints in the main application code (hit Memory View, set a BP on code inside the main module), not for data breakpoints on the heap.
The "find out what accesses this" feature is probably being detected because CE inserts transient breakpoints in order to capture the thread context, and the address that reads that memory is within the main module's code. The "find out what writes to this" probably doesn't catch anything because the code that wrote to it was in another library (e.g. the print API) or CE is using an alternative way to catch writes (e.g. the page write watch APIs).
A potential future improvement is to also do the same checks on pages assigned to the core APIs loaded into the process (ntdll, kernel32, user32, psapi, etc.), but that may come at a cost of having false positives.
At the moment we cannot do this check on the dynamic heap pages because the heap may contain guard pages and noaccess pages during regular operation (e.g. the stack has guard pages to trigger stack expansion, stack smashing protection also uses noaccess/guard pages) and there are other hazards such as mandatory CFG which may cause process termination if we touch the wrong heap memory.
eb59667
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok big thanks for implementing this!