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:
And so, the output is:
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!)