Side Effects of Debugger

A target program might behave differently if it is being debugged, sometimes this can be very annoying. Also, these behavior deviations can be leveraged by anti-debugging.

IsDebuggerPresent and CheckRemoteDebuggerPresent are well known APIs to tell if a program is attached by a debugger.

 0:000> uf KERNELBASE!IsDebuggerPresent KERNELBASE!IsDebuggerPresent:
7512f41b 64a118000000    mov     eax,dword ptr fs:[00000018h]
7512f421 8b4030          mov     eax,dword ptr [eax+30h]
7512f424 0fb64002        movzx   eax,byte ptr [eax+2]
7512f428 c3              ret

CloseHandle would raise an exception under a debugger, as stated by MSDN:

If the application is running under a debugger, the function will throw an exception if it receives either a handle value that is not valid or a pseudo-handle value.

Windows heap manager would use debug heap (note: this has nothing to do with the CRT Debug Heap) if a program was launched from debugger:

  • The heap manager makes use of FLG_HEAP_ENABLE_TAIL_CHECK, FLG_HEAP_ENABLE_FREE_CHECK and FLG_HEAP_VALIDATE_PARAMETERS flags in PEB!NtGlobalFlags.
  • Low Fragmentation Heap might be disabled.
  • Heap functions might throw SEH, an article covering this can be found at debuginfo.com.
  • Debug heap can be turned off by setting the environment variable _NO_DEBUG_HEAP = 1.
  • Windows debuggers has a command line option -hd which specifies that the debug heap should not be used.

OutputDebugString, we've have a dedicated topic on it.

SetUnhandledExceptionFilter, a decent article can be found at debuginfo.com. A simple detouring is to intercept IsDebugPortPresent and return FALSE.

NtSetInformationThread can be used to hide (detach) a thread from debugger.

In addition, the target program can check its own integrity or the integrity of the system.

  • PEB and TEB, this is exactly what IsDebuggerPresent has used.
  • DebugPort, this is used by the kernel (EPROCESS). NtQueryInformationProcess from NTDLL can be used to retrieve this information.
  • INT3 and thread context, as we've already demonstrated here.
  • Environment variable, parent process, process startup information.
  • Image File Execution Options.
  • Call stack and register. If the debugger makes use of func-eval, conditional breakpoints with side effects, or caused some execution flow changes, it can be detected.

A few things to mention:

  • You cannot attach a debugger to a program if the program is already attached by another debugger.
  • Attaching a debugger to a program can fail in many ways, such like loader lock, timeout and break-in thread creation failure. That is one reason why JIT debugging failed to work.
  • 64bit application cannot be debugged by a 32bit debugger, if you try to create a 64bit process from a 32bit process with debug creation flag, you always ended in failure. DebugActiveProcess would fail if a 32bit debugger tried to attach to a 64bit target.
  • Digital media application can take advantage of the windows kernel to protect itself from being debugged.
  • You should be cautious if you are debugging something that the debugger relies on (a GUI symbolic source level debugger relies on even more things), otherwise you would end up with deadlock or other strange behaviors.
  • Global Flags can affect the behavior of a program if running under a debugger (e.g. loader snaps).
  • CLR behaves very differently under a debugger (e.g. JIT compiler, GC).