┌───────────────────────┐
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
└───────────────────────┘
Intel Control-flow Enforcement Technology Bypass
~ CuB3y0nd
# 简介

[Intel Control-flow Enforcement Technology (CET)](https://en.wikipedia.org/wiki/
Control-flow_integrity) 由 [Shadow stack (SHSTK)](https://en.wikipedia.org/wiki/
Shadow_stack) 和 [Indirect branch tracking (IBT)](https://en.wikipedia.org/wiki/
Indirect_branch_tracking) 两部分组成,它们分别实现了不同的防护:

> The kernel must map a region of memory for the shadow stack not writable to us
er space programs except by special instructions. The shadow stack stores a copy
 of the return address of each CALL. On a RET, the processor checks if the retur
n address stored in the normal stack and shadow stack are equal. If the addresse
s are not equal, the processor generates an INT #21 (Control Flow Protection Fau
lt).
>
> Indirect branch tracking detects indirect JMP or CALL instructions to unauthor
ized targets. It is implemented by adding a new internal state machine in the pr
ocessor. The behavior of indirect JMP and CALL instructions is changed so that t
hey switch the state machine from IDLE to WAIT_FOR_ENDBRANCH. In the WAIT_FOR_EN
DBRANCH state, the next instruction to be executed is required to be the new END
BRANCH instruction (ENDBR32 in 32-bit mode or ENDBR64 in 64-bit mode), which cha
nges the internal state machine from WAIT_FOR_ENDBRANCH back to IDLE. Thus every
 authorized target of an indirect JMP or CALL must begin with ENDBRANCH. If the 
processor is in a WAIT_FOR_ENDBRANCH state (meaning, the previous instruction wa
s an indirect JMP or CALL), and the next instruction is not an ENDBRANCH instruc
tion, the processor generates an INT #21 (Control Flow Protection Fault). On pro
cessors not supporting CET indirect branch tracking, ENDBRANCH instructions are 
interpreted as NOPs and have no effect.

简单来说就是如下表所示的这些:

| 特性         | SHSTK                  | IBT                          |
| ------------ | ---------------------- | ---------------------------- |
| 所属体系     | Intel CET              | Intel CET                    |
| 防御重点     | 函数返回地址           | 间接跳转 / 调用              |
| 防御攻击类型 | ROP                    | JOP / COP                    |
| 检测时机     | 执行 RET 指令时        | 执行间接跳转后的下一条指令   |
| 硬件实现     | 维护独立的受保护栈空间 | 状态机检查特定的起始标记指令 |

# 检查硬件支持性

使用以下脚本测试 CPU 是否支持 SHSTK 和 IBT:

```c
#include <cpuid.h>
#include <stdint.h>
#include <stdio.h>

int cpu_supports_cet_shadow_stack() {
  uint32_t eax = 0, ebx = 0, ecx = 0, edx = 0;
  __cpuid_count(7, 0, eax, ebx, ecx, edx);
  return (ecx & (1 << 7)) != 0;
}

int cpu_supports_cet_ibt() {
  uint32_t eax = 0, ebx = 0, ecx = 0, edx = 0;
  __cpuid_count(7, 0, eax, ebx, ecx, edx);
  return (edx & (1 << 20)) != 0;
}

int main() {
  if (cpu_supports_cet_shadow_stack()) {
    puts("CET Shadow Stack is supported");
  }

  if (cpu_supports_cet_ibt()) {
    puts("CET IBT is supported");
  }
}
```

由于我的 CPU 是 `AMD Ryzen 7 4800H`,所以硬件层还没支持 CET,只能通过 patch 后的
 QEMU 模拟了。

# 模拟环境搭建

这里使用的是 [QEMU-8.2.2-CET: A Pseudo-Intel-CET Plugin of QEMU](https://github.
com/yikesoftware/qemu-8.2.2-cet),提供了一个伪 Intel CET 插件用于模拟 SHSTK 和 I
BT 。

这个项目最后更新在两年前,而我的内核版本,`gcc` 和 `glibc` 都比较新,所以还需要
自己 patch 几个地方才可以编译。

```plaintext showLineNumbers=false
λ ~/ uname -a
Linux Lux 6.18.6-zen1-1-zen #1 ZEN SMP PREEMPT_DYNAMIC Sun, 18 Jan 2026 00:33:55
 +0000 x86_64 GNU/Linux
λ ~/ ldd --version
ldd (GNU libc) 2.42
Copyright (C) 2024 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.
λ ~/ gcc --version
gcc (GCC) 15.2.1 20260103
Copyright (C) 2025 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
```

## Patch QEMU-8.2.2-CET

要改的地方不多,也就下面这三个文件:

```diff showLineNumbers=false
diff --git a/linux-user/strace.c b/linux-user/strace.c
index cf26e5526..a18ad1ee6 100644
--- a/linux-user/strace.c
+++ b/linux-user/strace.c
@@ -51,7 +51,8 @@ struct flags {
 };

 /* No 'struct flags' element should have a zero mask. */
-#define FLAG_BASIC(V, M, N)      { V, M | QEMU_BUILD_BUG_ON_ZERO(!(M)), N }
+// #define FLAG_BASIC(V, M, N)      { V, M | QEMU_BUILD_BUG_ON_ZERO(!(M)), N }
+#define FLAG_BASIC(V, M, N)      { V, M, N }

 /* common flags for all architectures */
 #define FLAG_GENERIC_MASK(V, M)  FLAG_BASIC(V, M, #V)
```

```diff showLineNumbers=false
diff --git a/linux-user/syscall.c b/linux-user/syscall.c
index 189eec0ec..e0205582c 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -365,18 +365,19 @@ _syscall3(int, sys_sched_getaffinity, pid_t, pid, unsigned
 int, len,
 _syscall3(int, sys_sched_setaffinity, pid_t, pid, unsigned int, len,
           unsigned long *, user_mask_ptr);
 /* sched_attr is not defined in glibc */
-struct sched_attr {
-    uint32_t size;
-    uint32_t sched_policy;
-    uint64_t sched_flags;
-    int32_t sched_nice;
-    uint32_t sched_priority;
-    uint64_t sched_runtime;
-    uint64_t sched_deadline;
-    uint64_t sched_period;
-    uint32_t sched_util_min;
-    uint32_t sched_util_max;
-};
+/* Use kernel-provided struct sched_attr */
+// struct sched_attr {
+//     uint32_t size;
+//     uint32_t sched_policy;
+//     uint64_t sched_flags;
+//     int32_t sched_nice;
+//     uint32_t sched_priority;
+//     uint64_t sched_runtime;
+//     uint64_t sched_deadline;
+//     uint64_t sched_period;
+//     uint32_t sched_util_min;
+//     uint32_t sched_util_max;
+// };
 #define __NR_sys_sched_getattr __NR_sched_getattr
 _syscall4(int, sys_sched_getattr, pid_t, pid, struct sched_attr *, attr,
           unsigned int, size, unsigned int, flags);
```

```diff showLineNumbers=false
diff --git a/linux-user/signal-common.h b/linux-user/signal-common.h
index 3e2dc604c..3f59ffe82 100644
--- a/linux-user/signal-common.h
+++ b/linux-user/signal-common.h
@@ -113,7 +113,9 @@ int process_sigsuspend_mask(sigset_t **pset, target_ulong si
gset,
 static inline void finish_sigsuspend_mask(int ret)
 {
     if (ret != -QEMU_ERESTARTSYS) {
-        TaskState *ts = (TaskState *)thread_cpu->opaque;
+        // TaskState *ts = (TaskState *)thread_cpu->opaque;
+        CPUState *cpu = current_cpu;
+        TaskState *ts = (TaskState *)cpu->opaque;
         ts->in_sigsuspend = 1;
     }
 }
```

## Build

然后进入项目根目录,执行以下指令来 build:

```shellsession frame=none showLineNumbers=false
mkdir build && cd build
../configure --enable-plugins --enable-seccomp --enable-tcg-interpreter --target
-list=x86_64-linux-user --disable-docs --disable-werror
make -j`nproc`
```

Build 完插件会生成在 `./build/tests/plugin/libcet.so`。

我们只需要创建一个软链接:

```shellsession frame=none showLineNumbers=false
ln -s ./build/qemu-x86_64 /path/to/qemu-x86_64-cet
ln -s ./build/tests/plugin/libcet.so /path/to/plugin/libcet.so
```

然后使用下面任意指令来执行编译出来带 SHSTK 或者 IBT 的程序进行测试:

```shellsession frame=none showLineNumbers=false
# without the plugin logs
/path/to/qemu-x86_64-cet -plugin /path/to/plugin/libcet.so,mode=user,ibt=on,ss=o
n,cpu_slots=128 ./test_cet

# with the plugin logs
/path/to/qemu-x86_64-cet -plugin /path/to/plugin/libcet.so,mode=user,ibt=on,ss=o
n,cpu_slots=128 -d plugin ./test_cet
```

# Challenge

```c
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void timedout(int) {
  puts("timedout");
  exit(0);
}

char g_buf[256];

int main() {
  char buf[16];
  long long int arg1 = 0;
  long long int arg2 = 0;
  void (*func)(long long int, long long int, long long int) = NULL;

  alarm(30);
  signal(SIGALRM, timedout);

  fgets(g_buf, 256, stdin); // My mercy
  fgets(buf, 256, stdin);
  if (func)
    func(arg1, arg2, 0);
}
```

题目代码如上,使用下面的脚本来编译:

```shellsession frame=none showLineNumbers=false
gcc chall.c -fno-stack-protector -fcf-protection=full -mshstk -fno-omit-frame-po
inter -static -o chall
```

## Write-up

TODO

## Exploit

TODO