|
| 1 | +--- |
| 2 | +title: 把 ROP 全都防出去:启用阴影栈 |
| 3 | +date: 2025/10/28 10:30:00 |
| 4 | +updated: 2025/10/28 10:30:00 |
| 5 | +tags: |
| 6 | + - tricks |
| 7 | + - ROP |
| 8 | +excerpt: >- |
| 9 | + 我们在这几年的题目里常常看见题目给的ELF里出现大量的`endbr64`指令, |
| 10 | + 但是我们总是无视它,毕竟它从来不起作用。那么这个指令原先设计的用意是什么呢? |
| 11 | + 直接搜索,我们发现它和Intel CET强相关,这是Intel引入的一项安全措施, |
| 12 | + 旨在尽量减少ROP/COP/JOP攻击。具体来说,要想开启CET,需要... |
| 13 | +thumbnail: /assets/trueblog/shstk/clear_env.png |
| 14 | +--- |
| 15 | + |
| 16 | +# 引入 |
| 17 | + |
| 18 | +我们在这几年的题目里常常看见题目给的ELF里出现大量的`endbr64`指令, |
| 19 | +但是我们总是无视它,毕竟它从来不起作用。那么这个指令原先设计的用意是什么呢? |
| 20 | +直接搜索,我们发现它和Intel CET强相关,这是Intel引入的一项安全措施, |
| 21 | +旨在尽量减少ROP/COP/JOP攻击。具体来说, **IBT** 措施会让处理器每次在执行 |
| 22 | +`call`、`jmp`等操作时,检查要跳转的地址有没有出现`endbr*`指令,否则就引发段错误。 |
| 23 | +**SHSTK** 会在`call`时向阴影栈中也压入返回地址,并在返回时检查栈上的返回地址是否和阴影栈上的一致。 |
| 24 | + |
| 25 | +{% note blue fa-book-bookmark %} |
| 26 | +**IBT**: Indirect Branch Tracking, 间接分支追踪; |
| 27 | +**SHSTK**: Shadow Stack, 阴影栈。 |
| 28 | +{% endnote %} |
| 29 | + |
| 30 | +# 在 Linux 中启用 CET |
| 31 | + |
| 32 | +现在的主线Linux已经支持CET了,但是开启需要大量的条件,具体可以看[这篇博客]。 |
| 33 | +省流: |
| 34 | + |
| 35 | +[这篇博客]: https://h3xduck.github.io/cfi/2025/06/26/enabling-intel-cet.html |
| 36 | + |
| 37 | +- 处理器支持:Intel 11代及以后;AMD 5000系以后 (AMD不支持IBT,只有SHSTK) |
| 38 | +- 内核支持:Linux 5.18+ 支持IBT;Linux 6.6+ 支持SHSTK(用户态支持) |
| 39 | +- 编译器支持:GCC 8.1+;Clang 11+ |
| 40 | +- 应用支持:需要编译时使用`-fcf-protection=full`编译 |
| 41 | +- 依赖库支持:所有依赖库必须使用以上旗标编译 |
| 42 | +- 运行时支持:通过在环境变量里设置`GLIBC_TUNABLES=glibc.cpu.hwcaps=SHSTK`通知LD启用CET |
| 43 | + |
| 44 | +满足所有条件后,才能打开CET保护。 |
| 45 | + |
| 46 | +{% note yellow fa-triangle-exclamation %} |
| 47 | +根据[Linux文档],SHSTK通过`arch_prctl(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK)`开启, |
| 48 | +但是我们不能自己开启,否则系统调用返回时阴影栈开启了,却没有返回条目,就会直接崩溃。 |
| 49 | + |
| 50 | +[Linux文档]: https://docs.kernel.org/next/x86/shstk.html |
| 51 | +{% endnote %} |
| 52 | + |
| 53 | +# 测试 SHSTK |
| 54 | + |
| 55 | +我的CPU是AMD CPU,只能测试SHSTK特性了。编写测试代码: |
| 56 | + |
| 57 | +```c test.c |
| 58 | +#include <stdlib.h> |
| 59 | +#include <stdio.h> |
| 60 | +#include <unistd.h> |
| 61 | +static void hack() { |
| 62 | + puts("HIJACKED!!!"); |
| 63 | + _exit(0); |
| 64 | +} |
| 65 | + |
| 66 | +int main(int argc, char **argv) { |
| 67 | + if (argc == 1) { |
| 68 | + puts("require a number"); |
| 69 | + return 1; |
| 70 | + } |
| 71 | + register int num = atoi(argv[1]); |
| 72 | + long buf[1]; |
| 73 | + for (register int i = 0; i < num; i++) |
| 74 | + buf[i] = (long)hack; |
| 75 | + return 0; |
| 76 | +} |
| 77 | +``` |
| 78 | +
|
| 79 | +我们将其编译后,尝试运行查看结果: |
| 80 | +
|
| 81 | + |
| 82 | +
|
| 83 | +可以看到SHSTK开启后直接阻止了ROP的执行。继续使用gdb尝试检查,就是在返回时崩溃的。 |
| 84 | +
|
| 85 | + |
| 86 | +
|
| 87 | +同时查看journalctl可以看到报错记录,由 *near ret* 引发。 |
| 88 | +
|
| 89 | +```plaintext |
| 90 | +10月 29 00:24:07 aRchOG kernel: a.out[22715] control protection ip:5597371d321d sp:7ffe7746dc18 ssp:7f9a21ffffe8 error:1(near ret) in a.out[121d,5597371d3000+1000] |
| 91 | +``` |
| 92 | + |
| 93 | +# 尝试绕过限制 |
| 94 | + |
| 95 | +有没有办法能绕过shstk的限制,执行rop呢?如果我们搜索call后的返回地址,发现它出现了两次, |
| 96 | +另一个出现在阴影栈上。阅读Linux源码,可知它对用户态是只读,内核态可读写。 |
| 97 | + |
| 98 | +```c linux/v6.17.5/source/arch/x86/kernel/shstk.c#L111 |
| 99 | +static unsigned long alloc_shstk(unsigned long addr, unsigned long size, |
| 100 | + unsigned long token_offset, bool set_res_tok) |
| 101 | +{ |
| 102 | + ... |
| 103 | + mapped_addr = do_mmap(NULL, addr, size, PROT_READ, flags, |
| 104 | + VM_SHADOW_STACK | VM_WRITE, 0, &unused, NULL); |
| 105 | + ... |
| 106 | +} |
| 107 | +``` |
| 108 | +
|
| 109 | +## 尝试直接写 rw 阴影栈 |
| 110 | +
|
| 111 | +经过测试,这个阴影栈刚好出现在libc的低地址处,并且在`maps`文件上显示为`rw-`权限。 |
| 112 | +难道可以直接写?编写poc测试,可以看到直接崩溃了。 |
| 113 | +
|
| 114 | +```c test.c |
| 115 | +#include <stdlib.h> |
| 116 | +#include <stdio.h> |
| 117 | +#include <unistd.h> |
| 118 | +
|
| 119 | +int main(int argc, char **argv) { |
| 120 | + char buf[0x20]; |
| 121 | + char *save = NULL; |
| 122 | + fgets(buf, 0x20, stdin); |
| 123 | + register long num = strtol(buf, &save, 0); |
| 124 | + if (save == buf) { |
| 125 | + puts("not a number"); |
| 126 | + return 1; |
| 127 | + } |
| 128 | + *(long *)num = 0xdeadbeef; |
| 129 | + return 0; |
| 130 | +} |
| 131 | +``` |
| 132 | + |
| 133 | + |
| 134 | + |
| 135 | +## 尝试先将阴影栈权限变更为 rw |
| 136 | + |
| 137 | +那我们先用`mprotect`将阴影栈变更为`rw-`呢?也不行。 |
| 138 | + |
| 139 | +```c test.c |
| 140 | +#include <assert.h> |
| 141 | +#include <stdlib.h> |
| 142 | +#include <stdio.h> |
| 143 | +#include <unistd.h> |
| 144 | +#include <sys/mman.h> |
| 145 | + |
| 146 | +int main(int argc, char **argv) { |
| 147 | + char buf[0x20]; |
| 148 | + char *save = NULL; |
| 149 | + fgets(buf, 0x20, stdin); |
| 150 | + register long num = strtol(buf, &save, 0); |
| 151 | + if (save == buf) { |
| 152 | + puts("not a number"); |
| 153 | + return 1; |
| 154 | + } |
| 155 | + long rc = mprotect((void *)num, 0x1000, PROT_READ | PROT_WRITE); |
| 156 | + assert(!rc); |
| 157 | + *(long *)num = 0xdeadbeef; |
| 158 | + return 0; |
| 159 | +} |
| 160 | +``` |
| 161 | +
|
| 162 | + |
| 163 | +
|
| 164 | +## 清除环境变量 |
| 165 | +
|
| 166 | +最后再尝试一次,由于要想启用SHSTK,必须存在这个环境变量,那如果我们预先清除这个环境变量, |
| 167 | +再次执行程序,会不会有变化呢?结果发现确实能关闭SHSTK。在SHSTK变成默认启用前, |
| 168 | +恐怕这是唯一的绕过方式。(如果默认启用的话恐怕就无法关闭了) |
| 169 | +
|
| 170 | +```c test.c |
| 171 | +#include <stdlib.h> |
| 172 | +#include <stdio.h> |
| 173 | +#include <unistd.h> |
| 174 | +static void hack() { |
| 175 | + puts("HIJACKED!!!"); |
| 176 | + _exit(0); |
| 177 | +} |
| 178 | +
|
| 179 | +int main(int argc, char **argv) { |
| 180 | + if (argc == 1) { |
| 181 | + unsetenv("GLIBC_TUNABLES"); |
| 182 | + execl("./a.out", "./a.out", "100"); |
| 183 | + } |
| 184 | + register int num = atoi(argv[1]); |
| 185 | + long buf[1]; |
| 186 | + for (register int i = 0; i < num; i++) |
| 187 | + buf[i] = (long)hack; |
| 188 | + return 0; |
| 189 | +} |
| 190 | +``` |
| 191 | + |
| 192 | + |
| 193 | + |
| 194 | +{% note blue fa-forward %} |
| 195 | +由于篇幅原因,没有展示代码正确性验证,实际上我自己测试过是正确的(例如修改只读段的权限, |
| 196 | +没有崩溃),也可以自己测试示例代码。 |
| 197 | +{% endnote %} |
| 198 | + |
| 199 | +# 参考 |
| 200 | + |
| 201 | +1. [How to enable Intel CET | h3xduck blog](https://h3xduck.github.io/cfi/2025/06/26/enabling-intel-cet.html) |
| 202 | +2. [Control-flow Enforcement Technology (CET) Shadow Stack](https://docs.kernel.org/next/x86/shstk.html) |
0 commit comments