http://gcc.gnu.org/viewcvs/trunk/libssp/ssp.c?view=log
Go ahead, look at it. Latest revision as of this writing is 146000. It may be updated by the time you look. Anyway, the important parts are reproduced below:
69 | static void __attribute__ ((constructor)) | |||
70 | __guard_setup (void) | |||
71 | { | |||
72 | unsigned char *p; | |||
73 | int fd; | |||
74 | ||||
75 | if (__stack_chk_guard != 0) | |||
76 | return; | |||
77 | ||||
78 | fd = open ("/dev/urandom", O_RDONLY); | |||
79 | if (fd != -1) | |||
80 | { | |||
81 | ssize_t size = read (fd, &__stack_chk_guard, | |||
82 | sizeof (__stack_chk_guard)); | |||
83 | close (fd); | |||
84 | if (size == sizeof(__stack_chk_guard) && __stack_chk_guard != 0) | |||
85 | return; | |||
86 | } | |||
87 | ||||
88 | /* If a random generator can't be used, the protector switches the guard | |||
89 | to the "terminator canary". */ | |||
90 | p = (unsigned char *) &__stack_chk_guard; | |||
91 | p[sizeof(__stack_chk_guard)-1] = 255; | |||
92 | p[sizeof(__stack_chk_guard)-2] = '\n'; | |||
93 | p[0] = 0; | |||
94 | } |
So, the stack guard is setup by the output of /dev/urandom.
Can we verify this? Of course! Lets just turn off read access to /dev/urandom and see if we get a static assignment:
[11:13:17][aconole@ssh:~]
$ ./sp32_t 1
gs:0x14[2786491977 / A6167E49]
gs:0x14[2786491977 / A6167E49]
gs:0x14[2786491977 / A6167E49]
gs:0x14[2786491977 / A6167E49]
gs:0x14[2786491977 / A6167E49]
gs:0x14[2786491977 / A6167E49]
[11:13:20][aconole@ssh:~]
$ sudo chmod go-r /dev/urandom
root's password:
[11:13:36][aconole@ssh:~]
$ ./sp32_t 1
gs:0x14[4278845440 / FF0A0000]
gs:0x14[4278845440 / FF0A0000]
gs:0x14[4278845440 / FF0A0000]
gs:0x14[4278845440 / FF0A0000]
gs:0x14[4278845440 / FF0A0000]
gs:0x14[4278845440 / FF0A0000]
[11:13:39][aconole@ssh:~]
$ ./sp32_t 1
gs:0x14[4278845440 / FF0A0000]
gs:0x14[4278845440 / FF0A0000]
gs:0x14[4278845440 / FF0A0000]
gs:0x14[4278845440 / FF0A0000]
gs:0x14[4278845440 / FF0A0000]
gs:0x14[4278845440 / FF0A0000]
[11:13:40][aconole@ssh:~]
$ ./sp32_t 1
gs:0x14[4278845440 / FF0A0000]
gs:0x14[4278845440 / FF0A0000]
gs:0x14[4278845440 / FF0A0000]
gs:0x14[4278845440 / FF0A0000]
gs:0x14[4278845440 / FF0A0000]
gs:0x14[4278845440 / FF0A0000]
Voila! Disabling read access means that the canary value is static. Why is this important? It's possible that the system would be misconfigured and /dev/urandom read access would be disabled. If that's the case, then our canary is well known, and we _possibly_ bypass the canary value.
NOTE: I say _possibly_ here. If we can't get 0xFF0A0000 into the stack (since \x00 is null terminator, \x0a is usually considered the line terminator) then we're hosed. However, if we _CAN_ inject that data, it's a clean way of bypass.
Let's setup a new test case to verify our stack overflow.
[11:33:06][aconole@ssh:~]
$ ./sp32_b 20 asdfasdfasdfasdfasdf 16
asdfasdfasdfasdf
*** stack smashing detected ***: ./sp32_b terminated
======= Backtrace: =========
/lib/libc.so.6(__fortify_fail+0x48)[0xf7635da8]
/lib/libc.so.6(__fortify_fail+0x0)[0xf7635d60]
./sp32_b[0x804869c]
./sp32_b[0x80488ab]
/lib/libc.so.6(__libc_start_main+0xe5)[0xf7565705]
./sp32_b[0x80485a1]
======= Memory map: ========
08048000-08049000 r-xp 00000000 08:11 7602732 /home/aconole/sp32_b
08049000-0804a000 r--p 00000000 08:11 7602732 /home/aconole/sp32_b
0804a000-0804b000 rw-p 00001000 08:11 7602732 /home/aconole/sp32_b
0804b000-0806c000 rw-p 0804b000 00:00 0 [heap]
f754e000-f754f000 rw-p f754e000 00:00 0
f754f000-f76a4000 r-xp 00000000 08:11 6381831 /lib/libc-2.9.so
f76a4000-f76a5000 ---p 00155000 08:11 6381831 /lib/libc-2.9.so
f76a5000-f76a7000 r--p 00155000 08:11 6381831 /lib/libc-2.9.so
f76a7000-f76a8000 rw-p 00157000 08:11 6381831 /lib/libc-2.9.so
f76a8000-f76ab000 rw-p f76a8000 00:00 0
f76ab000-f76c1000 r-xp 00000000 08:11 6381715 /lib/libpthread-2.9.so
f76c1000-f76c2000 r--p 00015000 08:11 6381715 /lib/libpthread-2.9.so
f76c2000-f76c3000 rw-p 00016000 08:11 6381715 /lib/libpthread-2.9.so
f76c3000-f76c5000 rw-p f76c3000 00:00 0
f76e5000-f76f2000 r-xp 00000000 08:11 6381883 /lib/libgcc_s.so.1
f76f2000-f76f3000 r--p 0000c000 08:11 6381883 /lib/libgcc_s.so.1
f76f3000-f76f4000 rw-p 0000d000 08:11 6381883 /lib/libgcc_s.so.1
f76f4000-f76f6000 rw-p f76f4000 00:00 0
f76f6000-f7714000 r-xp 00000000 08:11 6381973 /lib/ld-2.9.so
f7714000-f7715000 r--p 0001d000 08:11 6381973 /lib/ld-2.9.so
f7715000-f7716000 rw-p 0001e000 08:11 6381973 /lib/ld-2.9.so
ffe72000-ffe87000 rw-p 7ffffffea000 00:00 0 [stack]
ffffe000-fffff000 r-xp ffffe000 00:00 0 [vdso]
Aborted
[11:33:07][aconole@ssh:~]
$ sudo chmod go-r /dev/urandom
[11:33:16][aconole@ssh:~]
$ ./sp32_b 20 asdfasdfasdfasdfasdf 16
asdfasdfasdfasdf
[11:33:18][aconole@ssh:~]
$
Awesome! We have proven that it _IS_ possible to bypass the stack under severely contrived conditions. Those conditions are as follows:
1 - we use a memcpy
2 - we disable read access to /dev/urandom BEFORE the program is launched
3 - we have direct local access to the system
4 - it's 32-bits
We haven't tested 64-bits yet, so lets check that out now.
[11:36:04][aconole@ssh:~]
$ ./sp64_b 2
fs:0x28[18377501229438730240 / ff0a000000000000]
AHA! It's the same deal - just make sure our offset is correct, and we should be able to successfully exploit it.
[11:42:46][aconole@ssh:~]
$ ./sp64_b 26 asdfasdfasdfasdfasdfasdfasdfasdfasdfasdf 24
asdfasdfasdfasdfasdfasdf
[11:42:48][aconole@ssh:~]
$ sudo chmod go+r /dev/urandom
[11:42:57][aconole@ssh:~]
$ ./sp64_b 26 asdfasdfasdfasdfasdfasdfasdfasdfasdfasdf 24
asdfasdfasdfasdfasdfasdf
*** stack smashing detected ***: ./sp64_b terminated
Voila! So, we can now contrive a situation where stack canary bypass works.
Next week - playing with the linker. As a preview:
/tmp/ccUOKFpY.o: In function `reassign_sc_32':
/home/aconole/stack-protect.c:44: undefined reference to `__stack_chk_guard'
/tmp/ccUOKFpY.o: In function `reassign_sc_64':
/home/aconole/stack-protect.c:58: undefined reference to `__stack_chk_guard'
collect2: ld returned 1 exit status