CVE-2018-9568
前言
漏洞信息
- 实验环境:Nexus 5X(3.10版内核,)
- 漏洞类型:类型混淆类漏洞 –> 释放后重引用
- linux补丁:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/net/core/sock.c?id=9d538fa60bad4f7b23193c89e843797a1cf71ef3
- 漏洞描述:当应用程序使用IPV6_ADDRFORM(将IPv6套接字转换为IPv4)时,sk->sk_prot和sk->sk_prot_creator可能会不同。 这就是为什么sk_prot_creator确保sk_prot_free()在正确的kmem_cache slab上执行kmem_cache_free()的原因。
漏洞原理
漏洞原理
如果把一个IPV6类型的套接字句柄通过setsockopt函数使用IPV6_ADDRFORM选项转换成IPV4套接字,那么该套接字的sk->sk_prot会被改变为tcp_prot,此时套接字的sk->sk_prot和sk->sk_prot_creator就不一样了(sk->sk_prot指向tcp_prot,而sk->sk_prot_creator还是原来的tcpv6_prot,sk_prot_creator指向的proto结构体中的slab变量指向申请当前sk结构体的那块专用高速缓存)。
再使用accept函数去监听上面被转换成IPV4的套接字,底层函数sk_clone_lock会分配一个新的IPV4套接字,使用被监听套接字的sk->sk_prot(tcp_prot)中的slab变量指向的高速缓存申请一个新的sock结构体变量sk,再把被监听套接字的所有内容都拷贝到新的sock结构体变量sk中,由于这属于完全拷贝导致新套接字的sk->sk_prot_creator和被监听套接字的sk->sk_prot_creator一致(都指向tcpv6_prot),并且后面的代码也没有把新套接字的sk->sk_prot_createor更新为创建sock结构体时使用的sk_prot,后续关闭新套接字调用sk_prot_free函数释放sock结构体时使用的是sk_prot_creator指向的slab(也就是tcpv6_prot的slab,但实际应该使用tcp_prot的slab),导致释放这个sock结构体时使用的高速缓存不是申请这个结构体时使用的高速缓存,存在类型混淆的问题。
大概的触发链如下:
1.首先创建一个IPV6的套接字 底层会创建一个sk结构体,在inet6_create函数中,通过sk_alloc函数调用sk_prot_alloc函数在prot->slab指向的slab中申请,并且初始化sk->sk_prot = sk->sk_prot_creator = prot; 此时使用的是IPV6的初始化
2.把IPV6套接字转换为IPV4套接字,在do_ipv6_setsockopt函数中通过选项IPV6_ADDRFORM,把第一步申请的sk->sk_prot赋值成IPV4的prot,此时该sk的prot和sk_prot_creator已经不一致了,所以可以看出为什么在释放函数kmem_cache_free中传入的是sk_prot_creator,这是为了能够在正确的kmem_cache slab上做释放
3.用转换的IPV4套接字通过accept生成一个真正的IPV4套接字,在accept底层函数sk_clone_lock中会把监听套接字的sk结构体通过sock_copy拷贝到新的套接字的sk中,因为这里拷贝完成后没有对新套接字sk的sk_prot_creator变量做初始化,实际新套接字的该成员还是指向了监听套接字的sk_prot_creator,而监听套接字sk的sk_prot_creator是属于IPV6的,它的sk实际在sk_prot中的slab中申请的(newsk = sk_prot_alloc(sk->sk_prot, priority, sk->sk_family);)
4.释放真正的IPV4套接字触发kmem_cache_free,错误使用sk->sk_prot_creator在错误的kmem_cache slab上释放sk
漏洞造成的影响
该漏洞造成的结果:由于混淆释放使用了错误的偏移值导致受害者page的freelist链表损坏,会出现重复申请的情况,一个sk对象内存被多个套接字描述符重复指向,精心构造逻辑会出现释放后重引用的问题。
漏洞模型
通过对一个结构体对象完全拷贝来生成另一个新结构体变量时,由于一时疏忽忘记对新结构体变量中的某些值做初始化,导致使用新结构体变量时错误的使用了原结构体的值。和CVE-2017-8890有些类似,只是8890没初始化的是内存指针导致了浅拷贝的问题,而这个洞是使用了不该使用的变量导致的类型混淆的问题。
补丁用意
补丁就是在accept底层函数sk_clone_lock拷贝完成之后,对新套接字的sk_prot_creator变量初始化为sk_prot指向正确的高速缓存,避免最后释放时使用错误的缓存进行释放。
漏洞利用
基础知识
Slab分配器
什么是Slab分配器
在linux内核中一般是通过buddy system进行物理内存的分配的,其分配单位是页。之所以引入Slab分配器就是因为buddy system只能按页对齐来分配内存,而大多数情况下内核申请的内存大小不需要一页。如果直接通过buddy system分配内存就会造成大量的内存碎片也就是分配了而没有被用到并且无法再次分配的内存。而Slab却可以满足内核的小内存分配并且不会出现过多的内存碎片,虽然Slab分配比buddy system分配灵活但是Slab分配器还是基于buddy system实现的。
Slab分配器的优点
- 不会出现过多的内存碎片
- 内存每次申请内存和释放内存都不会和伙伴系统直接打交道,提高了分配效率
Slab分配器的工作
- 首先会向伙伴系统申请一块内存
- 然后Slab分配器将申请的内存分成相同大小的对象(slab)放到高速缓存中(通过struct kmem_cache结构体来描述一个高速缓存)
- 当内核需要申请内存时通过调用kmalloc函数向Slab分配器申请内存,Slab分配器会查找当前高速缓存中是否有可分配的内存,若没有可分配的内存则向伙伴系统索要新的内存块以供分配,否则直接返回查找到的空闲内存给内核
伙伴系统,Slab管理器和内核之间的关系我感觉就像[供货商(buddySystem)-采购商(Slab管理器)-商铺(高速缓存)-商品(slab对象)-客户(内核kmalloc)]一样。
高速缓存
前面讲了Slab分配器的工作原理提到了高速缓存,那什么是高速缓存呢?
其实高速缓存就是一个数据结构(struct kmem_cache),用来管理Slab分配器从buddy system那申请过来的若干大小相同的对象(object)
内核在系统启动初始化阶段会创建多个通用高速缓存和专用高速缓存,这两种高速缓存管理方式是一致的,只是它们的用途不一样而已。
- 通用高速缓存:一般用于常规的内存分配,如内核需要申请一块200 byte大小的内存,Slab管理器会在通用高速缓存中匹配和申请的大小最接近的高速缓存,由于对齐问题,只会匹配到空间大于我们申请的大小的缓存块而不会小于,这里我们申请的大小是200那么会匹配到”kmalloc-256”这块高速缓存,并从中分配一个对象返回给内核以供使用。
- 专用高速缓存:一般用于存放特定的结构体对象如当内核创建一个新任务时,他会从task_struct的专用缓存中获得struct task_struct对象所需要的内存,缓存上一般会有已分配好的并标记为空闲的struct task_struct对象来满足请求。
在linux上一般可以通过读取/proc/slabinfo这个文件来获取当前系统存在的一些高速缓存,不过android上一般没有该文件。
slab申请/释放
在进行漏洞利用之前我们还得来了解一些slab对象的申请流程与释放流程。
首先我们需要了解几个内核结构体
|
|
|
|
slab申请流程
分配流程如下:优先从cpu本地高速缓存中分配,如果per cpu freelist中没有空闲的内存可供分配了,那么就从per cpu partial链表中分配,如果per cpu partial也没有可以被分配的对象那么继续查看per node partial链表中是否有可供分配的,如果很不幸也没找到可以使用的对象那Slab管理器就从伙伴系统中申请一个空闲的slab对象链表,并挂入到per cpu freelist中以供内核的申请。
内存申请顺序:per cpu freelist -> per cpu partial -> per node partial
slab释放流程
释放流程如下:如果当前被释放的slab对象所在的page和cpu本地高速缓存的page一致,那么直接通过快释放路径释放到per cpu freelist,否则进入慢释放路径,慢释放路径首先把释放对象释放到该对象所在page的freelist上,然后page的引用计数(inuse)减1,接着判断释放掉当前obj后的slab page是否为empty,如果属于empty slab(inuse为0),那么在满足kmem_cache_node的nr_partial大于kmem_cache的min_partial的情况下,释放该slab page到伙伴系统。否则如果slab对象所属链表状态为full,那么释放之后该slab对象链表就属于partial empty链表,就从full链表中删除并且添加到per cpu partial链表中。
利用思路
前面提到过该漏洞的造成的结果就是触发漏洞时破坏了当时高速缓存的freelist指针,导致当时page空闲的所有slab对象都会被重复分配,也就是一块内存被两个sock对象同时指向,我们可以通过close第一个socket描述符去释放该内存,但是第二个socket描述符对该内存还有引用,这就把类型混淆转换成了释放后重引用问题,我们可以在第一次close之后对释放的内存进行喷射占位,第二次close的时候使用的就是我们前面喷射的数据了,从而达到控制内核执行流的效果。但由于涉及到的对象使用的是专用缓存,所以我们通过常规的堆喷射无法喷射成功,需要先通过堆风水把该对象从专用缓存转为通用缓存,然后再去喷射才能如我们所愿顺利控制内存中的内容。
如何把专用缓存转换为通用缓存呢?
通过申请大量的专用缓存,当Slab管理器发现过多的空闲缓存后会把这些空闲内存释放回伙伴系统,这样就有机会被通用缓存分配到。
Heap Fenshui
大概的一个布局流程
关键姿势
|
|
劫持内核执行流
基于我们喷射成功的情况下,通过close函数去关闭socket描述符会触发以下的函数链:
由于sk的内容已经被我们控制了,所以只需要简单布局下内存就能控制住内核的执行流程了。
实现提权
|
|
总结
对Linux内核的好多机制还不够熟悉,很多漏洞都需要结合内核机制才能完成利用,还需要多看看内核源码啊。
该漏洞还可以发挥更大的作用比如绕过一些保护机制,不过还没想到怎么玩o(╥﹏╥)o
有了想法就去验证,不要嫌麻烦偷懒啊。
参考
https://blog.csdn.net/u014089131/article/details/72782624 slub释放/申请
http://blog.chinaunix.net/uid-7494944-id-3833334.html slub/slab中的一些问题 释放/申请
https://awakening-fong.github.io/posts/mm/slub_partial/ slub : node partial和cpu partial
http://blog.chinaunix.net/uid-26859697-id-5512117.html slub分配算法
https://mp.weixin.qq.com/s/3eR3f8RfjCstYMqvPZayHw 图解slub
Author: Let_go
Link: http://github.com/2019/03/25/CVE-2018-9568/
Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.