I was recently been asked by a friend how the Linux’s stack canary values work. After performing a quick online research I wasn’t able to find anything useful to give him. So, here is my writing on how GNU C Library stack canary values work. :)
WARNING: There is no exploitation information in this post. Just the GLIBC’s stack canary value’s functionality.
Let’s have a look at this dummy C code…
|
1
2
3
4
5
6
7
8
9
|
xorl:~$ cat ahoy.c
#include <stdio.h>
int
main(
void
)
{
return
printf
(
"Ahoy!\n"
);
}
xorl:~$
|
We’ll compile it using the -fstack-protector-all to force GCC into using the Stack Smashing Protection features it has.
|
1
2
3
4
5
|
xorl:~$ gcc -fstack-protector-all ahoy.c -o ahoy -ggdb2
xorl:~$ .
/ahoy
Ahoy!
xorl:~$ gdb -q .
/ahoy
(gdb)
|
Now it’s time to understand the code. So, here we are…
|
1
2
3
4
5
6
7
8
|
(gdb) disas main
0x080483f4 <main+0>: lea 0x4(%esp),%ecx
0x080483f8 <main+4>: and $0xfffffff0,%esp
0x080483fb <main+7>: pushl -0x4(%ecx)
0x080483fe <main+10>: push %ebp
0x080483ff <main+11>: mov %esp,%ebp
0x08048401 <main+13>: push %ecx
0x08048402 <main+14>: sub $0x14,%esp
|
Since this post is mostly written for people that are just getting started with system’s internals, I’ll explain everything. The address of the stack pointer at an offset of 0×4 is loaded to ECX, then stack pointer is aligned and the original stack pointer (ECX-4) is pushed into the stack. Then, the well known function prologue takes place. The current base pointer (indicating a new stack frame) is pushed onto the stack and value of the stack pointer is placed in the base pointer. Next, the current value of ECX (containing the original stack pointer’s value) is also pushed on the stack and stack pointer is decremented by 0×14 in order to make sufficient space for the function.
|
1
2
3
|
0x08048405 <main+17>: mov %gs:0x14,%eax
0x0804840b <main+23>: mov %eax,-0x8(%ebp)
0x0804840e <main+26>: xor %eax,%eax
|
This is the actual stack canary code in the binary. It will obtain the canary’s value from ‘%gs:0×14′ and store it in EAX register. It will then place it on the stack just after the stored, previously constructed stack frame. Then EAX is zeroed out since the canary value has no reason in being left there.
|
1
2
|
0x08048410 <main+28>: movl $0x8048500,(%esp)
0x08048417 <main+35>: call 0x8048320 <
printf
@plt>
|
This is the actual program that places the contents of the 0×8048500 to the stack and invokes printf(3) from the available PLT (the Procedure Linkage Table) entry. Obviously…
|
1
2
3
|
(gdb) x
/s
0x8048500
0x8048500:
"Ahoy!\n"
(gdb)
|
After the return of printf(3) library routine, SSP code performs the canary value check…
|
1
2
3
4
|
0x0804841c <main+40>: mov -0x8(%ebp),%edx
0x0804841f <main+43>: xor %gs:0x14,%edx
0x08048426 <main+50>: je 0x804842d <main+57>
0x08048428 <main+52>: call 0x8048330 <__stack_chk_fail@plt>
|
It retrieves the canary value from the stack and stores it in EDX register. Then compares it against ‘%gs:0×14′ which is the actual canary value. If they are equal it will use the ‘je’ (Jump if Equal) instruction to continue with the execution at the 0x804842d address. Otherwise, it will call the __stack_chk_fail() library routine through the PLT.
In the first case, the execution will continue from this point:
|
1
2
3
4
5
|
0x0804842d <main+57>: add $0x14,%esp
0x08048430 <main+60>: pop %ecx
0x08048431 <main+61>: pop %ebp
0x08048432 <main+62>: lea -0x4(%ecx),%esp
0x08048435 <main+65>: ret
|
The first instruction frees the allocated stack space and the next ones are a common function epilogue procedure. It restores the stored stack and frame pointer by popping them from the stack, updating the stack pointer’s value with the original one stored in ECX and calling ‘ret’ to complete the execution.
This is pretty much what’s going on with the SSP. Now, let’s have a look at the __stack_chk_fail() routine which is implemented in the GNU C Library and you can find it at debug/stack_chk_fail.c like this:
|
1
2
3
4
5
6
7
8
9
10
11
12
|
#include <stdio.h>
#include <stdlib.h>
extern
char
**__libc_argv attribute_hidden;
void
__attribute__ ((
noreturn
))
__stack_chk_fail (
void
)
{
__fortify_fail (
"stack smashing detected"
);
}
|
The attribute “noreturn” is used in functions that cannot return such as this one. So, the code is a simple wrapper around __fortify_fail(). This routine is available at debug/fortify_fail.c and here is its code:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
#include <stdio.h>
#include <stdlib.h>
extern
char
**__libc_argv attribute_hidden;
void
__attribute__ ((
noreturn
))
__fortify_fail (msg)
const
char
*msg;
{
/* The loop is added only to keep gcc happy. */
while
(1)
__libc_message (2,
"*** %s ***: %s terminated\n"
,
msg, __libc_argv[0] ?:
"<unknown>"
);
}
libc_hidden_def (__fortify_fail)
|
Once again it’s a non-return function and it will use the __libc_message() to print a simple message like this:
|
1
|
*** stack smashing detected ***: ./bla terminated
|
Just for your information, __libc_message() is a POSIX compatible library routine available at sysdeps/posix/libc_fatal.c. I won’t describe it since it’s a common error message printing code.
So, we have talked about what happens after we have an executable application using GCC’s SSP but how about what happens before?
Let’s have a look at the Run-Time Dynamic Linker (RTLD) that is located at etc/rtld.c…
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
#ifndef THREAD_SET_STACK_GUARD
/* Only exported for architectures that don't store the stack guard canary
in thread local area. */
uintptr_t
__stack_chk_guard attribute_relro;
#endif
/* Only exported for architectures that don't store the pointer guard
value in thread local area. */
uintptr_t
__pointer_chk_guard_local
attribute_relro attribute_hidden __attribute__ ((nocommon));
#ifndef THREAD_SET_POINTER_GUARD
strong_alias (__pointer_chk_guard_local, __pointer_chk_guard)
#endif
|
The GCC attribute ‘nocommon’ is used to directly allocate space for that variable and not store it in a ‘common’ storage area. A look at elf/stackguard-macros.h will reveal exactly how this variable is initialized…
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
#include <stdint.h>
#ifdef __i386__
# define STACK_CHK_GUARD \
({
uintptr_t
x; asm (
"movl %%gs:0x14, %0"
:
"=r"
(x)); x; })
#elif defined __x86_64__
# define STACK_CHK_GUARD \
({
uintptr_t
x; asm (
"movq %%fs:0x28, %0"
:
"=r"
(x)); x; })
...
#else
extern
uintptr_t
__stack_chk_guard;
# define STACK_CHK_GUARD __stack_chk_guard
#endif
|
Now, if we move to the security features initialization code of the RTLD we can read the following…
|
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
|
static
void
security_init (
void
)
{
/* Set up the stack checker's canary. */
uintptr_t
stack_chk_guard = _dl_setup_stack_chk_guard ();
#ifdef THREAD_SET_STACK_GUARD
THREAD_SET_STACK_GUARD (stack_chk_guard);
#else
__stack_chk_guard = stack_chk_guard;
#endif
/* Set up the pointer guard as well, if necessary. */
if
(GLRO(dl_pointer_guard))
{
// XXX If it is cheap, we should use a separate value.
uintptr_t
pointer_chk_guard = stack_chk_guard;
#ifndef HP_TIMING_NONAVAIL
hp_timing_t now;
HP_TIMING_NOW (now);
pointer_chk_guard ^= now;
#endif
#ifdef THREAD_SET_POINTER_GUARD
THREAD_SET_POINTER_GUARD (pointer_chk_guard);
#endif
__pointer_chk_guard_local = pointer_chk_guard;
}
}
|
As you can see, it uses _dl_setup_stack_chk_guard() to setup the stack canary value. Next, it will initialize the pointer check guard value with an XOR logical operation using either a high precision timer’s current value or the same stack canary value based on the compile time options. Since our main concern is the canary value, we should now have a look at _dl_setup_stack_chk_guard() which is located either at sysdeps/unix/sysv/linux/dl-osinfo.h or sysdeps/generic/dl-osinfo.h depending on the operating system.
The sysdeps/unix/sysv/linux/dl-osinfo.h is UNIX System V release dependent.
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
static
inline
uintptr_t
__attribute__ ((always_inline))
_dl_setup_stack_chk_guard (
void
)
{
uintptr_t
ret;
#ifdef ENABLE_STACKGUARD_RANDOMIZE
int
fd = __open (
"/dev/urandom"
, O_RDONLY);
if
(fd >= 0)
{
ssize_t reslen = __read (fd, &ret,
sizeof
(ret));
__close (fd);
if
(reslen == (ssize_t)
sizeof
(ret))
return
ret;
}
#endif
ret = 0;
unsigned
char
*p = (unsigned
char
*) &ret;
p[
sizeof
(ret) - 1] = 255;
p[
sizeof
(ret) - 2] =
'\n'
;
return
ret;
}
|
If StackGuard’s stack randomization is enabled, it will open ‘/dev/urandom’ and simply read an ‘uintptr_t’ that is stored in ‘ret’ variable. If __read() returned a value that is equal to size of ‘ret’ which means that the reading operation was successful it will simply return that value. If no stack randomization is enabled, it will set ‘p’ to the address of ‘ret’ variable and then change two Bytes of ret’s value to 0xff (255 in decimal) and 0xA (which is the newline character) and return that value. So, the canary value will always be ’0xff0a0000′ which is the so-called terminator canary value.
The generic implementation which resides at sysdeps/generic/dl-osinfo.h uses just this implementation…
|
1
2
3
4
5
6
7
8
9
10
11
12
|
#include <stdint.h>
static
inline
uintptr_t
__attribute__ ((always_inline))
_dl_setup_stack_chk_guard (
void
)
{
uintptr_t
ret = 0;
unsigned
char
*p = (unsigned
char
*) &ret;
p[
sizeof
(ret) - 1] = 255;
p[
sizeof
(ret) - 2] =
'\n'
;
p[0] = 0;
return
ret;
}
|
That will always provide a terminator canary value.
So, that was pretty much a quick journey around stack canary values of the GNU C Library. :)
本文详细介绍了GNU C库中堆栈保护机制的工作原理,包括堆栈保护符的生成、存储及验证过程。通过分析具体的C代码示例,解释了如何使用GCC编译选项来启用堆栈粉碎保护特性,并深入探讨了堆栈保护符在运行时动态链接器中的初始化方式。
751

被折叠的 条评论
为什么被折叠?



