Defeating Anti-Debug Techniques: macOS AmIBeingDebugged

For a long time Apple has had a debugger-detection function on their developer site. It’s not very hard to defeat with a debugger, but since this technique is sometimes used by malware to try to prevent debugging, I decided to document how to defeat it.

NOTE: These examples assume compiling to x86_64, the current default.

An minimal program using Apple’s function goes something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// clang -o main main.c
#include <stdio.h>
#include <assert.h>
#include <stdbool.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/sysctl.h>

static int AmIBeingDebugged(void) {
int junk;
int mib[4];
struct kinfo_proc info;
size_t size;

info.kp_proc.p_flag = 0;

mib[0] = CTL_KERN;
mib[1] = KERN_PROC;
mib[2] = KERN_PROC_PID;
mib[3] = getpid();

size = sizeof(info);
junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
assert(junk == 0);

return ((info.kp_proc.p_flag & P_TRACED) != 0);
}

int main(int argc, char *argv[]) {
int debugger = AmIBeingDebugged();
if (debugger) {
printf("FAILURE\n");
return 1;
}

printf("SUCCESS\n");
return 0;
}

If we run it with and without a debugger, we get 2 different results:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ ./main
SUCCESS
$ echo $?
0

$ lldb main
(lldb) target create "main"
rCurrent executable set to 'main' (x86_64).
(lldb) r
Process 17532 launched: '.../main' (x86_64)
FAILURE
Process 17532 exited with status = 1 (0x00000001)
(lldb)

Let’s use a debugger to change that!

As we can see, sysctl is populating a struct with data, and info.kp_proc.p_flag ends up with a P_TRACED flag set if the progress is being traced (debugged).

Let’s checkout the headers for these structs.

From that we learn that info.kp_proc.p_flag has a type of int, and we can calculate the offset, but an even easier way to calculate the offset of the value, and the value of P_TRACED is to just compile and run a little C program.

1
2
3
4
5
6
7
8
9
10
11
// clang -o sample sample.c
#include <stdio.h>
#include <stddef.h>
#include <sys/types.h>
#include <sys/sysctl.h>

int main(int argc, char *argv[]) {
struct kinfo_proc info;
printf("%lu %i\n", offsetof(typeof(info), kp_proc.p_flag), P_TRACED);
return 0;
}

Running outputs:

1
$ ./testing 32 2048

Alright, now we have everything we need!

Now it’s just a simple matter of setting a breakpoint on sysctl to read the structure address from the rdx register, finish the function call, and use an LLDB expression to XOR out the P_TRACED flag for each sysctl call.

The LLDB expression might looks something like this, substituting the address for the one read at function start.

1
expr -- *((int *)(0x00007ffeefbfda98 + 32)) = *((int *)(0x00007ffeefbfda98 + 32)) ^ 2048;

Here’s how it all works in action:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
$ lldb main
(lldb) target create "main"
Current executable set to 'main' (x86_64).
(lldb) b sysctl
Breakpoint 1: where = libsystem_c.dylib`sysctl, address = 0x000000000002e100
(lldb) r
Process 16681 launched: '.../main' (x86_64)
Process 16681 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x00007fff6422b100 libsystem_c.dylib`sysctl
libsystem_c.dylib`sysctl:
-> 0x7fff6422b100 <+0>: pushq %rbp
0x7fff6422b101 <+1>: movq %rsp, %rbp
0x7fff6422b104 <+4>: movl (%rdi), %eax
0x7fff6422b106 <+6>: cmpl $0x8, %eax
Target 0: (main) stopped.
(lldb) reg read rdx
rdx = 0x00007ffeefbfda98
(lldb) fin
Process 16681 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = step out
frame #0: 0x00007fff644fd6b2 libxpc.dylib`_xpc_pid_is_being_debugged + 116
libxpc.dylib`_xpc_pid_is_being_debugged:
-> 0x7fff644fd6b2 <+116>: movl 0x20(%rbx), %eax
0x7fff644fd6b5 <+119>: movq 0x382e795c(%rip), %rcx ; (void *)0x00007fff9c7b9070: __stack_chk_guard
0x7fff644fd6bc <+126>: movq (%rcx), %rcx
0x7fff644fd6bf <+129>: cmpq -0x18(%rbp), %rcx
Target 0: (main) stopped.
(lldb) expr -- *((int *)(0x00007ffeefbfda98 + 32)) = *((int *)(0x00007ffeefbfda98 + 32)) ^ 2048;
(int) $0 = 20486
(lldb) c
Process 16681 resuming
Process 16681 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x00007fff6422b100 libsystem_c.dylib`sysctl
libsystem_c.dylib`sysctl:
-> 0x7fff6422b100 <+0>: pushq %rbp
0x7fff6422b101 <+1>: movq %rsp, %rbp
0x7fff6422b104 <+4>: movl (%rdi), %eax
0x7fff6422b106 <+6>: cmpl $0x8, %eax
Target 0: (main) stopped.
(lldb) reg read rdx
rdx = 0x00007ffeefbff3d8
(lldb) fin
Process 16681 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = step out
frame #0: 0x0000000100000e46 main`AmIBeingDebugged + 134
main`AmIBeingDebugged:
-> 0x100000e46 <+134>: movl %eax, -0x2ac(%rbp)
0x100000e4c <+140>: cmpl $0x0, -0x2ac(%rbp)
0x100000e53 <+147>: sete %r10b
0x100000e57 <+151>: xorb $-0x1, %r10b
Target 0: (main) stopped.
(lldb) expr -- *((int *)(0x00007ffeefbff3d8 + 32)) = *((int *)(0x00007ffeefbff3d8 + 32)) ^ 2048;
(int) $1 = 20486
(lldb) c
Process 16681 resuming
0x7ffeefbff3d8 0x7ffeefbff3f8
SUCCESS
Process 16681 exited with status = 0 (0x00000000)
(lldb)

Success! We have fooled the debugger detection code!

Obviously if you are doing this to avoid debugger detection in a piece of software, you will probably want to script this for your use-case. Fortunately, LLDB has python scripting capabilities, which I recommend you take advantage of.

Comments