Scott Knight
5 min read

Categories

Tags

Recently while looking into the Apple adid daemon, I noticed that I couldn’t attach to the process with lldb even if SIP was completely disabled. After digging into it a little bit I came to the conclusion that adid was calling the ptrace API passing in PT_DENY_ATTACH. There are numerous other posts out there (like this one) that talk about defeating PT_DENY_ATTACH if you’re running the application yourself. In my case adid is started as a LaunchDaemon and is already running by the time a user is logged in. I decided to take a look at how you could defeat the ptrace call even after the application is already running.

First a quick note on how I determined that adid was using the ptrace API. I started off on a virtual machine with SIP disabled. This is my usual setup when inspecting Apple platform binaries. I tried to connect to adid using lldb but it didn’t seem to work.

$ ps xa | grep adid
  374   ??  Ss     0:00.06 /System/Library/PrivateFrameworks/CoreADI.framework/adid

$ lldb -o "attach 374"
(lldb) attach 374
error: attach failed: Error 1

$ sudo lldb -o "attach 374"
Password:
(lldb) attach 374
error: attach failed: lost connection

The second attempt running lldb with sudo resulted in debugserver itself crashing with a segmentation fault. This is what made me think that ptrace might be in use. Checking the symbols on adid confirmed this.

$ jtool2 -S /System/Library/PrivateFrameworks/CoreADI.framework/adid  | grep ptrace
                 U _ptrace

From there I decided that I should be able to either catch the ptrace call at boot time by debugging XNU itself or potentially inspect and undo the ptrace call from the running process. I set up my virtual machine for kernel debugging and then decided to look into what the ptrace call itself does when called. Looking in the XNU source we can see that the following happens when calling ptrace(PT_DENY_ATTACH, 0, 0, 0);

...
#define P_LNOATTACH     0x00001000
...
if (uap->req == PT_DENY_ATTACH) {
  proc_lock(p);
  if (ISSET(p->p_lflag, P_LTRACED)) {
    proc_unlock(p);
    KERNEL_DEBUG_CONSTANT(BSDDBG_CODE(DBG_BSD_PROC, BSD_PROC_FRCEXIT) | DBG_FUNC_NONE,
              p->p_pid, W_EXITCODE(ENOTSUP, 0), 4, 0, 0);
    exit1(p, W_EXITCODE(ENOTSUP, 0), retval);

    thread_exception_return();
    /* NOTREACHED */
  }
  SET(p->p_lflag, P_LNOATTACH);
  proc_unlock(p);

  return(0);
}
...

When the ptrace API is called with the PT_DENY_ATTACH argument what happens is that the proc structures p_lflag field gets updated to have the P_LNOATTACH flag set. Some of the proc fields are exposed to user space APIs but unfortunately it doesn’t look like the p_lflag is. At this point I decided to start up lldb and connect to the kernel and inspect the task itself. (The following commands assume you’ve loaded the lldbmacros that come with the Kernel Debug Kit)

First I got some information about the adid task.

(lldb) showtask -F adid
task                 vm_map               ipc_space            #acts flags    pid       process             io_policy  wq_state  command
0xffffff8017de1188   0xffffff8018d97838   0xffffff801acca980       3 R        374   0xffffff801acecd50            BLT  2  2  0    adid

(lldb) showprocinfo 0xffffff801acecd50
Process 0xffffff801acecd50
	name adid
	pid:374    task:0xffffff8017de1188   p_stat:2      parent pid: 1
Cred: euid 265 ruid 265 svuid 265
Flags: 0x4004
	0x00000004 - process is 64 bit
	0x00004000 - process has called exec

Now that we have an address for the process itself we can inspect the p_lflag field.

(lldb) p/x ((struct proc *)0xffffff801acecd50)->p_lflag
(unsigned int) $2 = 0x00801000

You can see that the p_lflag field has the P_LNOATTACH flag set. All we need to do to effectively negate the ptrace call is to change the value of the p_lflag field.

(lldb) expr ((struct proc *)0xffffff801acecd50)->p_lflag = 0x00800000

After this lldb can happily attach to the adid process. This effectively solves the original problem I had, which was being able to debug adid after it has already started, but it got me wondering what other processes Apple has that might be doing the same type of thing. I created a short python script to check all tasks to see if they had the P_LNOATTACH flag set.

(lldb) command script import kernhelper.py

(lldb) script kernhelper.show_ptrace_deny_attach_procs()
...
...
0xffffff8025cabe20: SecurityAgent
0xffffff802a615b70: authorizationhos
0xffffff802cbd87f0: adid

(lldb)

So you can see there are a couple other processes running after first login that also have called ptrace passing in the PT_DENY_ATTACH argument. These correspond to the following binaries:

/System/Library/Frameworks/Security.framework/Versions/A/MachServices/SecurityAgent.bundle/Contents/MacOS/SecurityAgent
/System/Library/Frameworks/Security.framework/Versions/A/MachServices/authorizationhost.bundle/Contents/MacOS/authorizationhost
/System/Library/PrivateFrameworks/CoreADI.framework/adid

If we go even further and try to search for all binaries on a clean macOS 10.14.5 install that reference ptrace we see the following list:

$ cd /System/Library
$ rg -l -a -M 80 _ptrace 2> /dev/null
Kernels/kernel
QuickTime/QuickTimeComponents.component/Contents/MacOS/QuickTimeComponents
CoreServices/AppleFileServer.app/Contents/MacOS/AppleFileServer
Frameworks/DVDPlayback.framework/Versions/A/DVDPlayback
PrivateFrameworks/CoreADI.framework/Versions/A/adid
PrivateFrameworks/CoreFP.framework/Versions/A/fpsd
PrivateFrameworks/DiskImages.framework/Versions/A/DiskImages
PrivateFrameworks/AnnotationKit.framework/Versions/A/XPCServices/com.apple.AnnotationKit.MigratorService.xpc/Contents/MacOS/com.apple.AnnotationKit.MigratorService
Frameworks/Security.framework/Versions/A/MachServices/SecurityAgent.bundle/Contents/MacOS/SecurityAgent
Frameworks/Security.framework/Versions/A/MachServices/authorizationhost.bundle/Contents/MacOS/authorizationhost

I’m not sure if all of these are really using PT_DENY_ATTACH or if some are just referencing the text _ptrace in some way but they are candidates that could be looked at in the future.

My hope is this post shows a good example of how you can get started with kernel debugging learning about the internals of XNU itself as well as showing that as long as we still have control of the kernel on macOS there’s ultimately not much Apple can do to prevent us from inspecting their binaries.