查看原文
其他

关于CVE-2017-8890的一点细节

houjingyi 看雪学院 2019-05-25

因为有很多分析这个漏洞的文章所以我不详细分析了,只是总结要注意的细节,抛砖引玉。


感谢非常棒的几篇文章:


  • https://www.freebuf.com/articles/terminal/160041.html


  • https://xz.aliyun.com/t/2383


  • https://xz.aliyun.com/t/2385



1. 如何用qemu+busybox搭建调试环境?


因为漏洞存在于内核的网络实现,需要在menuconfig中进行一些额外的设置。详情参考这篇文档:基于virtio的网卡使用。


开启虚拟机之后ifconfig lo up&ifconfig eth0 up启动网卡。



2. ip_mc_socklist对象是如何被double free的?


通过两次调用close最终调用到ip_mc_drop_socket中的kfree_rcu,使用RCU机制最终实现了ip_mc_socklist对象的释放。


简单来说:对于被RCU保护的共享数据结构,读者不需要获得任何锁就可以访问它,但写者在访问它时首先拷贝一个副本,然后对副本进行修改,最后使用一个回调机制在适当的时机把指向原来数据的指针重新指向新的被修改的数据。


这段等待的时间叫宽限期。如果每个CPU都经过一次抢占,就认为度过了宽限期。时钟中断处理函数会依次调用update_process_timers->rcu_check_callbacks->rcu_pending判断,之后在invoke_rcu_core中调用软中断处理函数rcu_process_callbacks,然后依次调用__rcu_process_callbacks ->invoke_rcu_callbacks->rcu_do_batch->__rcu_reclaim调用回调函数head->func(head)。



3. 为什么不能直接劫持ip_mc_socklist对象中的这个函数指针来劫持EIP?


因为kfree_rcu会依次调用__kfree_rcu->kfree_call_rcu->__call_rcu,在__kfree_rcu中func会被设置成该成员在对象中的偏移,最终在__call_rcu中再次赋值给head->func。

#define kfree_rcu(ptr, rcu_head)                  \

   __kfree_rcu(&((ptr)->rcu_head), offsetof(typeof(*(ptr)), rcu_head))


static void __call_rcu(struct rcu_head *head,

              rcu_callback_t func,

              struct rcu_ctrlblk *rcp)


{

   unsigned long flags;



   debug_rcu_head_queue(head);

   head->func = func;

   head->next = NULL;



   local_irq_save(flags);

   *rcp->curtail = head;

   rcp->curtail = &head->next;

   RCU_TRACE(rcp->qlen++);

   local_irq_restore(flags);



   if (unlikely(is_idle_task(current)))
{

       /* force scheduling for rcu_sched_qs() */

       resched_cpu(0);

   }

}


所以需要一个多线程在__rcu_reclaim执行前再次修改ip_mc_socklist对象中的函数指针,但是我们并不能访问到堆喷的内核对象。刚好ip_mc_socklist对象的前8个字节是next_rcu指针,该指针指向rcu链表中的下一个ip_mc_socklist对象。可以通过伪造next_rcu指针使其指向我们在用户空间伪造的ip_mc_socklist对象,然后再通过伪造用户空间对象的函数指针来劫持EIP。




4. 如何寻找堆喷的对象?


ip_mc_socklist对象大小是0x30字节,由于slab分配机制所以需要找到在内核中稳定分配0x40字节大小并且能够控制分配内容的方法。首先想到的是类似的ipv6_mc_socklist对象。这个对象不合适的话可以从sock_kmalloc入手寻找调用链,最终找到适合的对象(这里采用的是ip_mc_source中的sock_kmalloc)。



在找到可能合适的对象之后还需要调试观察对象中是否包含其它干扰内核执行流程的数据,数据是否可控或者是固定的。一时找不到合适的对象也可以通过修改内核源码的方法强行构造出合适的对象,这就是源码在手的好处。



5. 为什么创建堆喷的socket会失败?


linux系统对于每个进程可以打开的文件数目是有限制的,可以通过ulimit -n修改,不过修改是需要root权限的。



6. 为什么堆喷时setsockopt会返回-1?


系统提示的错误信息是no such device。



这和当前的网络配置有关,因为系统中没有路由表,route add default gw 127.0.0.1即可。



7. 为什么不能调用commit_creds(prepare_kernel_cred(0))提权?


在commit_creds前面的注释中有一句:Install new credentials upon the current task。在代码中也是通过current宏获取当前task然后获取cred的。因为前面说了ip_mc_socklist对象的真正释放是在内核软中断处理中,不处于exp进程的上下文,所以这种方法并不能成功。可以修改exp进程的cred结构体来达到提权目的。



8. 剩下的ROP绕过SMEP这些都是常规操作了,不再展开。

最后给出qemu模拟环境kernel4.10.6下绕过SMEP提权的代码和截图,也是东拼西凑没啥技术含量。还是有一些时候会崩掉的,不过我不想浪费时间再完善它了。



代码:


https://github.com/houjingyi233/my-exploits/blob/master/CVE-2017-8890/exp-with-smep.c





- End -


看雪ID:houjingyi                              

https://bbs.pediy.com/user-734571.htm


本文由 houjingyi 原创

转载请注明来自看雪社区



热门图书推荐:

 立即购买!



热门技术文章推荐:




公众号ID:ikanxue

官方微博:看雪安全

商务合作:wsc@kanxue.com

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存