我一直在尝试理解 GCC 生成的汇编语言代码,并且经常在许多函数的开头遇到此指令,包括_start()
,但找不到任何解释其目的的指南:
31-0000000000001040 <_start>:
32: 1040: f3 0f 1e fa endbr64
33- 1044: 31 ed xor ebp,ebp
我一直在尝试理解 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
功能用于确保您的间接分支实际转到有效位置。这允许额外的安全性。这是英特尔关于它的段落:
ENDBRANCH(详见第 73 节)是一条新指令,用于标记程序中间接调用和跳转的有效跳转目标地址。该指令操作码被选择为旧机器上的 NOP,这样使用 ENDBRANCH 新指令编译的程序在没有 CET 强制的情况下继续在旧机器上运行。在支持 CET 的处理器上,ENDBRANCH 仍然是 NOP,主要用作处理器流水线的标记指令以检测控制流违规。CPU 实现了一个跟踪间接 jmp 和调用指令的状态机。当看到这些指令之一时,状态机从 IDLE 移动到 WAIT_FOR_ENDBRANCH 状态。在 WAIT_FOR_ENDBRANCH 状态下,程序流中的下一条指令必须是 ENDBRANCH。
endbr64
(和endbr32
) 是英特尔控制流强制技术(CET)的一部分(另请参阅英特尔软件开发人员手册,第 1 卷,第 18 章)。
英特尔 CET 提供针对返回导向编程 (ROP)和跳转/面向调用编程 (JOP/COP)攻击的硬件保护,这些攻击操纵控制流以将现有代码重新用于恶意目的。
它的两大特点是
endbr64
是其中的一部分。虽然 CET 只是在当前的处理器中慢慢变得可用,但从 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
现在让我们简要地看一下两种情况。
无攻击:
some_function
check_login
从虚拟方法表 中检索虚拟方法的地址vtable
并调用它。TRACKER = WAIT_FOR_ENDBRANCH
) 上触发。endbr64
,因此间接调用被认为是“安全的”并继续执行(endbr64
仍然表现为无操作)。状态机被重置 ( TRACKER = IDLE
)。攻击:
攻击者以某种方式设法操纵vtable
现在vtable+8
指向authenticated
.
some_function
authenticated
从虚拟方法表中检索地址vtable
并调用它。TRACKER = WAIT_FOR_ENDBRANCH
) 上触发。mov byte [is_admin], 1
,而不是预期的endbr64
指令。CET 状态机推断控制流被操纵并引发#CP
故障,终止程序。如果没有 CET,控制流操作将被忽视,攻击者将获得管理员权限。
总之,英特尔 CET 的间接分支跟踪功能确保间接调用和跳转只能重定向到以endbr64
指令开头的函数。
请注意,这并不能确保调用了正确的函数——如果攻击者更改控制流以跳转到另一个同样开头的函数endbr64
,状态机将不会抱怨并继续执行程序。然而,这仍然大大减少了攻击面,因为大多数 JOP/COP 攻击针对的是中间功能指令(甚至直接“跳转到”指令)。