CET与IBT

Posted by LXH on January 6, 2025

控制流劫持技术 (Control-Flow Hijacking)

篡改程序的正常执行流,来执行恶意代码(执行恶意的 shellcode 或代码复用攻击,如 ROP)

常见的实现方式:

  • 栈溢出修改返回地址
  • 修改函数指针,间接跳转劫持

简单形式:代码注入攻击-执行shellcode

进阶:代码重用攻击-ROP-返回式编程

用处:绕过DEP(数据执行保护)

  • Gadget:以ret指令为结尾的代码片段(程序中合法的代码片段)
  • ROP链:精心设计的gadgets

进阶:代码重用攻击-JOP-面向跳转的编程

控制流完整性(Control-Flow Integrity)

防御技术

栈canary

在函数栈帧的局部变量之后插入一个特殊的值,称为canary
在函数返回前检查这个值是否被篡改。若变化,说明可能发生了缓冲区溢出攻击,程序会中止执行

Windows平台下canary的编译支持与检验
  • 微软MSVC编译器开启/GS(Stack Guard)选项
  • 地址FS:[0x28](FS:段寄存器):表示当前线程的stack cookie,即canary
    • __readfsqword(0x28u):Windows平台下读取canary的方法
  • 检验:
    // 贴近编译器生成的伪代码
    unsigned __int64 callme()
    {
      unsigned __int64 __stack_canary = __readfsqword(0x28u);// 双下划线开头的变量,属于编译器变量
    
      // 函数主体
      // ...
    
      if (__readfsqword(0x28u) != __stack_canary)
      {
          __security_check_cookie_fail();  // Canary 值被篡改,可能存在栈溢出
      }
    
      return 0;  // 正常退出
    }
    
Linux平台下canary的编译支持与检验
  • GCC编译器指定-fstack-protector
  • 类似的,glibc 使用符号变量__stack_chk_guard作为当前线程的canary
  • 检验:
$ gcc -fstack-protector-all -o test test.c
$ objdump -d test | grep __stack_chk_guard
mov    rax,QWORD PTR fs:0x28
mov    QWORD PTR [rbp-0x8],rax     ; 保存 canary 到局部变量

...

mov    rax,QWORD PTR fs:0x28
xor    rax,QWORD PTR [rbp-0x8]
je     .Lreturn_ok
call   __stack_chk_fail            ; Canary 被破坏,执行终止

ASLR 地址空间布局随机化

启动ASLR选项

默认情况下,ASLR选项是关闭的🚫

  • 方法1️⃣:在系统进行初始程序装入(IPL)时,使用DIAGxx指定ASLR选项
  • 方法2️⃣:在IPL之后,设置SET DIAG=xx命令
    • 此时只会影响在启动ASLR选项之后启动的程序,不会影响先前已经启动的

DEP 数据执行保护

  • 限定内存页不能同时具有执行权限和写权限
  • 预防shellcode注入

基于硬件–控制流强制技术 CET

  • Control-flow Enforcement Technology
  • 基于硬件的
  • 预防控制流劫持攻击
    • 包括前向控制流劫持(JMP CALL)
    • 后向控制流劫持(RET)

      目前,64位内核中,仅支持用户空间影子栈技术和内核空间IBT技术

Shadow Stack-影子栈

在内存中分配第二个栈。影子栈是不能被应用直接修改的。 在执行函数调用(CALL指令)时,处理器将返回地址同时push原栈影子栈上。等函数返回时,比较影子栈返回地址原栈是否一致。若不同,则触发control protection fault

  • 影子栈的实质是映射一块私有、匿名、只读的内存
    • 私有。相对的是共享
    • 匿名页面。相对的是文件页面,即映射到文件的页面。
    • 只读:影子栈只读,只用来存储返回地址。

IBT(Indirect-branch-tracking)-间接分支追踪

  1. 间接分支
    • 依赖动态计算目标地址
    • 包括函数调用call、跳转jmpRET
    • 目标地址通常保存在指针里
    • 一旦攻击者恶意填充目标地址内容,即可劫持控制流。
  2. 间接分支追踪

    验证间接调用(CALL)或跳转(JMP)的目标是否使用endbr操作码标记。

  • endbr64用于64位;endbr32用于32位
  • 用于间接分支保护。编译器在每个可间接调用的函数入口插入endbr64指令。如果jmpcall跳转到非endbr开头的函数,则CPU触发异常。