gcc stack smashing protector(SSP)
Created:
Updated:
The stack smashing protector is a feature implemented in gcc. When entering function, it stores the return address and frame pointer on stack. And then, it stores a specific guard value called “canary” on stack. when exit function, it checkes whether this canary value has been changed or not. If the value has been changed, it means buffer overflow is occurred. In this case, it calls “stack_chk_fail()” function which print error message and exit the program.
There are gcc options which enable/disable stack smashing protector.
-
-fno-stack-protector : disables stack protection.
-
-fstack-protector : enables stack protection for vulnerable functions that contain buffers larger than 8 bytes. This includes functions that call “alloca()”.
-
-fstack-protector-all adds stack protection to all functions.
-
-fstack-protector-strong : like -fstack-protector. but it includes additional functions that have local array definitions, or have references to local frame address.
In this post, let’s see about -fstack-protector-all.
fstack-protector-all
source code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
int testFunc1()
{
int i = 3;
int buf[2] = {0,};
int j = 4;
printf("Input number: ");
scanf("%d", &i);
for (j = 0; j < i; j++) {
buf[j] = j;
printf("%s, %d\n", __func__, buf[j]);
}
return 0;
}
int main(void)
{
testFunc1();
return 0;
}
In this code, the element count of buf array is 2.
compile(generate executable file)
enables -fstack-protector-all.
1
$ arm-none-linux-gnueabihf-gcc -o stack_smashing_protector -static stack_smashing_protector.c -fstack-protector-all
compile(generate assembly code)
generates assembly code to see how stack smashing protector works.
1
$ arm-none-linux-gnueabihf-gcc -S -o stack_smashing_protector.s -static stack_smashing_protector.c -fstack-protector-all
output(with enabling -fstack-protector-all)
Here, input 3 or larger value to make buffer overflow.
1
2
3
4
5
6
7
8
$ qemu-arm stack_smashing_protector
Input number: 3
testFunc1, 0
testFunc1, 1
testFunc1, 2
*** stack smashing detected ***: <unknown> terminated
qemu: uncaught target signal 6 (Aborted) - core dumped
Aborted
Error message is printed and the program is exited.
generated assembly code
Let’s see “testFunc1” label
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
.arch armv7-a
.eabi_attribute 28, 1
.eabi_attribute 20, 1
.eabi_attribute 21, 1
.eabi_attribute 23, 3
.eabi_attribute 24, 1
.eabi_attribute 25, 1
.eabi_attribute 26, 2
.eabi_attribute 30, 6
.eabi_attribute 34, 1
.eabi_attribute 18, 4
.file "stack_smashing_protector.c"
.text
.section .rodata
.align 2
.LC0:
.ascii "Input number: \000"
.align 2
.LC1:
.ascii "%d\000"
.align 2
.LC2:
.ascii "%s, %d\012\000"
.align 2
.LC3:
.word __stack_chk_guard
.text
.align 1
.global testFunc1
.arch armv7-a
.syntax unified
.thumb
.thumb_func
.fpu neon
.type testFunc1, %function
testFunc1:
@ args = 0, pretend = 0, frame = 24
@ frame_needed = 1, uses_anonymous_args = 0
push {r7, lr}
sub sp, sp, #24
add r7, sp, #0
movw r3, #:lower16:.LC3
movt r3, #:upper16:.LC3
ldr r3, [r3]
str r3, [r7, #20]
mov r3,#0
movs r3, #3
str r3, [r7, #4]
add r3, r7, #12
vmov.i32 d16, #0 @ v8qi
vstr d16, [r3]
movs r3, #4
str r3, [r7, #8]
movw r0, #:lower16:.LC0
movt r0, #:upper16:.LC0
bl printf
adds r3, r7, #4
mov r1, r3
movw r0, #:lower16:.LC1
movt r0, #:upper16:.LC1
bl __isoc99_scanf
movs r3, #0
str r3, [r7, #8]
b .L2
.L3:
ldr r3, [r7, #8]
lsls r3, r3, #2
add r2, r7, #24
add r3, r3, r2
ldr r2, [r7, #8]
str r2, [r3, #-12]
ldr r3, [r7, #8]
lsls r3, r3, #2
add r2, r7, #24
add r3, r3, r2
ldr r3, [r3, #-12]
mov r2, r3
movw r1, #:lower16:__func__.5966
movt r1, #:upper16:__func__.5966
movw r0, #:lower16:.LC2
movt r0, #:upper16:.LC2
bl printf
ldr r3, [r7, #8]
adds r3, r3, #1
str r3, [r7, #8]
.L2:
ldr r3, [r7, #4]
ldr r2, [r7, #8]
cmp r2, r3
blt .L3
movs r3, #0
movw r2, #:lower16:.LC3
movt r2, #:upper16:.LC3
ldr r1, [r2]
ldr r2, [r7, #20]
eors r1, r2, r1
beq .L5
bl __stack_chk_fail
.L5:
mov r0, r3
adds r7, r7, #24
mov sp, r7
@ sp needed
pop {r7, pc}
.size testFunc1, .-testFunc1
.section .rodata
.align 2
.LC4:
.word __stack_chk_guard
.text
.align 1
.global main
.syntax unified
.thumb
.thumb_func
.fpu neon
.type main, %function
main:
@ args = 0, pretend = 0, frame = 8
@ frame_needed = 1, uses_anonymous_args = 0
push {r7, lr}
sub sp, sp, #8
add r7, sp, #0
movw r3, #:lower16:.LC4
movt r3, #:upper16:.LC4
ldr r3, [r3]
str r3, [r7, #4]
mov r3,#0
bl testFunc1
movs r3, #0
movw r2, #:lower16:.LC4
movt r2, #:upper16:.LC4
ldr r1, [r2]
ldr r2, [r7, #4]
eors r1, r2, r1
beq .L8
bl __stack_chk_fail
.L8:
mov r0, r3
adds r7, r7, #8
mov sp, r7
@ sp needed
pop {r7, pc}
.size main, .-main
.section .rodata
.align 2
.type __func__.5966, %object
.size __func__.5966, 10
__func__.5966:
.ascii "testFunc1\000"
.ident "GCC: (GNU Toolchain for the A-profile Architecture 9.2-2019.12 (arm-9.10)) 9.2.1 20191025"
.section .note.GNU-stack,"",%progbits
Here is a snippet of testFunc1 and relevant code. I added @comment here.
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
.LC3:
.word __stack_chk_guard
.text
.align 1
.global testFunc1
.arch armv7-a
.syntax unified
.thumb
.thumb_func
.fpu neon
.type testFunc1, %function
testFunc1:
@ args = 0, pretend = 0, frame = 24
@ frame_needed = 1, uses_anonymous_args = 0
push {r7, lr} @ push r7, lr on stack, THUMB2 mode uses r7 as frame pointer.
sub sp, sp, #24 @ push local variable/array on stack
add r7, sp, #0
movw r3, #:lower16:.LC3 @ mv lower16 of LC3 to r3
movt r3, #:upper16:.LC3 @ mv upper16 of LC3 to r3. This is __stack_chk_guard, which is an address canary value is stored.
ldr r3, [r3] @ load a canary value from address r3
str r3, [r7, #20] @ store a canary value on stack [r7 + #20]. This is an -4 smaller address than where r7 and lr stored.
mov r3,#0
movs r3, #3
str r3, [r7, #4] @ push variable i(3) on stack.
add r3, r7, #12
vmov.i32 d16, #0 @ v8qi
vstr d16, [r3] @ push buf[2] on stack
movs r3, #4
str r3, [r7, #8] @ push variable j(4) on stack
@ so, [r7, #20] is canary value, [r7, #4] is variable i
@ so, [r7, #8] is variable j, [r7, #12] array 'buf'
movw r0, #:lower16:.LC0
movt r0, #:upper16:.LC0
bl printf
adds r3, r7, #4
mov r1, r3
movw r0, #:lower16:.LC1
movt r0, #:upper16:.LC1
bl __isoc99_scanf
movs r3, #0
str r3, [r7, #8]
b .L2
.L2:
ldr r3, [r7, #4]
ldr r2, [r7, #8]
cmp r2, r3
blt .L3
movs r3, #0
movw r2, #:lower16:.LC3
movt r2, #:upper16:.LC3 @ mv LC3 to r3. This is __stack_chk_guard
ldr r1, [r2] @ load original canary value
ldr r2, [r7, #20] @ load canary value which was stored when entering function(testFunc1)
eors r1, r2, r1 @ check if both values are same or not by doing exclusive-or operation
beq .L5 @ if same, branch to .L5
bl __stack_chk_fail @ if not same, branch to __stack_chk_fail
.L5:
mov r0, r3
adds r7, r7, #24
mov sp, r7
@ sp needed
pop {r7, pc}
.size testFunc1, .-testFunc1
.section .rodata
.align 2
In gcc source code, __stack_chk_fail() is as below.
1
2
3
4
5
6
void
__stack_chk_fail (void)
{
const char *msg = "*** stack smashing detected ***: ";
fail (msg, strlen (msg), "stack smashing detected: terminated");
}
Leave a comment