endbr64 指令实际上做了什么?

What does the endbr64 instruction actually do?

我一直在努力理解 GCC 生成的汇编语言代码,并经常在包括 _start() 在内的许多函数的开头遇到这条指令,但找不到任何解释其用途的指南:

31-0000000000001040 <_start>:
32:    1040:    f3 0f 1e fa             endbr64 
33-    1044:    31 ed                   xor    ebp,ebp

它代表“End Branch 64 bit”——或者更准确地说,Terminate Indirect Branch in 64 bit。

操作如下:

IF EndbranchEnabled(CPL) & EFER.LMA = 1 & CS.L = 1
  IF CPL = 3
  THEN
    IA32_U_CET.TRACKER = IDLE
    IA32_U_CET.SUPPRESS = 0
  ELSE
    IA32_S_CET.TRACKER = IDLE
    IA32_S_CET.SUPPRESS = 0
  FI
FI;

否则该指令被视为 NOP

CET 功能用于确保您的间接分支实际到达有效位置。这允许额外的安全性。这是英特尔关于它的段落:

The ENDBRANCH (see Section 73 for details) is a new instruction that is used to mark valid jump target addresses of indirect calls and jumps in the program. This instruction opcode is selected to be one that is a NOP on legacy machines such that programs compiled with ENDBRANCH new instruction continue to function on old machines without the CET enforcement. On processors that support CET the ENDBRANCH is still a NOP and is primarily used as a marker instruction by the processor pipeline to detect control flow violations. The CPU implements a state machine that tracks indirect jmp and call instructions. When one of these instructions is seen, the state machine moves from IDLE to WAIT_FOR_ENDBRANCH state. In WAIT_FOR_ENDBRANCH state the next instruction in the program stream must be an ENDBRANCH. If an ENDBRANCH is not seen the processor causes a control protection exception (#CP), else the state machine moves back to IDLE state.

endbr64(和 endbr32)是 Intel's Control-Flow Enforcement Technology (CET) (see also Intel Software Developer Manual, Volume 1, Chapter 18 的一部分)。

英特尔 CET 提供针对 Return-oriented Programming (ROP) and Jump/Call-oriented Programming (JOP/COP) 攻击的硬件保护,这些攻击会操纵控制流以将现有代码重新用于恶意目的。

它的两大特点是

  • a shadow stack 用于跟踪 return 地址和
  • 间接分支跟踪,endbr64 是其中的一部分。

虽然 CET 在当前处理器一代中才慢慢可用,但它是 already supported as of GCC 8,它默认插入 endbrXX 指令。该操作码在较旧的处理器上被选择为空操作,这样如果不支持 CET,该指令将被忽略;在禁用间接分支跟踪的支持 CET 的处理器上也会发生同样的情况。


那么endbr64是做什么的呢?

前提条件:

  • 必须通过将控制寄存器标志 CR4.CET 设置为 1 来启用 CET。
  • IA32_U_CET(用户模式)或 IA32_S_CET(管理员模式)MSR 中设置了间接分支跟踪的适当标志。

CPU 设置了一个小型状态机来跟踪最后一个分支的类型。举个例子:

some_function:
    mov rax, qword [vtable+8]
    call rax
    ...

check_login:
    endbr64
    ...
authenticated:
    mov byte [is_admin], 1
    ...
    ret

现在让我们简要地看一下两种情况。

无攻击:

  1. some_functionvirtual method table vtable 中检索虚方法 check_login 的地址并调用它。
  2. 由于这是一个间接调用,CET 状态机被激活并设置为在下一条指令 (TRACKER = WAIT_FOR_ENDBRANCH) 上触发。
  3. 下一条指令是 endbr64,因此间接调用被认为是“安全的”并继续执行(endbr64 仍然表现为空操作)。状态机已重置 (TRACKER = IDLE)。

攻击力:
攻击者以某种方式设法操纵 vtable,使得 vtable+8 现在指向 authenticated

  1. some_function 从虚方法 table 中检索 authenticated 的地址 vtable 并调用它。
  2. 由于这是一个间接调用,CET 状态机被激活并设置为在下一条指令 (TRACKER = WAIT_FOR_ENDBRANCH) 上触发。
  3. 下一条指令是 mov byte [is_admin], 1,而不是预期的 endbr64 指令。 CET 状态机推断控制流被操纵并引发 #CP 错误,终止程序。

如果没有 CET,控制流操纵将不会被注意到,攻击者将获得管理员权限。


综上所述,英特尔 CET 的间接分支跟踪功能确保间接调用和跳转只能重定向到以 endbr64 指令开头的函数。

请注意,这 不会 确保调用 right 函数 - 如果攻击者更改控制流以跳转到另一个函数同样以 endbr64 开头,状态机不会抱怨并继续执行程序。然而,这仍然大大减少了攻击面,因为大多数 JOP/COP 攻击目标指令中间函数(甚至直接跳转到“指令”)。