CET(CONTROL-FLOW ENFORCEMENT TECHNOLOGY)机制是 Intel 提出的⽤于缓解 ROP/JOP/COP 的新技术。因其具备“图灵完备”的攻击效果,ROP ⼀直是漏洞利⽤领域经常使⽤的攻击技术,在漏洞防御⽅⾯,针对 ROP 攻击技术也不断地在做新的尝试。例如微软的 CFG 缓解技术,虽然能够起到⼀定的缓解效果,但是在复杂场景的攻击下还不⾜够。CET 是⼀项基于硬件⽀持的解决⽅案,旨在预防前向( call/jmp )和后向( ret )控制流指令劫持。
本⽂将从 CET 的设计理念和实际效果出发,探索 CET 技术在攻防上带来的新变化。
ROP 全称为 Return-oriented programming ,是⼀种⾼级的内存攻击技术,且这种攻击技术难以检测。因为它利⽤了程序本身拥有的代码来执⾏精⼼构造的代码链。
ROP 依赖 RET 指令将多个段间的代码拼接在⼀起,组成⼀组完整的恶意代码。利⽤这个攻击⽅式前需要拥有覆盖返回地址的能⼒,其次攻击者再从程序代码段中去寻找可利⽤的代码序列段⽤于后续的构造拼接。
来看⼀个例⼦,假设程序中存在以下⼀段代码⽚段:
此时这⼀段正常的按顺序执⾏的三个指令是不存在 ret 指令的。但是,如果稍加偏移⼀下解释代码的地址时,就会导致出现完全不⼀样的指令,如下图所示:
如果按照红框中的顺序解释这些指令的时候,那么将会产⽣⾮代码预期的结果,会出现原代码中未出现过的 ret 指令以及 call 指令,这些指令序列被称为 gadget 。
通过仔细构造这些由ret指令终⽌的指令集,攻击者可以执⾏原程序中⾮预期的任意恶意代码,这种攻击被称为 ROP 攻击。
Intel 提出了⼀种基于硬件的 CET 解决⽅案,其中之⼀的 shadow stack 机制⽤于缓解 ROP 攻击。前⽂可以得知 ROP 依赖于 ret 指令,其中要执⾏的后续指令地址从堆栈中获得。因此 ROP 攻击的前提是攻击者能够在堆栈中构造数据。那么再来看 shadow stack 机制是怎么⼯作的。
CET 使操作系统能够创建⼀个 shadow stack (影⼦栈)。正常情况下,当执⾏ call 指令时,会将 call 指令后⼀条指令地址压栈。当启⽤了 shadow stack 后,会同时在普通数据栈和 shadow stack 中压⼊返回地址,随后在执⾏ ret 返回时,会将 shadow stack 中的返回地址和普通数据栈中的返回地址做对⽐,如匹配,则正常执⾏,如不匹配,则触发#CP(Controlflow Protection) 异常。如下图所示:
ssp 为 shadow stack 的栈顶指针寄存器,和 sp ⼀样。按图就是在执⾏ ret 指令时,会同时在两个栈 pop 返回地址,随后对⽐值是否相同。
也就是说,在攻击者拥有篡改堆栈数据的能⼒,利⽤ ROP 技术将返回地址篡改为构造的 gadget 时, shadowstack 机制能够完全缓解这种情况,因为执⾏ ret 指令时, pop 出的两个返回地址并不相同。
实际效果
使⽤如下 demo ⽤于测试:
看调试时的情况,此时返回地址已经被篡改:
再看 shadow stack 中的情况,返回地址为预期的 0x4005ff :
继续执⾏,会发现触发异常导致崩溃:
直接执⾏时触发的 #CP 异常:
实际应⽤上,也是可以证明⽬前来说 CET 中的 shadow stack 机制是能够有效缓解 ROP 攻击的。
JOP/COP 攻击⼿法与 ROP 类似,只不过是把 ROP 中以 ret 指令做跳板的关键点替换成了 call/jmp 指令。还是拿讲述 ROP 章节的两幅图举例,第⼆幅图中偏移解释字节码后出现了不同的指令,包含了:
这⼀指令序列,这就是 COP 中的 gadget ,以 call 指令为跳板,也就不需要 ret 指令的辅助了。
这种不需要 ret 指令的攻击场景下,前⾯所说的 shadow stack 机制就失效了。这种情况下, CET 的第⼆种机制 IBT(Indirect Branch Tracking) 就应运⽽⽣了。
IBT 的设计原理是通过编译器在合理的间接跳转( call/jmp )中⽤新的指令做标记,新指令包含 endb*** 和endbr64 。
举例来说,有如下图所示的⼀个间接跳转,编译器给 main和 foo 都添加了 endbr64 标记。
在执⾏间接跳转 call 时,如果 IBT 机制启⽤, CPU 会判断下⼀跳指令是否为 endb***/64 ,若是,则正常执⾏,若不是则触发 #CP 异常。
继续上图,当 call rdx 按正常流程⾛时,后⼀条指令就是 foo 函数的 endbr64 指令,程序会正常执⾏,如果此时攻击者篡改了 rdx 的值,将其指向 foo 中的 add rax, rbx 指令地址,则后续执⾏时 CPU 发现指令不为 endbr64 ,会触发 #CP 异常。
当然了,为了兼容以往的架构,启⽤了 IBT 的程序在不⽀持 IBT 的 CPU 上运⾏时也能够正常运⾏,这种情况下 endb***/64 指令会被视为 NOP 指令。
那么 CPU 是如何实现这种校验机制的呢?答案是⽤了 ENDBRANCH 状态机。
CPU 在⽤户态和内核态分别设有⼀个 ENDBRANCH 状态机,状态机共有两个状态,为 IDLE 和WAIT_FOR_ENDBRANCH 。初始状态都为 IDLE ,当执⾏间接跳转 call 的时候,状态机会从 IDLE变为WAIT_FOR_ENDBRANCH ,这时候就意味着下⼀条指令就必须为 endb***/64 ,如果是 endb***/64 ,状态机就会从 WAIT_FOR_ENDBRANCH 变回 IDLE ,如果不是则会触发 #CP 异常。
实际效果
使⽤如下 demo ⽤于测试:
再来看看 gdb 中的调试情况,当再次执⾏ stru1.ops 时,也就是间接跳转 call rax 时, ops 已经被篡改了:
篡改的地址为 shell 函数中的⼀⾏指令,并不为 endbr64 :
执⾏到篡改地址后继续执⾏,触发崩溃:
直接执⾏时触发的 #CP 异常:
同样地,实际应⽤上也是可以证明⽬前来说 CET 中的 IBT 机制是能够有效缓解 COP/JOP 攻击的。
以上就是 CET 的概述了。总的来说 CET 在硬件层⾯实现的缓解机制与以往的软件层⾯缓解机制有着⽐较⼤的不同,在缓解效果上⾯增强了许多。但是本质上 CET 是在最底层( CPU 硬件层⾯)做了改动,那么上层也仍然需要配合底层作出相应的改动:操作系统需要使能 CET 以及配合 CET 做检测,编译器需要给应⽤程序做使能 CET 标记...这些都是需要在以往软件、操作系统、编译器层⾯做较⼤改动的,同时还肩负着兼容性的问题。
那么在这种多层⾯的复杂场景下,能否找到绕过 CET 缓解机制的⽅法呢,让我们敬请期待吧。
https://www.intel.com/content/www/us/en/developer/articles/technical/technical-look-control-flow-enforcement-technology.html
https://firmianay.gitbooks.io/ctf-all-in-one/content/doc/4.5_defense_rop.html
https://defuse.ca/online-x86-assembler.htm#disassembly2
https://binpwn.com/papers/control-flow-enforcement-technology-preview.pdf
https://publishedprd.lanyonevents.com/published/rsaus18/sessionsFiles/8831/HTA-F01_Enhance%20Virtualization%20Stack%20with%20Intel%20CET%20and%20MPX.pdf
https://lpc.events/event/2/contributions/147/attachments/72/83/CET-LPC-2018.pdf
https://www.intel.com/content/dam/develop/external/us/en/documents/catc17-introduction-intel-cet-844137.pdf
本文作者:YDclub
本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/174875.html