paper : https://dl.acm.org/doi/pdf/10.1145/3576915.3623220
摘要
提出了一种部署用户态数据到内核栈的方法辅助CFHP走ROP提权的方法;
对栈迁移的批判理由:
他们依赖特定的内核内存布局,以及某些gadget的精准存在或者一些额外的原语(例如对寄存器控制)的存在;
本工作能够绕过现有的内核保护措施,包括最新的FGKASLR,不需要任何其他的条件(例如寄存器的控制和特殊的内存配置)
当前内存保护:
SMEP, SMAP, KPTI,
NX-physmap:标记线性映射区为不可知性;
CR Pinning 阻止对CR4寄存器的非授权修改;
STATIC_USERMODE_HELPER:应该就是组织userfaultfd这些;
RKP:不许与直接修改进程的cred;
pt-rand:使直接修改内核页表的方法变得无效;
RANDSTACK:堆栈偏移,pt_regs不好用了;
威胁模型:
拥有KEPLER的所有保护措施,此外还有FG-KASLR;
对于原语,需要控制流劫持和内核地址泄露;
design
本方法完全不打算在堆中部署ROP,考虑到有些情况下,系统调用栈中最多部署7个ROP,因此就想每次执行一点点功能,然后退出去再重新进来;
数据存储
直接存储,像poll这个系统调用一样,copy_from_user将数据复制到内核栈中:
间接存储:通过多重syscall将数据加载到内核栈中,例如open+readlink两个系统调用;
在本工作中,要系统的分析直接存储,首先分析用户态的数据在系统调用期间是如何流向内核栈的——主要就是用户的寄存器和用户的内存地址空间,在用户态空间进行污点分析,然后手工分析污点的结果,
直接存储:copy_from_user;
pt_regs
调用约定:保存相关寄存器的值到栈上,分两种情况,一种是内核真的调用了syscall处理函数,另一种是系统调用处理函数调用其他的函数;
未初始化内存:之前系统调用用过的数据还残留在内核栈上;
武器化
startingROP:add rsp, x; ret
SmallChains:可用gadget通常很短,不同的syscal有不通过的情况;很CFHP需要特定的输入,这又减少了布置gadget的数量;很多用户态留到内核栈的数据可能不是连续的,也不好用;
独立的ROP:考虑到CFHP往往是覆盖写了内核堆上的一个系统资源,因此在exploit进程中的所有线程都可以访问到这个系统资源,所以攻击者可以创建一个独立的线程去执行ROP(意思是新开一个线程不影响劫持控制流),使得ROP的终止不会影响到主线程,这样多个独立的线程去触发,可以避免多次堆布局?
不受限制的任意地址rwx:
整合:
保护绕过
SMAP、SMEP、KPTI:根本就没直接访问用户态数据;
RANDSTACK:用合法数据和上下文;由于只随机了5位,搞一堆ret增大成功率;另外如果没有开启panic_on_oops,如果没有开启pamic_on_oops,那么一个任务的指令是不会引起内核崩溃,所以可以用多个线程反复尝试,
STACKLEAK/STRUCTLEAK/INITSTACK:这些保护阻止用户访问未初始化的内存,
FG-KASLR:其他代码片段,如程序集存根,也可以编译为在内核中具有固定偏移量的可执行部分。
KCFI/IBT:它们都旨在保护Linux内核免受前沿控制流劫持攻击,用KCFI编译,__efi_call函数不验证其调用目标。即使有了完美的实现,攻击者仍然可以通过劫持后边控制流来获得PC控制。
Shadow Stack.
CFI+Shadow Stack
半自动化实现
只考虑用commit_creds(init_cred)的方式提权,不考虑多种方法。
输出用于抬栈的gadget的地址,触发函数(能够数据传递、调用触发系统调用)
本工作基于虚拟机快照,之后分析了所有能够流向内核空间的数据,最后使用符号执行判断gadget;