吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4824|回复: 1
收起左侧

[调试逆向] task_t指针重大风险预报——PoC task_t considered harmful - many XNU EoPs

[复制链接]
看雪iOS小组 发表于 2017-1-1 21:52

CVE-2016-1757 是由于exec运行期间资源条件竞争导致port失效而产生的安全漏洞
CVE-2016-1757是一个涉及到在exec操作期间,端口结构顺序失效的条件竞争漏洞。
详情:

       当一个suid二进制程序被执行,尽管task struct与执行程序前状态保持一致,但是它执行前的task 和task port确实已失效。
当执行一个suid二进制程序时,虽然这个任务的旧任务以及线程端口已经无效,但是它的任务结构却还保持着相同的状态。

在此期间,执行前task没有自我复制和产生一个新的task。

如果没有fork或者创建新的任务,

这就意味着任何指向之前task struct的指针如今指向一个进程euid为0的进程的task struct。(即拥有root权限的执行环境)

许多IOKit驱动程序都保存着task struct指针作为它们的一部分,可以参考我之前的bug报告中的一些例子。

在这些例子中,我提到了另一个bug,若IOKit驱动程序未引用task struct,则如果杀死相应的task,然后fork和执行一段suid root二进制程序,
我们能够通过一个euid 为0的虚拟内存的task struct指针获得IOKit object交互。

我们就可以得到IOKit对象,并通过task struct指针,与一个euid 为0的进程的虚拟内存进行交互。

(还有一种攻击方式:你也可以通过强制产生一个恢复task struct的服务程序来逃逸沙盒)
(你也可以通过强制launchd生成一个将会重新利用已被释放的task struct的服务,来实现沙盒逃逸。)

反之若这些IOKit驱动程序引用task struct,没关系!

当然,再进一步,即使这些IOKit驱动程序对task struct作了引用,也无所谓!

(至少在没有suid二进制程序运行时)
(至少在suid二进制程序运行时没有问题。)

因为用户端的用户空间客户端在time A拥有发送至task port的权限,但当从task port 传递至IOKit并不意味着仍然有发送权限,仅仅是因为IOKIt驱动程序实际调用的是task struct指针。

就IOSurface而言,这个允许我们方便的发送任意代码至虚拟内存euid为0的读写区域。

以IOSurface为例,这使得我们可以轻松的map euid为0的进程的虚拟内存里的任意可读写区域,并且重新写入。

大量IOKit驱动程序存储taks struct指针,使用它们操作用户空间虚拟内存(如ioacceleratorFamily2,IOthunderboltFamily,IOSurface)或者依赖于taks struct指针去执行权限检测(如IOHIDFamily)

另外一个有趣的例子是stack中的task struct指针

MIG文件中相对应的用户层/内核层中的task port如下格式
  type task_t = mach_port_t
  #if KERNEL_SERVER
      intran: task_t convert_port_to_task(mach_port_t)
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);
  }

task port 转变为相对应的task struct指针,该指针引用于task struct,但仅仅是确保它不被释放,
而非为了执行二进制程序导致它自己的euid不变。

而不是保证它的euid不会变成suid root程序执行的结果。

尽管task port不再有效,但只要port lock解除锁定,task就可以执行标记为suid的二进制程序,task strut指针就依然有效。

这就产生了大量的有趣的条件竞争。

grep所有.defs文件的源代码,需要一个task_t来找到它们;-)

在这个exp中,我将证明最有趣的环节:task_threads

让我们一起来看一下task_threads实际是如何工作的,包括由MIG产生的核心代码。

在 task_server.c(一个自动产生的文件,若找不到该文件,先build XNU)
target_task =convert_port_to_task(In0P->Head.msgh_request_port);
  RetCode = task_threads(target_task,(thread_act_array_t *)&(OutP->act_list.address),&OutP->act_listCnt);
  task_deallocate(target_task);
This gives us backthe task struct from the task port then calls task_threads:
(unimportant bitsremoved)
  task_threads(
    task_t          task,
    thread_act_array_t    *threads_out,
    mach_msg_type_number_t  *count)
  {
    ...
    for (thread = (thread_t)queue_first(&task->threads);i < actual;
          ++i, thread =(thread_t)queue_next(&thread->task_threads)) {
      thread_reference_internal(thread);
      thread_list[j++] = thread;
    }
    ...
      for (i = 0; i < actual; ++i)
        ((ipc_port_t *) thread_list) =convert_thread_to_port(thread_list);
      }
    ...
  }
task_threads利用task struct 指针通过threads列表迭代threads(来遍历线程列表),
然后creates发送指令给task_threads,task_threads发送指令返回给用户空间,
然后赋予它们发送权限,随后在用户空间得到发送返回
过程中出现锁定和解锁,但是锁定和解锁是不相关的。

如果task同时运行suid标记为root的二进程代码会发生什么?

执行代码相关联的两部分主要是ipc_task_reset和ipc_thread_reset
  void
  ipc_task_reset(
    task_t   task)
  {
    ipc_port_t old_kport, new_kport;
    ipc_port_t old_sself;
    ipc_port_told_exc_actions[EXC_TYPES_COUNT];
    inti;
    new_kport = ipc_port_alloc_kernel();
    if (new_kport == IP_NULL)
      panic("ipc_task_reset");
    itk_lock(task);
    old_kport = task->itk_self;
    if (old_kport == IP_NULL) {
      itk_unlock(task);
      ipc_port_dealloc_kernel(new_kport);
      return;
    }
    task->itk_self = new_kport;
    old_sself = task->itk_sself;
    task->itk_sself =ipc_port_make_send(new_kport);
    ipc_kobject_set(old_kport, IKO_NULL,IKOT_NONE); <-- point (1)
  ... then calls:
  ipc_thread_reset(
    thread_t thread)
  {
    ipc_port_t old_kport, new_kport;
    ipc_port_t old_sself;
    ipc_port_told_exc_actions[EXC_TYPES_COUNT];
    boolean_t has_old_exc_actions = FALSE;
    int     i;
    new_kport = ipc_port_alloc_kernel();
    if (new_kport == IP_NULL)
      panic("ipc_task_reset");
    thread_mtx_lock(thread);
    old_kport = thread->ith_self;
    if (old_kport == IP_NULL) {
      thread_mtx_unlock(thread);
      ipc_port_dealloc_kernel(new_kport);
      return;
    }
    thread->ith_self = new_kport; <--point (2)
Point (1)从旧的task port清除task struct pointer,然后重新分配一个新的port给task
Point (2)对应的 thread port同上.

调用执行exec的进程B和处理task_threads()的进程A以及imagine

下面是执行过程:
  ProcessA: target_task = convert_port_to_task(In0P->Head.msgh_request_port); //
得到指向B进程的task struct 指针
  Process B: ipc_kobject_set(old_kport,IKO_NULL, IKOT_NONE); //
B进程使旧的task port失效以至于不(再)拥有task struct的指针
  Process B: thread->ith_self = new_kport //
B进程重新分配一个新的thread ports和激活(并设置)他们
  Process A: ((ipc_port_t *) thread_list) =convert_thread_to_port(thread_list); // A进程读取和转变为新的 thread port 对象!
这里最基本的问题不是这个特殊的资源(条件)竞争,事实上是当最先指定一个task struct指针后,你不能依赖拥有一个相同的euid的task struct 指针。

exploit:
这段利用代码说明一个euid为0进程的thread port竞争资源,
这段poc仅仅利用了这种条件竞争来得到一个euid为0的程序的线程端口。
一旦运行利用代码,我仅仅需要跟随(放置了)一小段ROP payload插入ret-slide。然后使用thread port 设置RIP到gadget添加了大量的rsp、X,随后会弹出shell,只需要运行一段时间,将会出现竞争情况。
测试系统 MacBookAir5,2 OS X 10.11.5(15F34)

在mac os10.12更优化的利用代码,对于内核版本不高于10.12的都有效。

翻译:银雁冰  

校对:布兜儿(看雪ID:我有亲友团)
编辑:凌迟



发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

小可爱~ 发表于 2017-1-17 09:24 来自手机
本帖最后由 小可爱~ 于 2017-1-17 09:42 编辑

task_struct不就是PCB进程控制块的结构体吧?
竞态条件就是两个以上用户或者和系统强资源的原因吧
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-4-26 11:15

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表