Tuesday, November 20, 2007

The Mysterious Breakpoint

(Or how even Microsoft programmers sometimes forget to clean-up..)

While working on a piece of software that relied on a ActiveX control, I regularly found my debugger stopping at an address somewhere in memory that was not part of the program. First I tried to find out where the bug was, then I found that just continuing the program worked fine, and that there were apparently no side-effects. But it kept stopping there, no matter how much of the debugger features I disabled. So I decided to get into it a bit deeper and found out it always stopped at an address named 'DbgBreakPoint' in module NTDLL.DLL. It looked like it stopped on a breakpoint, but not one that I had set. And the 'Disable all breakpoints' option in the Borland C++ Builder debugger did not make any difference. It really looked like Microsoft forgot to remove a breakpoint from one of the most important DLL's in Windows. A bit of Googling made clear that this was indeed the case, and also that it was not easy to get around it. Patching NTDLL.DLL appears impossible since it is one of the most 'protected' core-pieces of Windows. If you try to modify it, Windows will just 'restore' it on start-up. And if you are clever and modify the backup copy it uses for this restoration, Windows just won't start at all...
So the only way around seems to 'patch' the DLL in memory after it is loaded by your program by overwriting the memory location containing the breakpoint with a 'NOP' instruction. Here is the function to do it:

void PatchINT3()
{
unsigned char NOP=0x90;
void* pNTDLL;
DWORD BytesWritten ;
FARPROC Address;
if (Win32Platform != VER_PLATFORM_WIN32_NT){ return; }
pNTDLL = GetModuleHandle("NTDLL.DLL");
if (pNTDLL == 0) {return;}
Address = GetProcAddress(pNTDLL, (LPCSTR)"DbgBreakPoint");
if (Address == NULL){ return;}
unsigned char Byte= *(char *)Address;
if (Byte != 0xCC) {return;}
try

{
if (WriteProcessMemory(GetCurrentProcess(), Address, &NOP, 1, &BytesWritten))
{
if(BytesWritten == 1)
{
FlushInstructionCache(GetCurrentProcess(), Address, 1);
}
}
}catch(EAccessViolation &E){}
//Do not panic if you see an EAccessViolation here, it is perfectly harmless!

}

I suppose anyone who got to here has enough knowledge of Windows programming to see how it works :-)
And so, by calling PatchINT3() at the start of the program the breakpoint is erased and won't show up again.
(Thanks to Peter Morris at HowToDoThings , who wrote the Delphi version that I translated here)