Scott Knight

### 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);

/* 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
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

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.