Tuesday, December 1, 2009

Stack Canaries Part 3 - Sing birdie, sing!

So, time to look closer at the GNU Stack Canary generation. A brief bit of scrounging around with the GCC sources reveals that the stack protection is actually governed by the libssp code. Looking at the internals, we find that the following file contains some juicy bits:

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:






















































































69static 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

No comments: