A PoC demonstrating how to use task_for_pid in modern MacOS. Verified on El Capitan (10.11) but should work up to Catalina (10.15) since the API has not changed.
task_for_pid allows you to retrieve a pid for user-land processes that are not signed by Apple. If you are trying to hook the kernel, or applications like Safari or Pages, task_for_pid will fail. task_for_pid's definition is as follows:
kern_return_t task_for_pid(
mach_port_t parent,
int pid,
mach_port_t *task_out );
- parent should be the calling task and can be satisfied by
mach_task_self()
. - pid is the task you want to attach to.
- task_out is a pointer to a mach_port struct that will hold the task port id.
- task_for_pid returns a kernel code. The two you will generally see are KERN_SUCCESS( 0 ) and (os/kern) failure( 5 ).
An example call:
kern_return_t kern_return = 0;
mach_port_t task = 0;
long int pid = _some_pid;
kern_return = task_for_pid( mach_task_self(), pid, &task );
if( kern_return != KERN_SUCCESS )
{
printf( "task_for_pid failed: %s", mach_error_string( kern_return ) );
return 0;
}
You do not need to disable SIP to call task_for_pid. You also do not need to sign your code, but it doesn't hurt.
For the kernel to allow us to use task_for_pid
, we need to give our application the SecTaskAccess
permission. To do this, create a file called Info.plist
with the following content:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SecTaskAccess</key>
<array>
<string>allowed</string>
</array>
</dict>
</plist>
When compiling, use -sectcreate to create a section for the plist:
gcc task_for_pid.c -sectcreate __TEXT __info_plist ./Info.plist -o task_for_pid
Execute the target, find its process id with ps
, and then run sudo ./task_for_pid 1234
where 1234
is the pid found by ps
.
If you get an (os/kern) failure, make sure you are running with root. If it still fails, create a new system certificate in the keychain and use it to sign task_for-pid via codesign -s hook-cert ./task_for_pid
.
With a mach task, you can do pretty much anything, including reading memory (mach_vm_read), writing memory (mach_vm_write), mapping memory (mach_vm_map), and scanning memory regions (mach_vm_region and mach_vm_region_recurse).
Due to security features in Mac OS X El Capitan, task_for_pid
will correctly fill in task
but will always output 3331
.
- A blog post for 10.5 (Leopard) from 2010, which I used a fair amount.
- A blog post from 2016 which basically parrots the aforementioned post with some refinements.
- The Bit Slicer source code.
- An article from 2006 about ptrace.