位置:首页 > 安全分类 > WEB安全

Intel CET缓解措施深度研究

  • 2022-05-07 11:02:40
简介 0x00 TL;DR上?篇?章中已经简单介绍过了CET的基本原理和实际应?的?些技术,站在防守?的视?下,CET确实是?个能?较有效防御ROP攻击技术的措施。那么在攻击者的视?来看,研究清

0x00 TL;DR

上?篇?章中已经简单介绍过了CET的基本原理和实际应?的?些技术,站在防守?的视?下,CET确实是?个能

?较有效防御ROP攻击技术的措施。那么在攻击者的视?来看,研究清楚CET的技术细节,进?判断CET是否是?

个完美的防御?案,还是存在?定的局限性,则是攻击?的重中之重。

本?由浅?深地讲述CET的实现细节,最后提出?个理论可?的绕过?案,供研究者参考。

0x01 Shadow Stack Overview

上?篇?章已经?概对CET做了个基本概念介绍,所以就不重复,直接说重点。

Shadow Stack PTE

Shadow Stack本质上是块内存?,属于新增的?类型,因此需要增加?个新的?属性来标识Shadow Stack。PTE中的?些未有被CPU定义的,也有保留给操作系统使?的,例如第0位的Present就由CPU标识?是否分配。Linux

操作系统没有将所有保留位都使?掉(?于别的?途),但是其他操作系统则没有剩余可?的保留位了,因此从

Linux中取?个未使?的位,不太可取。

这?Linux采?了复?很少使?的?状态(写时复制的状态):write=0, dirty=1。当Linux需要创建写时复制

write=0, dirty=1的?时,?软件定义的_PAGE_COW代替_PAGE_DIRTY,创建shadow stack时,则使?write=0,

dirty=1。这就将两者区分开来了:


Shadow Stack

Management Instructions

为了保证shadow stack的独特性,CET专?设计了独有的汇编指令。普通的指令(MOV, XSAVE...)将不被允许操

作shadow stack。

这?重点说SAVEPREVSSP、RSTORSSP。Linux环境下,会存在栈切换的情况(系统调?、信号处理...),为了保

证shadow stack的正常运作,数据栈切换后shadow stack也需要相应切换,因此就会?到这两个指令。 

下图为执?RSTORSSP指令前后的shadow stack状态变化。执?的操作为先将SSP指针指向new shadow stack的

‘restore token’,即0x4000。然后?current(old) shadow stack的地址做‘new restore token’替换掉‘restore

token’,?于后续的SAVEPREVSSP指令使?。

下图为执?SAVEPREVSSP指令前后的变化。执?的操作为将前?设置的‘new restore token’压?previous shadow

stack中,并将标志位置0。然后将SSP指针加1。


?此,就完成了shadow stack切换的整个过程。

0x02 Shadow Stack Implementation

这?不提及Shadow Stack的普遍情况(?上?篇?章),只研究Shadow Stack在?些特殊场景下的实现,在这些

场景中光申请Shadow Stack?后做push/pop操作是不够的,往往需要更复杂的实现。

Signal

?般?户需要对某个信号做?定义的特殊处理时,就会?到信号。对应的函数为signal()、sigaction():

当捕获信号到执?信号处理函数再到恢复正常执?的整个过程中,会经历进程挂起、Ring0和Ring3间的切换、上下?切换等操作,这都需要shadow stack作出相应的变化,否则就会出现不可知的异常。下图是信号处理期间进程的变化。


以signal函数举例,在glibc中它的具体实现为下?所示,最终会调?rt_sigaction去注册信号。

再看CET的实现,它在 __setup_rt_frame 函数中添加了shadow stack相关的操作函数, __setup_rt_frame 函

数会在信号处理过程中被调?,即上?信号处理期间进程变化的图中②的期间:

上?新增的 setup_signal_shadow_stack 函数,参数restorer即为前? __libc_sigaction 函数中提到的

__NR_rt_sigreturn 系统调?,且该参数后续会被push到shadow stack中去作为新的函数返回地址。 

相应地,再看 __NR_rt_sigreturn 系统调?的实现,该调?会在上?信号处理期间进程变化的图中④执?,CET

也在该处做了相应的改动:

从上? rt_sigreturn 新增代码结合 __setup_rt_frame 新增代码可知,两者是相互配合的:?个负责创建

restore token并在shadow stack设置返回地址,另?个则负责校验restore token并设置新的ssp,以此来兼容在

信号处理过程中数据栈切换、上下?切换的场景。 

?于为什么要在创建restore token后设置shadow stack返回地址,是因为在信号处理过程中执?完sa_handler?户?定义函数后,紧接着就会执?sa_restorer所设置的函数,因此在CET场景下需要在shadow stack设置相应的返回地址。

Fork

调?fork后,存在两种情况: 

1. ?进程和?进程分别有??的?块内存,不共享; 

2. ?进程和?进程共享同?块内存,为vfork。

因此,在shadow stack场景下,需要对fork系统调?做特殊处理。fork调?链如下:

CET在copy_thread函数中添加了相关代码:

从上?新增的代码可知,CET针对fork系统调?过程增加了创建新的shadow stack的部分,以兼容fork后??进程

不共享内存的情况。同时也对vfork后??进程共享内存的情况做了处理,使得不创建新的shadow stack以兼容相应场景。

Ucontext

ucontext涉及到协程相关的技术,该技术和系统调?在R3、R0间的切换?较类似。但是该技术作?于?户态,?

的是给?户态程序提供更快的切换效果,以及使得?户态的代码能够更加灵活。在?户态层?实现上下?切换。常?的函数为getcontext/setcontext:

setjmp/longjmp的技术原理和实现和ucontext类似,就不提及了。getcontext/setcontext具体实现都在glibc中。ucontext协程技术涉及到上下?切换的场景,也会存在数据栈切换的情况,因此,shadow stack也需要做出相应

的动作。 

先看shadow stack在getcontext中的改动,先? __NR_arch_prctl 系统调?获取当前shadow stack的基地址,其

次将其保存在SSP_BASE_OFFSET寄存器中,随后保存shadow stack基地址、ssp值在ucontext结构体中,供后续

setcontext使?:

再来看setcontext中的改动,校验getcontext保存的ucontext中的shadow stack基地址和ssp,再恢复,达到切换

回上?状态的?的:

上?getcontext/setcontext的场景,是在同?块shadow stack中实现切换,因为进程并没有创建新的数据栈。此外,makecontext会创建?个新的数据栈,开辟?个新的上下?,和上?的场景?有些许不同,makecontext和

setcontext也都做了相应的改动,由于篇幅原因不过多叙述,读者??阅读源码即可,技术原理都是?样的。

0x03 CET Bypass

CET在多场景下的实现还是相对复杂的,需要软件层?做相应的配合,因此在复杂的设计实现层?,是否有可能存

在绕过CET的可能性呢?本?节提出?个理论可?的?案供研究者参考。

Overwrite Function

该?法?较简单粗暴,篡改结构体中的函数指针来控制执?流。假设现有如下代码:


调?结构体函数(1)处的汇编代码如下:

此时有间接call,IBT机制会起作?,call rax后?条指令必须为ENDBR64。

如果此时拥有任意读写的能?,就可以篡改结构体str1的test函数指针为over_write(2)即可改变执?流。且此时

over_write函数的??点也是ENDBR64,即可绕过IBT的检查:

IBT机制会给绝?部分函数体的??点添加ENDBR指令,因此这种?法还是可?的,实际测试:

扩展?下,还可以利?JOP去做。例如使?以下序列,也可以绕过CET:

但是这种JOP序列实际上是?较稀少的,难找到。

Migrate Shadow Stack by RSTORSSP

这种?案利?了CET新增的指令来做?章。前?已经介绍过了RSTORSSP,?于shadow stack的切换,那么如果切

换到的是攻击者伪造的shadow stack呢? 

整个过程?较简单,步骤如下: 

1. 构造?块可控内存; 

2. 在可控内存中事先构造好返回地址,后续作为shadow stack使?;

3. 将内存转变为shadow stack;

4. 构造ROP;

5. ROP利?rstorssp将原shadow stack迁移到伪造的shadow stack中;

6. ROP执?system。

CET针对mmap和mprotect都做了相应的改动,在mmap中主要增加了?个VMA_FLAG为VM_SHADOW_STACK的

属性,在mprotect中除了PROT_READ/PROT_WRITE外增加了PROT_SHADOW_STACK(有?点是PROT_WRITE和

PROT_SHADOW_STACK不能同时使?,即只读),这两者是互相对应的关系。简单编写了这种?案的demo:

调试效果如下,可?当前已经将shadow stack切换到事先伪造的内存?中,且返回地址也篡改得和数据栈返回地址

相同,为0x41414141:


最终,RIP也能成功执?到控制的执?流:


不过这种?法在实际场景中构造的要求?较?,局限性?较?。

当然了,还有更粗暴的?法,CET新增指令还有?个WRSS的指令,该指令可以直接在shadow stack中写数据。但

是该指令需要在CPU上做使能操作,?前笔者阅读的源码暂时还没有使能,就不赘述了。

0x04 Summary

CET与以往软件实现的CFI不同,它从硬件侧寻找解决?案,在底层就将ROP掐断,对于软件CFI来说从性能、缓解效果?度来说都有着极?的提升。有得必有失,底层的变动必然会撬动上层随之变化,想要将这?缓解措施真正实

施落地,还有着很?的?段路要?。笔者略浅地研究了?番CET当前的实施进展,提出了部分攻防?向上的想法,

供后续研究者参考。我相信在不远的将来,CET的落地会给攻防带来很?的变化,到时候?将摩擦出怎样的?花?让我们?起期待吧。

0x05 Reference

  • https://github.com/yyu168/linux_cet/commit/72367656271aba4d29a25b38232e680ab9231a26 
  • https://ty-chen.github.io/linux-kernel-signal/ https://code.woboq.org/userspace/glibc/sysdeps/unix/sysv/linux/sigaction.c.html#__libc_sigaction 
  • https://man7.org/linux/man-pages/man2/signal.2.html 
  • https://code.woboq.org/userspace/glibc/sysdeps/unix/sysv/linux/x86_64/getcontext.S.html#137 
  • https://code.woboq.org/userspace/glibc/sysdeps/unix/sysv/linux/x86_64/setcontext.S.html#197 
  • https://man7.org/linux/man-pages/man3/getcontext.3.html 
  • https://lore.kernel.org/lkml/776fb081217145f4a488f7bca3e16eab@AcuMS.aculab.com/ 
  • https://github.com/hjl-tools/linux/commit/280503098ea762b3100edb30d60489a030d4abca

很赞哦! (119)