模拟shadow_stack

模拟shadow_stack保护(可编译选项)


认识shadow_stack

什么是shadow stack

Shadow Stack(影子栈)是一种由硬件或操作系统直接管控的只读备份栈,专门用于同步存储主栈中的返回地址。其核心机制是:当程序执行CALL指令时,返回地址会同时压入主执行栈和影子栈;而执行RET指令时,系统会校验主栈弹出的返回地址与影子栈中的备份是否一致 —— 若不一致则立即终止执行,以此防御 ROP这类通过篡改主栈返回地址、拼接无害代码片段(gadget)来劫持程序执行流的攻击。

验证某程序是否在编译时启用 CET:

1
2
3
4
$ readelf -n <application> | grep -a SHSTK
Properties: x86 feature: IBT, SHSTK
$ readelf -n <application> | grep -a IBT
Properties: x86 feature: IBT, SHSTK

验证当前程序(cat)运行时是否已启用 Shadow Stack:(通常不会启用)

1
2
$ cat /proc/self/status | grep shstk
$ cat /proc/<pid>/status | grep shstk # pid 为 <pid> 的程序是否启用 Shadow Stack

HSTK 的主要逻辑就在 arch/x86/kernel/shstk.c

引用自[初探 Shadow Stack](初探 Shadow Stack - RiK’s Blog)

COOP攻击

Shadow Stack 启用后,传统的 ROP 等技术失效,但并非无法做到类似链式 gadgets 的任意代码执行。可以采用一种叫“伪面向对象编程”(Counterfeit Object-Oriented Programming / COOP)的新技术,其基本思路是在类似 C++ 的面向对象语言中通过修改虚函数表为 vfgadgets 来达到类似 ROP 的效果。vfgadget 分为 Main Loop Gadget(将其他 gadgets 串联起来)、Argument Loader Gadget(类似 ROP 中的 pop rdi; ret;)、Invoker Gadget(类似 ROP 中的 system)、Collector Gadget(存储 Invoker 的返回值)。

shadow_stack的实现 (引自linux内核)

大小

任务的影子堆栈从内存中分配到 MIN(RLIMIT_STACK, 4 GB) 的固定大小。

信号

启用shaow_stack后,执行call时的函数的返回地址会被同时push到shadow_stack上。

随后在ret时,内核会验证ssp(shadow_stack的栈顶指针)与call stack的返回地址。

关于用户态simulate_shadow_stack的设想

演示poc

由于笔者水平有限,所以只能尝试在用户态上分配一块内存来模拟shadow_stack

以下是笔者写的一个写入源码里面演示poc(初版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <sys/mman.h>

void *shadow_stack;
void **ssp;
void **sbp;

void create_stack()
{
shadow_stack = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (shadow_stack == MAP_FAILED)
{
perror("mmap failed");
exit(1);
}

sbp = (void **)((char *)shadow_stack + 4096);
ssp = sbp;
printf("Shadow stack created:\n");
printf(" Base address: %p\n", shadow_stack);
printf(" Stack base (high): %p\n", sbp);
printf(" Stack top (current): %p\n", ssp);
printf(" Stack size: 4096 bytes\n");
}

void push_addr()
{
void *ret_addr = __builtin_return_address(1);

if (ssp <= (void **)shadow_stack)
{
printf("Shadow stack overflow!\n");
exit(1);
}

ssp--;
*ssp = ret_addr;

printf("Pushed return address: %p to stack position: %p\n", ret_addr, ssp);
}

void check_addr()
{
void *curr_addr = __builtin_return_address(1);

if (ssp >= sbp)
{
printf("Shadow stack underflow!\n");
exit(1);
}

void *saved_addr = *ssp;
ssp++;

printf("Checking: current=%p, saved=%p\n", curr_addr, saved_addr);

if (curr_addr != saved_addr)
{
printf("Address mismatch detected! Potential ROP attack!\n");
printf("Expected: %p\n", saved_addr);
printf("Found: %p\n", curr_addr);
exit(1);
}
else
{
printf("Address check passed\n");
}
}

void __printf(const char *format, ...)
{
push_addr();
va_list args;
va_start(args, format);
vprintf(format, args);
va_end(args);
check_addr();
}

void __gets(char *buf)
{
push_addr();
gets(buf);
check_addr();
}

int main()
{
char buf[16];
create_stack();
push_addr();
__printf("Hello, World!\n");
__gets(buf);
check_addr();
return 0;
}

第二版poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <sys/mman.h>
#include <string.h>

#define FUNC_ENTRY() do { \
void *__ret_addr = __builtin_return_address(0); \
shadow_stack_push(__ret_addr); \
} while(0)

#define FUNC_EXIT() do { \
void *__ret_addr = __builtin_return_address(0); \
shadow_stack_check(__ret_addr); \
} while(0)

#define PROTECTED_FUNC_BEGIN() FUNC_ENTRY()
#define PROTECTED_FUNC_END() FUNC_EXIT()

void *shadow_stack;
void **ssp;
void **sbp;

void shadow_stack_init() {
shadow_stack = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (shadow_stack == MAP_FAILED) {
perror("[system]Shadow stack init failed");
exit(1);
}
sbp = (void **)((char *)shadow_stack + 4096);
ssp = sbp;
printf("[system]Shadow stack protection enabled\n");
printf("Shadow stack created:\n");
printf(" Base address: %p\n", shadow_stack);
printf(" Stack base (high): %p\n", sbp);
printf(" Stack top (current): %p\n", ssp);
printf(" Stack size: 4096 bytes\n");
}

void shadow_stack_push(void *ret_addr) {
if (ssp <= (void **)shadow_stack) {
printf("[system]Shadow stack overflow!\n");
exit(1);
}
ssp--;
*ssp = ret_addr;
}

void shadow_stack_check(void *curr_addr) {
if (ssp >= sbp) {
printf("[system]Shadow stack underflow!\n");
exit(1);
}

void *saved_addr = *ssp;
ssp++;

if (curr_addr != saved_addr) {
printf("[system]SECURITY ALERT: Return address tampering detected!\n");
printf("Expected: %p\n", saved_addr);
printf("Found: %p\n", curr_addr);
printf("Possible ROP/JOP attack blocked!\n");
exit(1);
}
}

void vulnerable_function() {
PROTECTED_FUNC_BEGIN();
char buffer[16];
gets(buffer);
PROTECTED_FUNC_END();
}

int main() {
shadow_stack_init();
PROTECTED_FUNC_BEGIN();
vulnerable_function();
PROTECTED_FUNC_END();
return 0;
}

初版脚本

写了一个脚本,把模拟影子栈的那部分源码放到了脚本里面,然后利用gcc里面的-finstrument-functions选项来帮助完成插入。利用这个脚本,任意一个c源码都可以快速加上模拟影子栈保护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
#!/bin/bash

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SHADOW_RUNTIME_DIR="$SCRIPT_DIR"
ORIGINAL_GCC="gcc"

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

show_help() {
echo "Shadow Stack Compiler Wrapper"
echo "用法: $0 [选项] 源文件..."
echo ""
echo "Shadow Stack 选项:"
echo " -shadow 启用影子栈保护"
echo " -shadow-debug 启用影子栈保护并显示调试信息"
echo " -shadow-silent 启用影子栈保护但不显示系统信息"
echo " -shadow-help 显示此帮助信息"
echo ""
echo "其他选项将直接传递给 GCC"
echo ""
echo "示例:"
echo " $0 -shadow test.c -o test"
echo " $0 -shadow-debug -g test.c -o test"
}

ENABLE_SHADOW=0
SHADOW_DEBUG=0
SHADOW_SILENT=0
GCC_ARGS=()
SOURCE_FILES=()

while [[ $# -gt 0 ]]; do
case $1 in
-shadow-help)
show_help
exit 0
;;
-shadow-debug)
ENABLE_SHADOW=1
SHADOW_DEBUG=1
shift
;;
-shadow-silent)
ENABLE_SHADOW=1
SHADOW_SILENT=1
shift
;;
-shadow)
ENABLE_SHADOW=1
shift
;;
*.c|*.cpp|*.cc|*.cxx)
SOURCE_FILES+=("$1")
GCC_ARGS+=("$1")
shift
;;
*)
GCC_ARGS+=("$1")
shift
;;
esac
done

if [[ $ENABLE_SHADOW -eq 1 ]]; then
echo -e "${GREEN}[Shadow Stack]${NC} 启用影子栈保护"

TEMP_DIR=$(mktemp -d)
trap "rm -rf $TEMP_DIR" EXIT

MODIFIED_FILES=()
for src_file in "${SOURCE_FILES[@]}"; do
if [[ -f "$src_file" ]]; then
base_name=$(basename "$src_file")
temp_file="$TEMP_DIR/$base_name"

echo -e "${BLUE}[Shadow Stack]${NC} 处理源文件: $src_file"
{
head -n 10 "$src_file" | grep -E '^#include'
cat << 'EOF'

// ==================== Shadow Stack Runtime ====================
#include <sys/mman.h>
#include <unistd.h>
#include <stdlib.h>

// 声明gets函数
char *gets(char *s);

// Shadow stack runtime
void *shadow_stack;
void **ssp;
void **sbp;
int shadow_stack_enabled = 0;
static int in_hook = 0;

// 简单的strlen实现,避免调用库函数
static size_t safe_strlen(const char *s) {
size_t len = 0;
while (s[len]) len++;
return len;
}

// 安全的写入函数,避免调用被插桩的库函数
static void safe_write(const char *msg) {
write(STDERR_FILENO, msg, safe_strlen(msg));
}

__attribute__((constructor)) void shadow_stack_init()
{
if (shadow_stack_enabled)
return;

shadow_stack = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (shadow_stack == MAP_FAILED)
{
perror("[system]Shadow stack init failed");
exit(1);
}
sbp = (void **)((char *)shadow_stack + 4096);
ssp = sbp;
shadow_stack_enabled = 1;
//printf("[system]Shadow stack protection enabled\n");
}

__attribute__((destructor)) void shadow_stack_cleanup()
{
if (shadow_stack_enabled)
{
shadow_stack_enabled = 0; // 先禁用,防止清理过程中的递归调用
if (shadow_stack != NULL) {
munmap(shadow_stack, 4096);
shadow_stack = NULL;
}
// 不在这里使用printf,因为可能导致段错误
}
}

void __fshadow_stack_push(void *ret_addr)
{
if (!shadow_stack_enabled || shadow_stack == NULL)
return;

if (ssp <= (void **)shadow_stack)
{
safe_write("[system]Shadow stack overflow!\n");
exit(1);
}
ssp--;
*ssp = ret_addr;
}

void __fshadow_stack_check(void *curr_addr)
{
if (!shadow_stack_enabled || shadow_stack == NULL)
return;

// 如果影子栈为空,直接返回而不报错(可能是程序正常退出)
if (ssp >= sbp)
{
return; // 静默处理空栈情况
}

void *saved_addr = *ssp;
ssp++;

if (curr_addr != saved_addr)
{
safe_write("\n=== SHADOW STACK SECURITY ALERT ===\n");
safe_write("Return address tampering detected!\n");
safe_write("Possible ROP/JOP attack blocked!\n");
safe_write("Process terminated for security.\n");
safe_write("===================================\n");
_exit(1);
}
}

// 编译器会自动插入这些调用
__attribute__((no_instrument_function)) void __cyg_profile_func_enter(void *this_fn, void *call_site)
{
if (!shadow_stack_enabled || in_hook)
return;

in_hook = 1; // 设置标志,防止递归

#ifdef SHADOW_DEBUG
if (call_site != NULL && call_site != (void *)0x1) {
safe_write("[DEBUG] Function enter - pushing to shadow stack\n");
} else {
safe_write("[DEBUG] Function enter - skipping (invalid call_site)\n");
}
#endif

// 只有当call_site有效时才推入影子栈
if (call_site != NULL && call_site != (void *)0x1 && shadow_stack != NULL)
{
__fshadow_stack_push(call_site);
}

in_hook = 0; // 清除标志
}

__attribute__((no_instrument_function)) void __cyg_profile_func_exit(void *this_fn, void *call_site)
{
if (!shadow_stack_enabled || in_hook)
return;

in_hook = 1; // 设置标志,防止递归

#ifdef SHADOW_DEBUG
if (call_site != NULL && call_site != (void *)0x1) {
safe_write("[DEBUG] Function exit - checking shadow stack\n");
} else {
safe_write("[DEBUG] Function exit - skipping (invalid call_site)\n");
}
#endif

// 只有当call_site有效时才检查影子栈
if (call_site != NULL && call_site != (void *)0x1 && shadow_stack != NULL)
{
__fshadow_stack_check(call_site);
}

in_hook = 0; // 清除标志
}

// ==================== End Shadow Stack Runtime ====================

EOF

tail -n +1 "$src_file" | grep -v '^#include'
} > "$temp_file"

MODIFIED_FILES+=("$temp_file")
fi
done

NEW_GCC_ARGS=()
for arg in "${GCC_ARGS[@]}"; do
found=0
for i in "${!SOURCE_FILES[@]}"; do
if [[ "$arg" == "${SOURCE_FILES[$i]}" ]]; then
NEW_GCC_ARGS+=("${MODIFIED_FILES[$i]}")
found=1
break
fi
done
if [[ $found -eq 0 ]]; then
NEW_GCC_ARGS+=("$arg")
fi
done

SHADOW_FLAGS=(
"-finstrument-functions"
)

if [[ $SHADOW_DEBUG -eq 1 ]]; then
SHADOW_FLAGS+=("-DSHADOW_DEBUG")
echo -e "${YELLOW}[Shadow Stack]${NC} 启用调试模式"
fi

if [[ $SHADOW_SILENT -eq 1 ]]; then
SHADOW_FLAGS+=("-DSHADOW_SILENT")
echo -e "${YELLOW}[Shadow Stack]${NC} 启用静默模式"
fi

FINAL_COMMAND=(
"$ORIGINAL_GCC"
"${SHADOW_FLAGS[@]}"
"${NEW_GCC_ARGS[@]}"
)

echo -e "${BLUE}[Shadow Stack]${NC} 执行编译命令:"
echo -e "${BLUE}[Shadow Stack]${NC} ${FINAL_COMMAND[*]}"

"${FINAL_COMMAND[@]}"
compile_result=$?

if [[ $compile_result -eq 0 ]]; then
echo -e "${GREEN}[Shadow Stack]${NC} 编译成功,影子栈保护已启用"
else
echo -e "${RED}[Shadow Stack]${NC} 编译失败"
fi

exit $compile_result
else
exec "$ORIGINAL_GCC" "${GCC_ARGS[@]}"
fi

安全版本(2025/9/9改)

上一版本的shadow_stack漏洞过多,比如可以通过其他漏洞修改shadow_stack_enable这个全局变量来关闭保护(可以用来出题),直接修改影子栈等问题。这个版本与canary类似引入了一个储存在fs:0x48段的一个随机key值,并把key与储存在shadow_stack上的返回地址进行加密,在比较的时候再引用key值来解密,这样就可以起到防止修改shadow_stack段的作用,同时修复了修改shadow_stack_enable这个全局变量来关闭保护的bug,现在只在保护初始化的时候使用这个全局变量。

poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>

char *gets(char *s);

void *shadow_stack;
void **ssp;
void **sbp;
static int in_hook = 0;

static size_t safe_strlen(const char *s) {
size_t len = 0;
while (s[len]) len++;
return len;
}

static void safe_write(const char *msg) {
write(STDERR_FILENO, msg, safe_strlen(msg));
}

static inline void write_fs_48(unsigned long value)
{
__asm__ volatile("movq %0, %%fs:0x48" : : "r"(value));
}

static inline unsigned long read_fs_48()
{
unsigned long key;
__asm__ volatile("movq %%fs:0x48, %0" : "=r"(key));
return key;
}

unsigned long read_urandom_key()
{
int fd;
unsigned long key = 0;
ssize_t bytes_read;
fd = open("/dev/urandom", O_RDONLY);
if (fd == -1)
{
safe_write("[ERROR] 无法打开/dev/urandom\n");
exit(-1);
}
bytes_read = read(fd, &key, sizeof(key));
if (bytes_read != sizeof(key))
{
safe_write("[WARNING]key读取出错\n");
exit(-1);
}
close(fd);
return key;
}

unsigned long generate_and_store_shadow_key()
{
static int initialized = 0;
if (!initialized)
{
unsigned long key = read_urandom_key();
write_fs_48(key);
unsigned long stored_key = read_fs_48();
if (stored_key != key)
{
exit(-1);
}
initialized = 1;
}

return read_fs_48();
}

__attribute__((destructor)) void restore_fs_original_value()
{
if (read_fs_48()) {
write_fs_48(0);
if (shadow_stack != NULL) {
munmap(shadow_stack, 4096);
shadow_stack = NULL;
}
}
}

__attribute__((constructor)) void shadow_stack_init()
{
if (read_fs_48())
return;

generate_and_store_shadow_key();

shadow_stack = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (shadow_stack == MAP_FAILED)
{
safe_write("[system]Shadow stack init failed\n");
exit(1);
}
sbp = (void **)((char *)shadow_stack + 4096);
ssp = sbp;
}

void __fshadow_stack_push_encrypted(void *ret_addr)
{
if (!read_fs_48() || shadow_stack == NULL)
return;

if (ssp <= (void **)shadow_stack)
{
safe_write("[system]Shadow stack overflow!\n");
exit(1);
}
void *encrypted_addr = (void *)((unsigned long)ret_addr ^ read_fs_48());
ssp--;
*ssp = encrypted_addr;
}

void __fshadow_stack_check_encrypted(void *curr_addr)
{
if (!read_fs_48() || shadow_stack == NULL)
return;

if (ssp >= sbp)
{
return;
}

void *encrypted_addr = *ssp;
void *decrypted_addr = (void *)((unsigned long)encrypted_addr ^ read_fs_48());
ssp++;

if (curr_addr != decrypted_addr)
{
safe_write("\n=== FS:0x48 SHADOW STACK SECURITY ALERT ===\n");
safe_write("Return address tampering detected!\n");
safe_write("FS:0x48-based encryption detected attack!\n");
safe_write("Possible ROP/JOP attack blocked!\n");
safe_write("Process terminated for security.\n");
safe_write("==========================================\n");
_exit(1);
}
}

#define PROTECTED_FUNC_BEGIN() \
do \
{ \
void *__ret_addr = __builtin_return_address(0); \
__fshadow_stack_push_encrypted(__ret_addr); \
} while (0)

#define PROTECTED_FUNC_END() \
do \
{ \
void *__ret_addr = __builtin_return_address(0); \
__fshadow_stack_check_encrypted(__ret_addr); \
} while (0)

void vulnerable_function()
{
PROTECTED_FUNC_BEGIN();
char buffer[16];
printf("输入数据: ");
gets(buffer);
printf("您输入的是: %s\n", buffer);
PROTECTED_FUNC_END();
}

int main()
{
PROTECTED_FUNC_BEGIN();
vulnerable_function();
PROTECTED_FUNC_END();
return 0;
}


脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
#!/bin/bash

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SHADOW_RUNTIME_DIR="$SCRIPT_DIR"
ORIGINAL_GCC="gcc"

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

show_help() {
echo "Shadow Stack Compiler Wrapper"
echo "用法: $0 [选项] 源文件..."
echo ""
echo "Shadow Stack 选项:"
echo " -shadow 启用影子栈保护"
echo " -shadow-debug 启用影子栈保护并显示调试信息"
echo " -shadow-silent 启用影子栈保护但不显示系统信息"
echo " -shadow-help 显示此帮助信息"
echo ""
echo "其他选项将直接传递给 GCC"
echo ""
echo "示例:"
echo " $0 -shadow test.c -o test"
echo " $0 -shadow-debug -g test.c -o test"
echo " $0 -shadow-silent test.c -o test"
echo ""
echo "注意:"
echo " - 必须包含至少一个 .c/.cpp/.cc/.cxx 源文件"
echo " - 使用影子栈选项时,源文件会被自动处理"
echo " - 不使用影子栈选项时,直接调用原始 GCC"
}

show_error() {
echo -e "${RED}错误: $1${NC}" >&2
echo ""
show_help
exit 1
}

ENABLE_SHADOW=0
SHADOW_DEBUG=0
SHADOW_SILENT=0
GCC_ARGS=()
SOURCE_FILES=()
UNKNOWN_SHADOW_OPTIONS=()

# 如果没有参数,显示帮助
if [[ $# -eq 0 ]]; then
show_help
exit 0
fi

while [[ $# -gt 0 ]]; do
case $1 in
-shadow-help)
show_help
exit 0
;;
-shadow-debug)
ENABLE_SHADOW=1
SHADOW_DEBUG=1
shift
;;
-shadow-silent)
ENABLE_SHADOW=1
SHADOW_SILENT=1
shift
;;
-shadow)
ENABLE_SHADOW=1
shift
;;
-shadow-*)
# 捕获未知的shadow选项
UNKNOWN_SHADOW_OPTIONS+=("$1")
shift
;;
*.c|*.cpp|*.cc|*.cxx)
SOURCE_FILES+=("$1")
GCC_ARGS+=("$1")
shift
;;
*)
GCC_ARGS+=("$1")
shift
;;
esac
done

# 检查未知的shadow选项
if [[ ${#UNKNOWN_SHADOW_OPTIONS[@]} -gt 0 ]]; then
show_error "未知的影子栈选项: ${UNKNOWN_SHADOW_OPTIONS[*]}"
fi

if [[ $ENABLE_SHADOW -eq 1 ]]; then
# 检查是否有源文件
if [[ ${#SOURCE_FILES[@]} -eq 0 ]]; then
show_error "启用影子栈保护时必须指定至少一个源文件 (.c/.cpp/.cc/.cxx)"
fi

echo -e "${GREEN}[Shadow Stack]${NC} 启用影子栈保护"

TEMP_DIR=$(mktemp -d)
trap "rm -rf $TEMP_DIR" EXIT

MODIFIED_FILES=()
for src_file in "${SOURCE_FILES[@]}"; do
if [[ -f "$src_file" ]]; then
base_name=$(basename "$src_file")
temp_file="$TEMP_DIR/$base_name"

echo -e "${BLUE}[Shadow Stack]${NC} 处理源文件: $src_file"
{
head -n 10 "$src_file" | grep -E '^#include'
cat << 'EOF'

// ==================== Shadow Stack Runtime ====================

#include <sys/mman.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>

char *gets(char *s);

// Shadow stack runtime
void *shadow_stack;
void **ssp;
void **sbp;
int shadow_stack_enabled = 0;
static int in_hook = 0;

static size_t safe_strlen(const char *s) {
size_t len = 0;
while (s[len]) len++;
return len;
}

static void safe_write(const char *msg) {
write(STDERR_FILENO, msg, safe_strlen(msg));
}

static inline void write_in(unsigned long value)
{
__asm__ volatile("movq %0, %%fs:0x48" : : "r"(value));
}

static inline unsigned long read_in()
{
unsigned long key;
__asm__ volatile("movq %%fs:0x48, %0" : "=r"(key));
return key;
}

unsigned long read_urandom_key()
{
int fd;
unsigned long key = 0;
ssize_t bytes_read;
fd = open("/dev/urandom", O_RDONLY);
if (fd == -1)
{
safe_write("[ERROR] 无法打开/dev/urandom\n");
exit(-1);
}
bytes_read = read(fd, &key, sizeof(key));
if (bytes_read != sizeof(key))
{
safe_write("[WARNING] key读取出错\n");
exit(-1);
}
close(fd);
return key;
}

unsigned long generate_and_store_shadow_key()
{
static int initialized = 0;
if (!initialized)
{
unsigned long key = read_urandom_key();
write_in(key);
unsigned long stored_key = read_in();
if (stored_key != key)
{
safe_write("[ERROR] 密钥验证失败\n");
exit(-1);
}
initialized = 1;

#ifndef SHADOW_SILENT
//safe_write("[system] 加密密钥已生成\n");
#endif
}
return read_in();
}

__attribute__((constructor)) void shadow_stack_init()
{
if (shadow_stack_enabled)
return;

generate_and_store_shadow_key();

shadow_stack = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (shadow_stack == MAP_FAILED)
{
safe_write("[system] Shadow stack init failed\n");
exit(1);
}
sbp = (void **)((char *)shadow_stack + 4096);
ssp = sbp;
shadow_stack_enabled = 1;

#ifndef SHADOW_SILENT
safe_write("[system] Shadow stack protection was enabled \n");
#endif
}

__attribute__((destructor)) void shadow_stack_cleanup()
{
if (shadow_stack_enabled)
{
shadow_stack_enabled = 0;
if (read_in())
{
write_in(0);
}

if (shadow_stack != NULL) {
munmap(shadow_stack, 4096);
shadow_stack = NULL;
}
}
}

void __fshadow_stack_push_encrypted(void *ret_addr)
{
if (shadow_stack == NULL || !read_in())
return;

if (ssp <= (void **)shadow_stack)
{
safe_write("[system] Shadow stack overflow!\n");
exit(1);
}

void *encrypted_addr = (void *)((unsigned long)ret_addr ^ read_in());
ssp--;
*ssp = encrypted_addr;

#ifdef SHADOW_DEBUG
safe_write("[DEBUG] 加密返回地址并推入影子栈\n");
#endif
}

void __fshadow_stack_check_encrypted(void *curr_addr)
{
if (shadow_stack == NULL || !read_in())
return;

if (ssp >= sbp)
{
return;
}

void *encrypted_addr = *ssp;
void *decrypted_addr = (void *)((unsigned long)encrypted_addr ^ read_in());
ssp++;

#ifdef SHADOW_DEBUG
safe_write("[DEBUG] 解密并检查返回地址\n");
#endif

if (curr_addr != decrypted_addr)
{
safe_write("\n=== SHADOW STACK SECURITY ALERT ===\n");
safe_write("Return address tampering detected!\n");
safe_write("Simulate shadow stack detected attack!\n");
safe_write("Possible ROP/JOP attack blocked!\n");
safe_write("Process terminated for security.\n");
safe_write("==========================================\n");
_exit(1);
}
}

void __fshadow_stack_push(void *ret_addr)
{
__fshadow_stack_push_encrypted(ret_addr);
}

void __fshadow_stack_check(void *curr_addr)
{
__fshadow_stack_check_encrypted(curr_addr);
}

__attribute__((no_instrument_function)) void __cyg_profile_func_enter(void *this_fn, void *call_site)
{
if (!shadow_stack_enabled || in_hook)
return;

in_hook = 1;

#ifdef SHADOW_DEBUG
if (call_site != NULL && call_site != (void *)0x1) {
safe_write("[DEBUG] Function enter - pushing encrypted address to shadow stack\n");
} else {
safe_write("[DEBUG] Function enter - skipping (invalid call_site)\n");
}
#endif

if (call_site != NULL && call_site != (void *)0x1 && shadow_stack != NULL)
{
__fshadow_stack_push_encrypted(call_site);
}

in_hook = 0;
}

__attribute__((no_instrument_function)) void __cyg_profile_func_exit(void *this_fn, void *call_site)
{
if (!shadow_stack_enabled || in_hook)
return;

in_hook = 1; // 设置标志,防止递归

#ifdef SHADOW_DEBUG
if (call_site != NULL && call_site != (void *)0x1) {
safe_write("[DEBUG] Function exit - checking encrypted shadow stack\n");
} else {
safe_write("[DEBUG] Function exit - skipping (invalid call_site)\n");
}
#endif

if (call_site != NULL && call_site != (void *)0x1 && shadow_stack != NULL)
{
__fshadow_stack_check_encrypted(call_site);
}

in_hook = 0;
}
// ==================== End Shadow Stack Runtime ====================

EOF

tail -n +1 "$src_file" | grep -v '^#include'
} > "$temp_file"

MODIFIED_FILES+=("$temp_file")
fi
done

NEW_GCC_ARGS=()
for arg in "${GCC_ARGS[@]}"; do
found=0
for i in "${!SOURCE_FILES[@]}"; do
if [[ "$arg" == "${SOURCE_FILES[$i]}" ]]; then
NEW_GCC_ARGS+=("${MODIFIED_FILES[$i]}")
found=1
break
fi
done
if [[ $found -eq 0 ]]; then
NEW_GCC_ARGS+=("$arg")
fi
done

SHADOW_FLAGS=(
"-finstrument-functions"
)

if [[ $SHADOW_DEBUG -eq 1 ]]; then
SHADOW_FLAGS+=("-DSHADOW_DEBUG")
echo -e "${YELLOW}[Shadow Stack]${NC} 启用调试模式"
fi

if [[ $SHADOW_SILENT -eq 1 ]]; then
SHADOW_FLAGS+=("-DSHADOW_SILENT")
echo -e "${YELLOW}[Shadow Stack]${NC} 启用静默模式"
fi

FINAL_COMMAND=(
"$ORIGINAL_GCC"
"${SHADOW_FLAGS[@]}"
"${NEW_GCC_ARGS[@]}"
)

echo -e "${BLUE}[Shadow Stack]${NC} 执行编译命令:"
echo -e "${BLUE}[Shadow Stack]${NC} ${FINAL_COMMAND[*]}"

"${FINAL_COMMAND[@]}"
compile_result=$?

if [[ $compile_result -eq 0 ]]; then
echo -e "${GREEN}[Shadow Stack]${NC} 编译成功,影子栈保护已启用"
else
echo -e "${RED}[Shadow Stack]${NC} 编译失败"
fi

exit $compile_result
else
exec "$ORIGINAL_GCC" "${GCC_ARGS[@]}"
fi

github链接:Sally-0000/simulate_shadow_stack: JNSEC2025暑期项目

模拟shadow_stack
http://example.com/2025/08/27/模拟shadow_stack保护(可编译选项)/
发布于
2025年8月27日
许可协议