Skip to content

Commit b3b48aa

Browse files
committed
update(trueblog): SHSTK
1 parent 559f84a commit b3b48aa

File tree

7 files changed

+204
-2
lines changed

7 files changed

+204
-2
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"server": "hexo server"
1010
},
1111
"hexo": {
12-
"version": "8.0.0"
12+
"version": "8.1.0"
1313
},
1414
"dependencies": {
1515
"hexo": "^8.0.0",
@@ -26,4 +26,4 @@
2626
"hexo-server": "^3.0.0",
2727
"hexo-theme-redefine": "^2.8.5"
2828
}
29-
}
29+
}

source/_posts/trueblog/shstk.md

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
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+
![shstk](/assets/trueblog/shstk/test_shstk.png)
82+
83+
可以看到SHSTK开启后直接阻止了ROP的执行。继续使用gdb尝试检查,就是在返回时崩溃的。
84+
85+
![crash](/assets/trueblog/shstk/shstk_crash.png)
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+
![no write](/assets/trueblog/shstk/no_write.png)
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+
![still no write](/assets/trueblog/shstk/still_no_write.png)
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+
![bypass](/assets/trueblog/shstk/clear_env.png)
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)
44.8 KB
Loading
52.7 KB
Loading
110 KB
Loading
51.8 KB
Loading
37.8 KB
Loading

0 commit comments

Comments
 (0)