Adventure With Stack Smashing Protector
Adventure With Stack Smashing Protector
Adventure With Stack Smashing Protector
11 November 2013
STDERR_FILENO
*/
First discovery in the very early stage is __libc_argv[0] cannot be trusted, because the memory area
where it's pointing to can be modified at the time of crash (each pointer on the stack may be corrupted).
Moving forward:
"sysdeps/unix/sysv/linux/libc_fatal.c"
/* Abort with an error message. */
void
__libc_message (int do_abort, const char *fmt, ...)
{
va_list ap;
va_list ap_copy;
int fd = -1;
va_start (ap, fmt);
va_copy (ap_copy, ap);
#ifdef FATAL_PREPARE
FATAL_PREPARE;
#endif
*/
if (next[0] == '\0')
break;
}
/* Determine what to print. */
const char *str;
size_t len;
if (cp[0] == '%' && cp[1] == 's')
{
str = va_arg (ap, const char *);
len = strlen (str);
cp += 2;
}
else
{
str = cp;
len = next - cp;
cp = next;
}
struct str_list *newp = alloca (sizeof (struct str_list));
newp->str = str;
newp->len = len;
newp->next = list;
list = newp;
++nlist;
}
bool written = false;
if (nlist > 0)
3
*/
*/
}
}
What is interesting in this function? At first, before function abort() is called a lot of code (too much
;)) is executed. This is not amazing idea, because corrupted process cannot be trusted, and execution of
any code (especially the code which relies on any pointer[s]) is unexpected.
Let's look closer for the following line:
const char *on_2 = __libc_secure_getenv ("LIBC_FATAL_STDERR_");
Which essentially executes:
"stdlib/secure-getenv.c"
char *
__libc_secure_getenv (name)
const char *name;
{
5
"/dev/tty"
STDERR_FILENO
*/
__builtin_alloca (size)
OK, so now let's analyze something more interesting. Let's look for the following code:
--- CUT --struct str_list *list = NULL;
int nlist = 0;
const char *cp = fmt;
while (*cp != '\0')
{
...
...
/* Determine what to print. */
const char *str;
size_t len;
if (cp[0] == '%' && cp[1] == 's')
{
str = va_arg (ap, const char *);
[1] len = strlen (str);
cp += 2;
}
...
...
}
bool written = false;
if (nlist > 0)
{
struct iovec *iov = alloca (nlist * sizeof (struct iovec));
ssize_t total = 0;
for (int cnt = nlist - 1; cnt >= 0; --cnt)
{
iov[cnt].iov_base = (void *) list->str;
iov[cnt].iov_len = list->len;
[2] total += list->len;
list = list->next;
9
11
*/
12
13
Where:
static _Unwind_Reason_Code (*unwind_backtrace) (_Unwind_Trace_Fn,
void *);
...
static void *libgcc_handle;
...
...
libgcc_handle = __libc_dlopen ("libgcc_s.so.1");
...
unwind_backtrace
=
__libc_dlsym
(libgcc_handle,
"_Unwind_Backtrace");
...
Before we move to the "_Unwind_Backtrace" function, lets see helper function passed as an
argument to it:
static _Unwind_Reason_Code
backtrace_helper (struct _Unwind_Context *ctx, void *a)
14
1]
==
arg-
Where:
unwind_getip = __libc_dlsym (libgcc_handle, "_Unwind_GetIP");
unwind_getcfa = (__libc_dlsym (libgcc_handle, "_Unwind_GetCFA")
?: dummy_getcfa);
inline _Unwind_Ptr
_Unwind_GetIP (struct _Unwind_Context *context)
{
return (_Unwind_Ptr) context->ra;
}
_Unwind_Word
_Unwind_GetCFA (struct _Unwind_Context *context)
{
return (_Unwind_Ptr) context->cfa;
}
In short, helper function is responsible for checking if there is any progress in stack unwinding by
analyzing CFA. It also prevents from the looping around the same frames.
Returning to the main unwinding function:
"libgcc/unwind.inc"
/* Perform stack backtrace through unwind data.
*/
_Unwind_Reason_Code LIBGCC2_UNWIND_ATTRIBUTE
_Unwind_Backtrace(_Unwind_Trace_Fn trace, void * trace_argument)
{
15
*/
*/
}
return code;
}
In short this function is responsible for setting up current frame state ("fs" variable) based on current
context. After that "context" is updated for the next frame and parsing starts again. This infinitive loop
will break if algorithm detects that current frame is the last one ("_URC_END_OF_STACK").
Lets move to the most important function in this algorithm:
"libgcc/unwind-dw2.c"
/* Given the _Unwind_Context CONTEXT for a stack frame, look up
the FDE for
its caller and decode it into FS. This function also sets the
args_size and lsda members of CONTEXT, as they are really
information
about the caller's frame. */
static _Unwind_Reason_Code
uw_frame_state_for
(struct
_Unwind_Context
_Unwind_FrameState *fs)
{
const struct dwarf_fde *fde;
const struct dwarf_cie *cie;
const unsigned char *aug, *insn, *end;
16
*context,
(context->ra
_Unwind_IsSignalFrame
&context->bases);
if (fde == NULL)
{
#ifdef MD_FALLBACK_FRAME_STATE_FOR
/* Couldn't find frame unwind info for this function.
target-specific
fallback
mechanism.
This
necessarily
not provide a personality routine or LSDA. */
return MD_FALLBACK_FRAME_STATE_FOR (context, fs);
#else
return _URC_END_OF_STACK;
#endif
}
Try a
will
fs->pc = context->bases.func;
cie = get_cie (fde);
insn = extract_cie_info (cie, context, fs);
if (insn == NULL)
/* CIE contained unknown augmentation. */
return _URC_FATAL_PHASE1_ERROR;
/* First decode all the insns in the CIE. */
end = (const unsigned char *) next_fde ((const struct dwarf_fde
*) cie);
execute_cfa_program (insn, end, context, fs);
/* Locate augmentation for the fde. */
aug = (const unsigned char *) fde + sizeof (*fde);
aug += 2 * size_of_encoded_value (fs->fde_encoding);
insn = NULL;
if (fs->saw_z)
{
_uleb128_t i;
aug = read_uleb128 (aug, &i);
insn = aug + i;
}
if (fs->lsda_encoding != DW_EH_PE_omit)
{
_Unwind_Ptr lsda;
aug = read_encoded_value (context, fs->lsda_encoding, aug,
&lsda);
17
*/
return _URC_NO_REASON;
}
If return address of current context is 0 (which is indicator for the end of stack) function immediately
returns. Otherwise complicated "_Unwind_Find_FDE" function is called. The main goal of it is to find
FDE object based on current context and return address:
const fde *
_Unwind_Find_FDE (void *pc, struct dwarf_eh_bases *bases)
{
struct object *ob;
const fde *f = NULL;
init_object_mutex_once ();
__gthread_mutex_lock (&object_mutex);
/* Linear search through the classified objects, to find the one
containing the pc. Note that pc_begin is sorted descending,
and
we expect objects to be non-overlapping. */
for (ob = seen_objects; ob; ob = ob->next)
if (pc >= ob->pc_begin)
{
f = search_object (ob, pc);
if (f)
goto fini;
break;
}
/* Classify and search the objects we've not yet processed.
while ((ob = unseen_objects))
{
struct object **p;
unseen_objects = ob->next;
f = search_object (ob, pc);
/* Insert the object into the classified list.
for (p = &seen_objects; *p ; p = &(*p)->next)
if ((*p)->pc_begin < ob->pc_begin)
break;
ob->next = *p;
18
*/
*/
We may
A quick
Try a
will
Both values are fully controllable. In the end of the unwinding loop, context is updated
("uw_update_context"). At this point new frame is found and parsed (using our fully controllable
data):
/* CONTEXT describes the unwind state for a frame, and FS describes
the FDE
of its caller. Update CONTEXT to refer to the caller as well.
Note
that the args_size and lsda members are not updated here, but
later in
uw_frame_state_for. */
static void
23
*context,
&&
context-
26
Random ideas
Not security related
OK, let's summarize what can be done (with SPP) from the non-security perspective:
1. We can change programs name (from SSP perspective) via overwriting memory region where
pointer to "argv[0]" points to.
2. We can crash Stack Smashing Protector code in many ways:
a. Via corrupting memory region pointed by "__environ" variable.
b. Via setting "LIBC_FATAL_STDERR_" to the edge of valid addresses.
c. Via forcing "alloca()" to fail e.g. stack exhaustion.
d. There is one more bug which Im analyzing more comprehensively at point 4. It may
indirectly force SSP to crash. It exists in DWARF stack (state) machine which is responsible
for gathering information about the stack trace ("__backtrace()") and prints it.
29
STDERR_FILENO
*/
size_t
which is the unsigned integer type of the result of the sizeof
operator; "
and reading further:
"The types used for size_t and ptrdiff_t should not have an integer
conversion rank greater than that of signed long int unless the
implementation supports objects large enough to make this
necessary."
30
31
Lazy practice
Im too lazy to test all possible scenarios thats why I did only few of them
At first let's create very simple vulnerable program:
#include <stdio.h>
int main(int argc, char *argv[]) {
char buf[100];
memset(buf,0x0,sizeof(buf));
if (argv[1])
strcpy(buf,argv[1]);
printf("DONE!\n");
return 0;
}
and compile with "-fstack-protector-all" flag:
[pi3@localhost ~]$ gcc test.c -o test -g -ggdb -fstack-protectorall
test.c: In function main:
test.c:7:4: warning: incompatible implicit declaration of builtin function memset [enabled by default]
memset(buf,0x0,sizeof(buf));
^
32
35
Crash 2d:
(gdb) r `perl -e 'print "A"x300'`
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/pi3/test `perl -e 'print "A"x300'`
DONE!
*** stack smashing detected ***: /home/pi3/test terminated
Program received signal SIGSEGV, Segmentation fault.
x86_64_fallback_frame_state (context=0x7fffffffd3a0,
context=0x7fffffffd3a0, fs=0x7fffffffd490) at ./md-unwindsupport.h:58
58
if (*(unsigned char *)(pc+0) == 0x48
(gdb) bt
#0 x86_64_fallback_frame_state (context=0x7fffffffd3a0,
context=0x7fffffffd3a0, fs=0x7fffffffd490)
at ./md-unwind-support.h:58
#1 uw_frame_state_for (context=context@entry=0x7fffffffd3a0,
fs=fs@entry=0x7fffffffd490)
at ../../../libgcc/unwind-dw2.c:1253
#2 0x00000035c400ff19 in _Unwind_Backtrace (trace=0x35c1909bc0
<backtrace_helper>, trace_argument=0x7fffffffd650)
at ../../../libgcc/unwind.inc:290
#3 0x00000035c1909d36 in __GI___backtrace
(array=array@entry=0x7fffffffd830, size=size@entry=64)
at ../sysdeps/x86_64/backtrace.c:109
#4 0x00000035c1875d64 in __libc_message
(do_abort=do_abort@entry=2,
fmt=fmt@entry=0x35c197d302 "*** %s ***: %s terminated\n") at
../sysdeps/unix/sysv/linux/libc_fatal.c:176
#5 0x00000035c190d6b7 in __GI___fortify_fail
(msg=msg@entry=0x35c197d2ea "stack smashing detected") at
fortify_fail.c:31
#6 0x00000035c190d680 in __stack_chk_fail () at
stack_chk_fail.c:28
#7 0x00000000004006b1 in main (argc=2, argv=0x7fffffffe0b8) at
test.c:15
(gdb) print context->ra
$14 = (void *) 0x4141414141414141
(gdb) x/i $rip
=> 0x35c400f018 <uw_frame_state_for+1080>: cmpb
$0x48,(%rcx)
(gdb) i r rcx
rcx
0x4141414141414141
4702111234474983745
(gdb) list 50
45 x86_64_fallback_frame_state (struct _Unwind_Context *context,
37
Scenario 3a:
[pi3@localhost ~]$ gdb -q -p 16473
Attaching to process 16473
Reading symbols from /home/pi3/test...done.
Reading symbols from /lib64/libc.so.6...Reading symbols from
/usr/lib/debug/lib64/libc-2.17.so.debug...done.
done.
Loaded symbols for /lib64/libc.so.6
Reading symbols from /lib64/ld-linux-x86-64.so.2...Reading
symbols from /usr/lib/debug/lib64/ld-2.17.so.debug...done.
done.
Loaded symbols for /lib64/ld-linux-x86-64.so.2
0x00000035c18e7650 in __read_nocancel () at
../sysdeps/unix/syscall-template.S:81
81 T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)
(gdb) break libc_fatal.c:66
Breakpoint 1 at 0x35c1875a37: file
../sysdeps/unix/sysv/linux/libc_fatal.c, line 66.
(gdb) c
Continuing.
Breakpoint 1, __libc_message (do_abort=do_abort@entry=2,
fmt=fmt@entry=0x35c197d302 "*** %s ***: %s terminated\n")
at ../sysdeps/unix/sysv/linux/libc_fatal.c:66
66
const char *on_2 = __libc_secure_getenv
("LIBC_FATAL_STDERR_");
(gdb) list
61
FATAL_PREPARE;
62 #endif
38
39
-al /proc/16473/fd
0
0
64
64
64
64
Sep
Sep
Sep
Sep
Sep
Sep
29
29
29
29
29
29
11:02
11:02
11:06
11:06
11:02
11:06
.
..
0 ->
1 ->
2 ->
3 ->
/dev/pts/3
/dev/pts/3
/dev/pts/3
/dev/tty
Scenario 3b:
[pi3@localhost ~]$ gdb -q -p 16531
Attaching to process 16531
Reading symbols from /home/pi3/test...done.
Reading symbols from /lib64/libc.so.6...Reading symbols from
/usr/lib/debug/lib64/libc-2.17.so.debug...done.
done.
Loaded symbols for /lib64/libc.so.6
Reading symbols from /lib64/ld-linux-x86-64.so.2...Reading
symbols from /usr/lib/debug/lib64/ld-2.17.so.debug...done.
done.
Loaded symbols for /lib64/ld-linux-x86-64.so.2
0x00000035c18e7650 in __read_nocancel () at
../sysdeps/unix/syscall-template.S:81
81 T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)
(gdb) break libc_fatal.c:66
Breakpoint 1 at 0x35c1875a37: file
../sysdeps/unix/sysv/linux/libc_fatal.c, line 66.
(gdb) break libc_fatal.c:67
Breakpoint 2 at 0x35c1875a9a: file
../sysdeps/unix/sysv/linux/libc_fatal.c, line 67.
(gdb) c
Continuing.
Breakpoint 1, __libc_message (do_abort=do_abort@entry=2,
fmt=fmt@entry=0x35c197d302 "*** %s ***: %s terminated\n")
at ../sysdeps/unix/sysv/linux/libc_fatal.c:66
66
const char *on_2 = __libc_secure_getenv
("LIBC_FATAL_STDERR_");
(gdb) break libc_fatal.c:70
Breakpoint 3 at 0x35c1875abb: file
../sysdeps/unix/sysv/linux/libc_fatal.c, line 70.
(gdb) print on_2
$1 = <optimized out>
(gdb) c
Continuing.
Breakpoint 2, __libc_message (do_abort=do_abort@entry=2,
fmt=fmt@entry=0x35c197d302 "*** %s ***: %s terminated\n")
40
-al /proc/16531/fd
0
0
64
64
64
Sep
Sep
Sep
Sep
Sep
29
29
29
29
29
11:11
11:11
11:14
11:14
11:11
.
..
0 -> /dev/pts/3
1 -> /dev/pts/3
2 -> /dev/pts/3
Scenario 4a:
At the beginning lets just prove that simple return address overflow might lead to read-AV crash. First
overflow cookie without touching return address:
[pi3@localhost ~]$ gdb -q ./test
Reading symbols from /home/pi3/test...(no debugging symbols
found)...done.
(gdb) r `perl -e 'print "A"x120'`
Starting program: /home/pi3/test `perl -e 'print "A"x120'`
DONE!
*** stack smashing detected ***: /home/pi3/test terminated
======= Backtrace: =========
/lib64/libc.so.6(__fortify_fail+0x37)[0x35c190d6b7]
/lib64/libc.so.6(__fortify_fail+0x0)[0x35c190d680]
/home/pi3/test[0x4006b1]
/lib64/libc.so.6(__libc_start_main+0x80)[0x35c1821b00]
/home/pi3/test[0x400569]
======= Memory map: ========
00400000-00401000 r-xp 00000000 fd:02 262194
/home/pi3/test
00600000-00601000 r--p 00000000 fd:02 262194
/home/pi3/test
00601000-00602000 rw-p 00001000 fd:02 262194
/home/pi3/test
00602000-00623000 rw-p 00000000 00:00 0
[heap]
35c1000000-35c1021000 r-xp 00000000 fd:01 1061612
/usr/lib64/ld-2.17.so
35c1220000-35c1221000 r--p 00020000 fd:01 1061612
/usr/lib64/ld-2.17.so
35c1221000-35c1222000 rw-p 00021000 fd:01 1061612
/usr/lib64/ld-2.17.so
41
}
}
43
fs->regs.reg[3].how = REG_SAVED_OFFSET;
fs->regs.cfa_offset = new_cfa - (long) context->cfa;
fs->regs.reg[0].loc.offset = (long)&sc->rax - new_cfa;
fs->regs.reg[1].loc.offset = (long)&sc->rdx - new_cfa;
fs->regs.cfa_offset = new_cfa - (long) context->cfa;
fs->regs.reg[0].loc.offset = (long)&sc->rax - new_cfa;
fs->regs.reg[4].how = REG_SAVED_OFFSET;
fs->regs.reg[1].loc.offset = (long)&sc->rdx - new_cfa;
fs->regs.reg[5].how = REG_SAVED_OFFSET;
fs->regs.reg[6].how = REG_SAVED_OFFSET;
fs->regs.reg[8].loc.offset = (long)&sc->r8 - new_cfa;
fs->regs.reg[1].loc.offset = (long)&sc->rdx - new_cfa;
fs->regs.reg[2].loc.offset = (long)&sc->rcx - new_cfa;
fs->regs.reg[8].loc.offset = (long)&sc->r8 - new_cfa;
fs->regs.reg[9].loc.offset = (long)&sc->r9 - new_cfa;
fs->regs.reg[8].how = REG_SAVED_OFFSET;
fs->regs.reg[2].loc.offset = (long)&sc->rcx - new_cfa;
fs->regs.reg[9].how = REG_SAVED_OFFSET;
fs->regs.reg[10].how = REG_SAVED_OFFSET;
fs->regs.reg[9].loc.offset = (long)&sc->r9 - new_cfa;
fs->regs.reg[2].loc.offset = (long)&sc->rcx - new_cfa;
fs->regs.reg[3].loc.offset = (long)&sc->rbx - new_cfa;
fs->regs.reg[9].loc.offset = (long)&sc->r9 - new_cfa;
fs->regs.reg[10].loc.offset = (long)&sc->r10 - new_cfa;
fs->regs.reg[11].how = REG_SAVED_OFFSET;
fs->regs.reg[3].loc.offset = (long)&sc->rbx - new_cfa;
49
fs->regs.reg[12].how = REG_SAVED_OFFSET;
fs->regs.reg[13].how = REG_SAVED_OFFSET;
fs->regs.reg[10].loc.offset = (long)&sc->r10 - new_cfa;
fs->regs.reg[3].loc.offset = (long)&sc->rbx - new_cfa;
fs->regs.reg[4].loc.offset = (long)&sc->rsi - new_cfa;
fs->regs.reg[10].loc.offset = (long)&sc->r10 - new_cfa;
fs->regs.reg[11].loc.offset = (long)&sc->r11 - new_cfa;
fs->regs.reg[14].how = REG_SAVED_OFFSET;
fs->regs.reg[4].loc.offset = (long)&sc->rsi - new_cfa;
fs->regs.reg[15].how = REG_SAVED_OFFSET;
fs->regs.reg[11].loc.offset = (long)&sc->r11 - new_cfa;
fs->regs.reg[4].loc.offset = (long)&sc->rsi - new_cfa;
fs->regs.reg[5].loc.offset = (long)&sc->rdi - new_cfa;
fs->regs.reg[11].loc.offset = (long)&sc->r11 - new_cfa;
fs->regs.reg[12].loc.offset = (long)&sc->r12 - new_cfa;
fs->regs.reg[5].loc.offset = (long)&sc->rdi - new_cfa;
fs->regs.reg[12].loc.offset = (long)&sc->r12 - new_cfa;
fs->regs.reg[5].loc.offset = (long)&sc->rdi - new_cfa;
fs->regs.reg[6].loc.offset = (long)&sc->rbp - new_cfa;
fs->regs.reg[12].loc.offset = (long)&sc->r12 - new_cfa;
fs->regs.reg[13].loc.offset = (long)&sc->r13 - new_cfa;
fs->regs.reg[6].loc.offset = (long)&sc->rbp - new_cfa;
fs->regs.reg[13].loc.offset = (long)&sc->r13 - new_cfa;
fs->regs.reg[6].loc.offset = (long)&sc->rbp - new_cfa;
fs->regs.reg[13].loc.offset = (long)&sc->r13 - new_cfa;
fs->regs.reg[14].loc.offset = (long)&sc->r14 - new_cfa;
50
_Unwind_SetGRPtr (context, i,
for (i = 0; i < DWARF_FRAME_REGISTERS + 1; ++i)
switch (fs->regs.reg[i].how)
_Unwind_SetGRPtr (context, i,
for (i = 0; i < DWARF_FRAME_REGISTERS + 1; ++i)
switch (fs->regs.reg[i].how)
_Unwind_SetGRPtr (context, i,
for (i = 0; i < DWARF_FRAME_REGISTERS + 1; ++i)
switch (fs->regs.reg[i].how)
_Unwind_SetGRPtr (context, i,
for (i = 0; i < DWARF_FRAME_REGISTERS + 1; ++i)
switch (fs->regs.reg[i].how)
_Unwind_SetGRPtr (context, i,
for (i = 0; i < DWARF_FRAME_REGISTERS + 1; ++i)
switch (fs->regs.reg[i].how)
_Unwind_SetGRPtr (context, i,
for (i = 0; i < DWARF_FRAME_REGISTERS + 1; ++i)
switch (fs->regs.reg[i].how)
_Unwind_SetGRPtr (context, i,
for (i = 0; i < DWARF_FRAME_REGISTERS + 1; ++i)
switch (fs->regs.reg[i].how)
for (i = 0; i < DWARF_FRAME_REGISTERS + 1; ++i)
switch (fs->regs.reg[i].how)
_Unwind_SetGRPtr (context, i,
for (i = 0; i < DWARF_FRAME_REGISTERS + 1; ++i)
switch (fs->regs.reg[i].how)
54
_Unwind_SetGRPtr (context, i,
for (i = 0; i < DWARF_FRAME_REGISTERS + 1; ++i)
switch (fs->regs.reg[i].how)
_Unwind_SetGRPtr (context, i,
for (i = 0; i < DWARF_FRAME_REGISTERS + 1; ++i)
switch (fs->regs.reg[i].how)
_Unwind_SetGRPtr (context, i,
for (i = 0; i < DWARF_FRAME_REGISTERS + 1; ++i)
switch (fs->regs.reg[i].how)
_Unwind_SetGRPtr (context, i,
for (i = 0; i < DWARF_FRAME_REGISTERS + 1; ++i)
switch (fs->regs.reg[i].how)
_Unwind_SetGRPtr (context, i,
for (i = 0; i < DWARF_FRAME_REGISTERS + 1; ++i)
switch (fs->regs.reg[i].how)
_Unwind_SetGRPtr (context, i,
for (i = 0; i < DWARF_FRAME_REGISTERS + 1; ++i)
switch (fs->regs.reg[i].how)
_Unwind_SetGRPtr (context, i,
for (i = 0; i < DWARF_FRAME_REGISTERS + 1; ++i)
switch (fs->regs.reg[i].how)
_Unwind_SetGRPtr (context, i,
for (i = 0; i < DWARF_FRAME_REGISTERS + 1; ++i)
switch (fs->regs.reg[i].how)
for (i = 0; i < DWARF_FRAME_REGISTERS + 1; ++i)
_Unwind_SetSignalFrame (context, fs->signal_frame);
55
57
83
84
95
Greetings:
Mateusz 'j00ru' Jurczyk, Gynvael Coldwind for reviewing and great confrontation (discussion) of ideas.
Rafa 'nergal' Wojtczuk for great discussion and helping with DWARF.
Brian Pak for great research and at least for reading this paper ;)
sergio and #trololol for motivation and at least for reading this paper ;)
References:
1.
2.
3.
4.
5.
http://www.phrack.org/issues.html?issue=67&id=13&mode=txt
http://xorl.wordpress.com/2010/10/14/linux-glibc-stack-canary-values/
http://www.bpak.org/blog/2014/02/codegate-2014-membership-800pt-pwnable-write-up/
https://www.usenix.org/legacy/event/woot11/tech/final_files/Oakley.pdf
http://dwarfstd.org/
Best regards,
Adam 'pi3' Zabrocki
http://pi3.com.pl
88