Every reverse engineer who handles software for macOS knows about ptrace(PT_DENY_ATTACH, 0, 0, 0), the infamous kernel-enforced anti-tracing DRM feature added to OS X years back (somewhere around Leopard) and most-notably used in iTunes. There are plenty of resources out there on how to bypass the common use of this feature, ranging from using a debugger to loading up a custom kernel-extension, but clever hackers have found new ways to abuse this feature to try to prevent researchers from debugging their malicious code.
I debated publishing this for a while as this information could misused, but since these techniques are being used in malware in the wild, I think it’s important to document how to defeat them.
NOTE: All of these examples assume compilation for x86_64, the default now for years. Also, all bypasses will be done in-debugger, without patching the binary itself (which may not be feasible when dealing with packed code).
Common Implementation:
To start off, let’s cover the common implementation.
1 2 3 4 5 6 7 8 9 10 11 12
// clang -o main main.c #include<stdio.h> #include<stdlib.h> #include<sys/types.h> #include<sys/ptrace.h> #include<unistd.h>
Now if you compile and run this code both without and with a debugger, you will see that when run under a debugger, it crashes.
1 2 3 4 5 6 7 8 9
$ ./main PT_DENY_ATTACH $ lldb main (lldb) target create "main" Current executable set to 'main' (x86_64). (lldb) r Process 5672 launched: '.../main' (x86_64) Process 5672 exited with status = 45 (0x0000002d) (lldb)
The message Process # exited with status = 45 (0x0000002d) is usually a tell-tale sign that the debug target is using PT_DENY_ATTACH.
Now defeating this trick is pretty easy, just set a breakpoint on the ptrace symbol, and change the argument value to something else, like NULL. In x86_64, the PT_DENY_ATTACH value (0x1f) is stored in the rdi register.
(lldb) b 0x100000f66 Breakpoint 1: address = 0x0000000100000f66 (lldb) r Process 6690 launched: '.../main' (x86_64) Process 6690 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 frame #0: 0x0000000100000f66 main`main + 38 main`main: -> 0x100000f66 <+38>: syscall 0x100000f68 <+40>: popq %rdi 0x100000f69 <+41>: popq %rax 0x100000f6a <+42>: leaq 0x35(%rip), %rdi ; "PT_DENY_ATTACH\n" Target 0: (main) stopped. (lldb) reg w rax 0 (lldb) c Process 6690 resuming PT_DENY_ATTACH Process 6690 exited with status = 0 (0x00000000) (lldb)
Success! Changing the rax register to 0 does the trick.
Alternately, a kernel extension that disables the feature in the kernel could be employed to defeat this.
Detecting if PT_DENY_ATTACH worked:
Now for the most-dastardly ptrace-based technique I’ve seen, detecting if ptrace(PT_DENY_ATTACH, 0, 0, 0) actually worked, and changing what the code does based on this. It’s a little-known fact that attempting to attach to a process that has called ptrace(PT_DENY_ATTACH, 0, 0, 0) results in a catch-able segmentation fault, so code that first requests no attaching, can then attempt to attach to itself to see if that worked.
Additionally as you may have guessed, doing this enables the code to detect if a PT_DENY_ATTACH disabling kernel extension was loaded, forcing us to defeat it manually, which is one of the main reason this technique is so dastardly.
if (!deny_attach_successful) { printf("FAILURE\n"); return1; }
printf("SUCCESS\n"); return0; }
Getting past this code is actually kinda tricky. Basically, you would have to do the following:
Breakpoint ptrace.
Disable the first ptrace call.
Disable the second ptrace call.
Manually send a SIGSEGV signal after the second ptrace call.
Trouble is, LLDB cannot actually send a SIGSEGV signal, another reason this technique is a bit of work to beat. I’m not sure if this is an issue unique to LLDB or if other debuggers on macOS suffer from this limitation, but never-the-less we can still defeat this sneaky trick by manually calling the signal handler. So our new plan of attack is:
Alright, now you know how to defeat all the different variants of ptrace-based anti-debugging techniques on macOS that I have discovered thus-far. If you find a new one, leave a comment letting me know!
Comments