paper:https://www.youwei.site/papers/SP2025b.pdf
IEEE S&P 2025
摘要
借助bridge和route这两类object,实现将一个受限制的OOB-write漏洞升级为任意地址写漏洞;
bridge object是指将一个同cache越界写升级为跨cache越界写的object;
router object是指重定向源地址、或者目标地址的object;
主要分为4步:
- 利用静态分析找这两类object;
- 利用fuzz找相关的分配点等;
- 匹配对象;
- 结合原始exp和精心堆布局生成完整的调度策略;
假设:
- 开启了好多保护:KASLR, KPTI, KCFI, SMEP, SMAP;
- 自动能力升级过程步违反这些保护;
- 但是最终的提权还是要借助其他办法绕过上述保护;
motivation
负责将内容从ip(第一行的参数)写到dp上(第四行);
KOOBE
作者认为KOOBE存在两个缺点:
- 越界写的内容需要可控;
- 被越界写入的obj需要时和vul-obj来自相同的cache;搜索空间就小了;
SLUBStick
将一个受限制的堆漏洞转化为一个页表操纵,然后达到任意地址写;
主要是三个步骤:
- 将漏洞迁移成double-free,导致一个空悬指针,然后就有了一个内存写原语(
- cross cache到页表;
- 篡改页表;
然后提出了一种侧信道的方式提升效率;
本文作者通过实验得到该方法的成功率低于3%;
这个原语的挑战在于:
- 本身很复杂;
- 要满足free和use的空窗期;
- Linux5.17之后能防止任意的页表篡改;
本文方法
强调不需要漏洞类型的迁移;也不需要slab页面的回收;也不需要页表的操纵;
关于堆OOB的写漏洞的能力可以利用的假设如下:
- offset、length、value必须在可控的范围内;
bridge object:
- 包含一个长度字段,临近vul-obj;
- 这是一个用于复制的结构,源数据用户可控;
- 目标在堆上;
router object:
- 包含指针;
当一个可控的溢出写发生在bridge object相关的目标地址,与之相邻的router object的指针被篡改,然后导致了新的任意地址写;
挑战
- 识别这两种结构体;
- 触发分配和相应的解引用;
- 将这两种结构体组合起来,并探索它们的能力;大量的噪音可能会阻碍人们对其潜在能力的充分探索
- 生成完整的利用,
design
识别两种object
先定义一堆字典:
分配点识别:
对每个分配点进行 backward inter-procedural 控制流分析;这一步的目的是建立系统调用和分配点的联系(前提是不需要root权限)
然后使用一个forward inter-procedural 数据流分析,来决定每一个分配点分配出来的对象的类型;
跟踪所有返回值的使用情况,关注类型转换、指针解引用、参数传递等,这些也能用于推测对象的类型;
memory copy路径识别:
- backward inter-procedural 数据流分析,对dst,src,size等参数进行;
- 如果size源自于一个堆上obj的某个field,并且obj的类型是type,就记录下来这个关系;(说明是从名为type类型的obj中取出size进行memory copy);
- 类似地,如果src和dst也是源自于堆上的type类型的obj的field,如果是来自user就记录成USER_SPACE,总之还是记录下这个关系。
Bridge Objects 识别:
- 包含决定memory copy长度的长度字段;
- copy的目标buffer在堆上;
- 源地址在堆上或者来自用户空间;
然后就是将上述条件形式化的表示成如下的样子:
alloc**src be Copy2-
SrcAllocSet[copy] and alloc**dst be Copy2DstAlloc
Set[copy], let type**sizeobj be Copy2SizeObjType[copy]
and alloc**sizeobj be Type2AllocSet[type**sizeobj ],
1 alloc**sizeobj=∅, 2 alloc**dst=∅, 3 alloc**src=∅ or alloc**src
=USER SPACE,
Router Objects 识别:
- 包含指针指向memory copy的目标地址;
- memory copy的源地址的指针也在这个结构体中(就是溢出可控的意思),或者这个源地址本身就在用户态;
然后还是一通形式化:
限制提取:
- 关注指针的解引用,以及和obj中相关的条件分析、数据依赖;
- 总之要保证指针解引用的是一个合法的地址,分支条件要被满足;
- 以”range|op|value”的形式给定约束限制;
- 使用LLVM IR来静态分析这些限制;
触发分配和copy
整体算法
很多操作要依次进行,但是fuzz只能找出触发一个site的syscall,却不能将他们按照希望的顺序串联起来;
这样巨大的搜索空间,fuzz盲目搜是很无效的;
因此本文设计了一个two-stage constraint-guided fuzzing:
- 第一阶段用于触发bridge/router object的分配,以及对应缓冲区的分配;
- 第二阶段在第一阶段生成的基础上,关注于copy的触发;
- copy时的路径限制用于引导第二个阶段;
- 使用定制的monitor函数来监控内核,判断是否到达对应的copy点;
先初始化,找好分配点、copy点、路径限制等等,加到集合中;
stage1找分配点,stage2找copy点,在达到资源限制前随机选择stage执行;
stage1的具体做法:
先关闭无关的锚点,激活感兴趣的锚点;
在queue_stage1中以公平概率平等地变异产生testcase;或者通过组合可以到达感兴趣的分配站点的系统调用,从头生成它们;
如果所有感兴趣的锚点都被触发,就将testcase加入到queue_stage2,如果只是部分触发,就将testcase加入到queue_stage1;
stage2的具体做法如下:
首先改变锚点;
利用路径限制来知道变异;(Line42)
通过将现有测试用例与能够到达感兴趣的内存复制站点的系统调用相结合(第44-7 45行),生成新的测试用例,而不是从头创建;
如果到达了感兴趣的内存复制站点,则将新的测试用例添加到最终结果语料库中;
锚点设置
基于内核源码,在每一个分配点和copy点后将加入anchor;
每次到达这些锚点,就会通过copy_to_user,通知到用户态;(这就应该是一个插桩实现🤔)
内核只编译一次,但是这些锚点是可以在运行时被关闭和激活的;
但是要考虑到其他各种内核的例程(routine)也会触发到这些site,像那些异常信号、外围设备的中断信号、再或者其他的内核线程、用户态进程等等,为了消除这些噪声,增强了monitor的能力,来识别是否来自我们感兴趣的syscall;具体做法是检查内核变量system_state
是否等于SYSTEM_RUNNING
,以及当前进程的pid是否和被fuzz进程的pid相同;😮
fuzz策略
在第一个stage,随机增加和删除syscall,变化参数;
在第二个stage,只改变参数,重复已经存在的syscall,不在新增或者删除syscall;我们探测哪些系统调用参数影响与约束相关的字段,并优先考虑这些参数的变异。
串联和能力探索
这一部分的任务是将vul-obj、bridge-obj以及router-obj布局在一起。
主要挑战是:各种干扰可能会阻碍对潜在能力的全面探索;
- 计划外的syscall可能会导致对bridge-obj和router-obj的意外分配以及copy操作;(感觉就是内存分配噪声和copy操作的噪声🤔)
- 堆风水策略引入的意外的内存布局;(还是堆风水🤔)
本文的具体策略是分为两个步骤:
- 在模拟环境中进行能力探索;
- 在真实环境中综合生成exp;
使用能力引导的fuzz进行能力探索;
- 通过一个全局的map来记录testcase的能力;(注意这里的testcase是针对vul-obj的,和前面有所区别🤔)
- 首先执行一次testcase记录初始的能力;
- 然后每次循环中,如果一个testcase有不可控溢出,就和bridge-obj结合;如果是可控溢出就和router-obj结合;
串联的算法如下:
注意看前面9-15行的代码,可以看到是根据当前testcase的能力(oob-write是否可控)决定bridge-obj还是router-obj,然后随机选择对应集合中的一个type,之后进行match(应该是匹配cache🤔),如果匹配成功,就进行chain;testcase2是前面利用fuzz找出来的分配以及copy的testcase;
- 首先测试testcase1的能力;(17)并记录(18);
- 我们进行能力引导的变异来探索能力边界(19-29的循环中);
- 在循环中,根据历史变异记录分析对testcase2(bridge/router-obj)进行变异,并且在执行变异的testcase2时,强制执行testcase1的功能(就是直接借助模拟环境强制重定向控制流?😮😮);当没有新能力产生的时候结束循环;
- 如果最后的能力能够导致aaw,就生成exp;否则,如果发现了新的能力,记录了测试用例的组合以及新功能;
- 🕊🕊
如果有多个可选的obj,就要优先选择字段靠前、限制较少的;
模拟监控锚点
这部分主要是为了在运行时具体判断写的能力,🤔;
不看了。😊
综合exp
此时已经有了模拟环境下的exp原型;(总结前边工作就是,fuzz出obj的使用接口,然后有poc,选obj用,再变异变异,强制组合两者,生成一个原始的exp);
关于内存分配,原文如下:
The memory management strategy involves memory layout manipulation and memory content spraying, which are standard in kernel heap vulnerability exploitation [18], [23]. This subsection specifically focuses on the system call scheduling strategy.
syscall调度
- 首先将一个exp分为三个部分,对应:vul、bridge、router三种obj;
- 之后提取代表相关操作的syscall;
- 多次执行exp,测量每个系统调用的平均执行时间;
- 通过这些信息保证:bridge-obj的分配发生在触发漏洞之前;router-obj的分配发生在bridge-obj上的内存复制之前;触发漏洞、bridge-obj上的内存拷贝以及router-obj上的内存拷贝按顺序发生;
- 只要满足上述约束就行,不用非得每一个操作都依靠一个独一无二的syscall;