Continuing my series on anti-debug technique and how to defeat them, today we have a mach exception ports based trick on macOS.
For those unfamiliar with how the macOS flavor of Unix does things, these ports are used by debuggers to handle exceptions like breakpoints and bad-access. With this knowledge, a piece of software that does not want to be debugged, like a piece of malware trying to prevent analysis, can check if these ports are open and do something else in response, like shutdown.
The code for mach exception handling is fairly complex and poorly documented, but I’ve created a fairly simple example of debugger-detection code so we can dive right in.
NOTE: All of these examples assume compilation for x86_64, the default now for years and soon may be the only option. Also, all bypasses will be done in-debugger, without patching the binary itself (which may not be feasible when dealing with packed code).
// clang -o main main.c
Now when run directly, no debugger is detected:
But when run with LLDB, a debugger is detected:
$ lldb main
Alright, so how do we defeat this? There are a few way, and the best approach may depend on the code doing the detection, but in this simple example we can simply mask out all the exceptions by changing the second argument (register
task_get_exception_ports to a
0 in a debugger.
Allow me to demonstrate just how easy that is.
$ lldb main
Easy, even easier than the better-known
sysctl technique, once you know what’s going on. If this function gets called a lot, you will probably want to automate it in your debugger of choice, but I leave that as an exercise for the reader.
You may have noticed in the comments that it was formerly possible to use
EXC_MASK_ALL (which actually does not include “all”) instead of listing the exceptions individually. This is no longer possible since the newer
EXC_MASK_GUARD (along with
EXC_MASK_CORPSE_NOTIFY) ports are present outside a debugger. You can find a list of all of these constants in the XNU header file
Stay tuned for more on mach task ports, because next up we will have force-detaching of debuggers with the mach exception ports API.