SLAKE


paper:https://dl.acm.org/doi/pdf/10.1145/3319535.3363212

摘要

工作:

  1. 使用静态分析和动态分析来识别内核object以及对应的syscall;

  2. 建模常见的exp方法,开发一种方法简化slab的布局;

(具有一定的发现新的可用object的能力)

问题假设:

  1. 有一个仅能让内核panic的poc;
  2. 不认为攻击者可以扩展出新的能力;
  3. 认为只要能够劫持控制流既可以完成最终的提权;

背景知识:

  1. slub中有一个meta data指向下一个没有被占用的obj,构成单链表freelist;但是slab并没有这个地址,它使用索引,构成逻辑上的单链表,也叫free_list;都是LIFO;

  2. 三种漏洞的利用方法:

    1. OOB write:越界写重要数据(函数指针、数据指针,会被函数指针用到)
    2. UAF: 覆盖写重要数据;
    3. double-free:两次申请子引用obj,覆写重要数据;

    :star::star:没看懂怎么就找到共性了?怎么就开始写meta data了?

  3. 挑战:

    1. 如何找到合适的object;
    2. 如何寻找syscall进行利用;
    3. 如何系统地调整syscall来获得预期内存布局

object & syscall 的识别

object

首先静态分析,识别出感兴趣object的类型,然后使用类型信息和一组启发式方法,进一步确定我们感兴趣的object以及它的相关点(分配、释放、引用、解引用);在此期间记录object的type、size、cache以及重要字段的offset;

然后在内核调用图的指引下进行fuzz,进行可达性分析,减少分支

victim object:包含函数指针或者对象指针 ,函数指针直接选中,对象指针则要进一步分析这个对象的type,最重要达到一个函数指针的解引用才行;同时要避免链表的情况;

spray object:用于占据一个已经释放的obj、覆写用;主要是能够从用户空间复制数据到内核空间;先检查copy_from_user的参数dst,然后检查这个dst是不是一个slab分配函数的返回值;

分配释放点的确定

分配点:检查kmalloc等的返回值的def-use链,然后确认这个点是一个分配点;

释放点:kfree;

解引用点:定义与识别:rcu,kfree_call_rcu() and call_rcu_sched()作为解引用点;非rcu的,认为obj中最近的指针的解引用为解引用点;

寻找syscall

使用fuzz跟踪系统调用的缺陷:当内核运行到释放点,不能保证释放的就是我们的object(可能是相同type的其他object);也不能保证真正解引用的就是我们预期的那个函数指针(同理);

fuzz遇到的一个问题:当我们将一个objectA分配到目标的slot中的之后,到了fuzz运行到这个地方的时候,可能多个同type的obj共享了?

解决办法:在内核中我们感兴趣的点插桩,插入panic anchor,当内核运行到这里的时候直接panic掉,然后回溯相应的系统调用的参数来确定是不是我们想要的;(仅仅是解决了上面的一个问题,还要继续找)

为了减少误报率,检查变量system_state,来确定当前状态是不是由用户态系统调用而导致的;

识别syscall

allocation:当触发一个object的分配site的时候,我们通过记录每一个syscall在slab/slub上分配的object来描述它们这些syscall;此外,我们记录它们的参数,以此构建数据库存储信息;

deallocation:首先使用前面的fuzz例子分配一个object,并记录其地址和fuzz输入,然后当fuzz走到一个deallocation site的时候,检查是否和前面记录的地址匹配,如果匹配就保存这个点对应的系统调用,将相关信息存储到数据库中;

dereference:首先使用前面识别的syscall分配一个victim-object,然后执行fuzz,然后探索能够执行到dereference site的syscall,当执行到之后继续执行,记录状态,整个函数执行完之后检查所有的解引用,确认是否是我们的victim-object的;

内存布局操纵

漏洞能力匹配

符号表示

用数组$A_r[m]$来描述一个漏洞的能力,每个数组有m个元素,每个元素表示一个攻击者所能控制的内存区域;每一个元素有一个数对(l, h),这表示该区域相对于victim-obj或者spray-obj的起始和终止偏移;在这个区域内,攻击者可以自由地写数据;这些区域之间是没有重叠的;(这怎么那么像一个vm_area_struct);

使用数组$A_p[n]$来建模一个内核对象的属性,这个数组包含n个元素,每一个元素表示一个包含重要字段的内存区域;同样也是起始和终止offset;

能力匹配——OOBwrite

第一种方案:越界写meta-data,这个很简单直接写指针为一个攻击者所能控制的内存区域,然后分配一个包含重要字段的obj占位,转化为UAF即可;

第二种方案:检查目标obj的$A_p[n]$中的字段是否有被包含在$A_r[m]$中,然后由两个标准:一方面要来自同一个cache,其次是要能够识别成功overlap;

能力匹配——UAF

第一种方案:写meta-data,同上;

第二种方案:用一个spray-obj覆盖原来的obj,并借此机会覆盖写重要字段,两个准则:1. 来自同一个cache;2. 保证$A_p[n]$至少有一个域和$A_r[n]$中的域重合;

能力匹配——double-free

第一种方案:用spray-obj覆盖一个victim-obj,然后写;两个准则:1. 相同cache;2. 保证$A_p[n]$至少有一个域和$A_r[n]$中的域重合;

第二种方案:meta-data,两个准则;

调整slab布局

可能无法恰好将spray-obj分配到victim-obj上,内核中总有许多额外的分配和释放;

具体做法:首先将free_list上的所有未使用的slot进行编号,然后确定目标slot的索引,然后分配一个spray-obj,看看分配出来的索引,计算差值,根据差值的正负决定具体的分配方案;

选取副作用小甚至没有副作用的object;

在利用开始前做一些准备工作消除内存碎片,吞噬了大量碎片之后立即exploit,可以使得创建新的slab,这样可以提升成功率;

识别占用的slot

现在攻击者已经可以得到想要的内存布局了,但是对于OOB write等漏洞光占据unoccupid slot是不够的;

刚消除碎片后,slab中的空闲slot是连续的,假设有一个OOB-write,是第i-1个slot,那么按道理讲我们此时需要分配出来第i个slot加以利用,但是在例子中,我们分配victim-obj的时候会伴随一个其他内核结构,并且还来自同一个cache、且时间上考前,导致了溢出失败;

具体实现

静态分析

动态分析

考虑到需要连接我们想在slab中分配的结构体的释放和解引用对应的两个syscall,因此不能直接使用fuzz进行测试。(还是说fuzz不太适合多syscall配合)

因此不用KCOV这些插件来简化syscall的识别,因为它无法提供内核结构体的信息https://docs.kernel.org/translations/zh_CN/dev-tools/kcov.html

自己编写gcc插件+然后修改内核;

使用的工具是Syzkaller+Moonshine;

https://dawuge.github.io/2019/10/09/2019-10-09-moonshine/

总结

本文工作如下:

  1. 利用静态分析找到感兴趣的内核object,具体使用到的方法是:定位一些函数点、检查参数、返回值等等;配合fuzz;
  2. 寻找对应的syscall:在感兴趣的点插桩,插入panic anchor,借助system_state减少错误,记录信息构建数据库,得到allocate之后即可先分配然后fuzz看参数得到deallocate,对于dereference则需要fuzz之后看完整过程;
  3. 漏洞和object的建模+匹配:定义数据结构、针对不同漏洞的不同匹配方式;
  4. 操纵内存布局:对free_list进行标记,ftrace跟踪,保证分配目标object到目标内存;

文章作者: q1ming
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 q1ming !
  目录