-
-
[翻译]task_t指针重大风险预报
-
发表于: 2017-2-20 10:54 3319
-
引言:大家都知道知名意大利天才少年Luca放出来的针对<=10.2版本的yalu越狱使用的是对kernel port的buffer overflow拿到了kernel_task_port,本文对类似的task_t指针做出了针对性的分析,从mach端口背景知识,到IOkit的相关处理,最终如何利用在堆栈上写出Exploit,最终甚至给苹果团队给出了修复漏洞的建议,由浅入深,偏辟入里,值得推荐。
本文分三篇推出,分别是分析篇,Exploit篇,和修复建议篇。
译者注:
·一些诸如bug,exploit之类的行话选择性的翻译,这通常取决于句子的流畅性。
·不确定的地方在括号中附注了原句
·超链接附在括号内,方便查看
by ruanbonan
分析篇
task_t 指针存在漏洞 由 lan Beer,Project Zero 发布
本文讨论了一个存在于驱动 iOS 和 MacOS 的 XNU 内核核心部分的设计问题。苹果已经发布了两轮缓解策略,紧随其后地,昨天又发布了MacOS 10.12.1/iOS 10.1 中的重大重构(Apple have shipped two iterations of mitigations followed yesterday by a large refactor in MacOS 10.12.1/iOS 10.1)。我们将关注以下内容:这些bug怎样被利用来进行沙箱逃逸并提升权限;我们如何绕过每一个缓解策略。每一步都配有一个可用的exploit。
一些关于mach端口的背景知识
mach端口是由内核维护的多发送者-单接收者的消息队列。某些特别的mach端口。
提供与用户空间相同的消息传送API,但是发送给它们的消息会被内核消息处理程序同步处理。从这个意义上来说,发给这些端口的消息与系统调用非常像。
任务端口就是这样的一个例子。它们处理那些允许发送者操作一个任务的虚拟内存,并能够访问它的线程的消息。每一个任务有它自己的任务端口。内核占用的消息端口使用MIG这个工具来生成序列化代码。
从底层看IOKit
当在用户空间创建一个新的IOKit用户客户端时,你通常会调用IOKitLib库的以下方法:
kern_return_t IOServiceOpen( io_service_t service, task_port_t owningTask, uint32_t type, io_connect_t *connect ); |
IOServiceOpen调用MIG为io_service_open_extended进程间通信方法生成序列化代码,并把序列化消息发送给已定的IOService端口。mach陷阱mach_msg注意到这个端口被内核占用,并为该消息调用正确的内核MIG处理程序,而不是把它排在端口消息队列的队尾。
这里传来的任务端口是owningTask;这个名字在用户空间和内核代码中相同。它是引起我注意的第一处地方。OwningTask暗示着一个所属关系,这可能导致内核扩展开发者相信IOKit实际在这背后维护了一个所属关系,而这个关系确保userclient的生命周期总由owningTask的生命周期决定。这是一个危险的假设,本篇博客文章是质疑这个假设的结果。让我们来跟随代码流进入内核。下面是一段来自内核里面的MIG为io_service_open_extended生成的反序列化代码片段:
mig_internal novalue _Xio_service_open_extended( mach_msg_header_t *InHeadP, mach_msg_header_t *OutHeadP) { ... owningTask = convert_port_to_task(In0P->owningTask.n11ame);
RetCode = is_io_service_open_extended( service, owningTask, In0P->connect_type, In0P->ndr, (io_buf_ptr_t)(In0P->properties.address), In0P->propertiesCnt, &OutP->result, &connection);
task_deallocate(owningTask); ... } |
内核已经把所有包含在消息中的权限复制进来,所以In0P->owningTask.name 实际上是指向一个struct ipc_port的指针,而不是用户态看到的mach端口名。
下面是convert_port_to_task:
task_t convert_port_to_task( ipc_port_t port) { task_t task = TASK_NULL;
if (IP_VALID(port)) { ip_lock(port); if (ip_active(port) && ip_kotype(port) == IKOT_TASK) { task = (task_t)port->ip_kobject; assert(task != TASK_NULL); task_reference_internal(task); } ip_unlock(port); }
return (task); } |
它检查了port参数,确保是一个任务端口对象,然后通过调用task_reference来对任务提供引用,返回task_t指针。task_t是对struct task指针的别名,从代码中可以看出,它是一个引用计数对象。
这里is_is_service_open_extended仅仅是将owningTask传给::newUserClient:
res = service->newUserClient( owningTask, (void *) owningTask, connect_type, propertiesDict, &client ); |
newUserClient是一个IOService方法,如果它们想提供多userclient类型,它可以被一个IOService覆盖。否则默认执行在IOKit记录中查询IOService的IOUserClient子类类名,通过IOKit的反射API(链接:https://bugs.chromium.org/p/project-zero/issues/detail?id=221)分配它,并调用它的::initWithTask方法。::initWithTask的默认执行流也不对owningTask做任何处理。
查看代码到此,似乎默认情况下owningTask并不会保有对userclient的引用(这会避免userclient对任务进行引用,形成循环引用,事实却完全相反;如果userclient想要保持对owningTask的引用,它必须对owningTask进行引用——不存在隐式的所属关系。
译者:ruanbonan
原文链接:https://googleprojectzero.blogspot.kr/2016/10/taskt-considered-harmful.html
原文作者:lan Beer,Project Zero
微信公众号:看雪iOS安全小组 我们的微博:weibo.com/pediyiosteam
我们的知乎:zhihu.com/people/pediyiosteam
加入我们:看雪iOS安全小组成员募集中:http://bbs.pediy.com/showthread.php?t=212949
[看雪iOS安全小组]置顶向导集合贴: http://bbs.pediy.com/showthread.php?t=212685
·
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)