How can your program detect if it’s being attached to a debugger? One approach is detecting if a mach exception handler is configured. Another more well known approach is detecting the usage of
ptrace. I will discuss the merits and disadvantages of both of these.
This question is easily neglected, but is necessary to answer so we understand what we are and aren’t detecting.
For our purposes, an inferior (the process being debugged) is attached to a debugger if the debugger catches unhandled exceptions that occur inside the inferior.
For example, when an inferior crashes or hits a breakpoint, a typical debugger may catch the exception and handle it by displaying the inferior’s state to the user while keeping the process suspended.
On the other hand, an example that falls short of attaching is reading/writing to a process’ virtual memory, which can even include overwriting code. We will not be discussing approaches that detect this type of behavior.
Now that we settled for a definition, here is code that allows a program to detect if it is being attached via a mach exception handler1:
static bool amIAnInferior(void)
mach_msg_type_number_t count = 0;
exception_mask_t mask = EXC_MASK_ALL & ~(EXC_MASK_RESOURCE | EXC_MASK_GUARD);
kern_return_t result = task_get_exception_ports(mach_task_self(), mask, masks, &count, ports, behaviors, flavors);
if (result == KERN_SUCCESS)
for (mach_msg_type_number_t portIndex = 0; portIndex < count; portIndex++)
We iterate through all ports an exception may use (
EXC_BAD_ACCESS, etc) and see if any of the ports are valid. If any of them are, then we know we are attached through a mach exception handler.
(You may wonder why the
EXC_MASK_GUARD exceptions are ignored; they are not supported)
ptrace is a system call found in OS X and other Unix-like systems that provides debugging capabilities. A common misconception on OS X is that a debugger needs to use
ptrace for attaching to an inferior, which leads people to solely rely on detection code as shown in Apple’s Technical Q&A QA1361.
Such code is ineffective against debuggers that choose not to use
ptrace though. Full-featured debuggers like LLDB and GDB tend to make use of
ptrace, but most of the legwork needed for writing a capable debugger does not use this system call. Bit Slicer is just one example of a debugger that can set up exception handling for breakpoints without being caught by
Some even believe ptrace is useless on our platform. Why would debuggers on OS X use
ptrace then if that were not the case? Two reasons I can think of are2:
PT_TRACE_ME stops the inferior before executing its first instruction when a new process is spawned.
PT_SIGEXC allows all BSD signals to be received as mach exceptions.
Useful, but otherwise
ptrace is not very interesting. Still, detecting if a debugger is attached via
ptrace is not a bad idea because a debugger can use
ptrace to trace execution without setting up a mach exception handler. However, detecting the usage of
ptrace alone does not provide much coverage.
Until now, I have avoided discussing anti-debugging measures that prevent debuggers from attaching. Although several measures exist, I am interested in discussing just two of them for the sake of our discussion:
ptrace(PT_DENY_ATTACH, 0, NULL, 0). This denies any debugger that uses
ptrace for attaching, but it won’t catch debuggers that don’t use
amIAnInferior() and responding accordingly. The downside is it may not catch
ptrace tracing or single stepping. This approach may also require polling.
Note the user can always break and return from your
ptrace call, no-op your detection code in the executable, in memory, or wherever else. No program is truly safe from being debugged; we can only inconvenience the user’s efforts.
To sum up:
ptrace flag has been set.
ptrace is not required for writing an capable debugger, and shouldn’t be used alone for detecting if a debugger is attached.
amIAnInferior() can be used for preventing debugger attachments.
Code is partially derived from Secuinside 2012 Playing with OS X. ↩︎
ptrace references in lldb’s ProcessMacOSX.cpp. Retrieved 30 January 2016. ↩︎