首页
社区
课程
招聘
[原创]Intel CET缓解措施深度研究
发表于: 2022-5-6 16:01 5968

[原创]Intel CET缓解措施深度研究

2022-5-6 16:01
5968

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

  • f30K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6&6P5i4f1I4y4U0S2Q4x3V1k6D9K9h3&6#2P5q4)9#2k6X3y4W2N6q4)9J5c8X3y4G2L8h3#2A6N6q4)9J5c8U0M7J5x3K6j5%4y4U0f1$3x3U0M7I4j5h3u0S2y4r3b7J5z5h3p5J5y4h3t1K6z5o6t1K6x3X3f1$3z5o6m8S2j5U0V1J5x3K6q4S2x3U0k6Q4x3U0k6F1j5Y4y4H3i4K6y4n7

  • df4K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6@1P5g2)9J5k6r3y4Z5k6h3&6Q4x3X3g2Y4K9i4c8Z5N6h3u0Q4x3X3g2A6L8#2)9J5c8X3I4A6L8Y4g2^5i4K6u0V1K9$3g2J5L8X3g2D9i4K6u0V1M7$3W2Y4L8X3q4D9i4K6u0r3i4K6t1$3L8X3u0K6M7q4)9K6b7R3`.`.947K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6U0L8$3c8W2i4K6u0W2N6$3!0T1L8%4q4Q4x3X3g2G2M7X3N6Q4x3V1k6#2M7$3g2J5M7%4m8S2j5$3g2Q4x3V1k6Y4L8r3W2T1j5#2)9J5c8Y4y4&6M7$3c8W2M7s2y4Q4x3V1k6#2L8X3W2^5i4K6u0r3M7%4W2K6N6W2)9J5c8X3I4A6L8Y4g2^5i4K6u0r3M7$3W2Y4j5h3y4@1K9h3!0F1i4K6u0W2j5#2)9J5k6h3S2@1L8h3I4Q4x3U0y4Q4y4h3k6Q4y4h3k6D9K9h3u0U0i4K6g2X3M7$3W2Y4j5h3y4@1K9h3!0F1i4K6t1$3L8X3u0K6M7q4)9K6b7R3`.`.

  • 3acK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6E0j5h3^5%4i4K6u0W2L8%4u0Y4i4K6u0r3L8r3W2F1N6i4S2Q4x3V1k6E0j5h3&6Q4x3X3c8H3j5h3N6W2M7#2)9J5c8X3#2S2L8U0u0Q4x3V1k6K6K9h3N6F1j5h3I4Q4x3X3f1J5i4K6u0W2K9s2c8E0L8q4)9J5y4X3&6T1M7%4m8Q4x3@1t1`.

  • 4ffK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6U0L8$3c8W2i4K6u0W2N6$3!0T1L8%4q4Q4x3X3g2G2M7X3N6Q4x3V1k6#2M7$3g2J5M7%4m8S2j5$3g2Q4x3V1k6Y4L8r3W2T1j5#2)9J5c8Y4y4&6M7$3c8W2M7s2y4Q4x3V1k6#2L8X3W2^5i4K6u0r3M7%4W2K6N6W2)9J5c8X3I4A6L8Y4g2^5i4K6u0r3P5o6R3$3i4K6g2X3y4U0c8Q4x3V1k6Y4k6i4c8U0L8$3&6@1k6i4S2@1i4K6u0W2f1#2)9J5k6h3S2@1L8h3I4Q4x3U0x3I4x3K6N6Q4x3U0k6F1j5Y4y4H3i4K6y4n7

  • 0b1K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6U0L8$3c8W2i4K6u0W2N6$3!0T1L8%4q4Q4x3X3g2G2M7X3N6Q4x3V1k6#2M7$3g2J5M7%4m8S2j5$3g2Q4x3V1k6Y4L8r3W2T1j5#2)9J5c8Y4y4&6M7$3c8W2M7s2y4Q4x3V1k6#2L8X3W2^5i4K6u0r3M7%4W2K6N6W2)9J5c8X3I4A6L8Y4g2^5i4K6u0r3P5o6R3$3i4K6g2X3y4U0c8Q4x3V1k6K6k6i4c8U0L8$3&6@1k6i4S2@1i4K6u0W2f1#2)9J5k6h3S2@1L8h3I4Q4x3U0x3I4z5e0N6Q4x3U0k6F1j5Y4y4H3i4K6y4n7

  • 508K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6E0j5h3^5%4i4K6u0W2L8%4u0Y4i4K6u0r3L8r3W2F1N6i4S2Q4x3V1k6E0j5h3&6Q4x3X3c8H3j5h3N6W2M7#2)9J5c8X3#2S2L8U0y4Q4x3V1k6Y4k6i4c8U0L8$3&6@1k6i4S2@1i4K6u0W2x3#2)9J5k6h3S2@1L8h3I4Q4x3U0k6F1j5Y4y4H3i4K6y4n7

  • 518K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6D9L8%4u0W2i4K6u0W2K9$3g2J5L8X3g2D9i4K6u0W2L8%4u0Y4i4K6u0r3L8r3E0E0L8q4)9J5c8U0M7%4y4X3k6T1x3o6R3I4x3U0p5%4x3e0b7#2k6U0c8S2y4o6R3^5k6U0N6T1j5$3p5K6k6e0p5$3k6h3q4T1i4K6b7H3b7h3y4#2e0g2y4Q4x3X3g2S2j5%4g2D9j5h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6Q4x3U0k6F1j5Y4y4H3i4K6y4n7

  • f98K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6Z5K9X3I4Q4x3X3c8@1L8$3!0D9M7#2)9J5c8X3I4A6L8Y4g2^5i4K6u0r3j5$3!0E0L8h3W2@1i4K6u0r3x3U0R3H3y4e0l9K6x3o6V1^5k6h3p5%4y4U0u0T1x3K6p5H3x3r3g2V1j5U0x3H3k6o6j5H3y4o6R3&6j5e0l9K6x3r3b7@1j5h3u0U0j5b7`.`.



[培训]科锐逆向工程师培训第53期2025年7月8日开班!

收藏
免费 1
支持
分享
最新回复 (1)
雪    币: 2258
活跃值: (4916)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2022-5-6 16:06
0
游客
登录 | 注册 方可回帖
返回