So, lets dive into some libSSP stuff, already.

Understanding Code

A dearth of information is available at the LFS SSP hints. Additionally, we should keep the actual GLIBC SSP implementation handy for looking through the code

Let's start, in fact, with the implementation and information from the LFS. Looking at the default, we see that the GlibC unix SSP code does the following:

63 _dl_setup_stack_chk_guard (void *dl_random)
64 {
70       int fd = __open ("/dev/urandom", O_RDONLY);
71       if (fd >= 0)
72         {
73           ssize_t reslen = __read (fd, &ret, sizeof (ret));
74           __close (fd);
75           if (reslen == (ssize_t) sizeof (ret))
76             return ret;
79       ret = 0;
80       unsigned char *p = (unsigned char *) &ret;
81       p[sizeof (ret) - 1] = 255;
82       p[sizeof (ret) - 2] = '\n';
83     }

The above code is the static defined, always inlined, C code which does the actual canary initialization.

An interesting bit to note is the use of the strong_alias'd functions - __open, __close, and __read. There's no way we will be able to hook the linker at any point to override these functions via a re-alias. The linker won't accept this. However, we do have one possible attack vector. If we can prevent the linker from being able to open /dev/urandom, then we can force a well known canary 0x00000aff into the stack, and have a viable attack vector for stack overflows.

Test Harness

In order to continue these exercises, it will be useful to build a test harness program for us to use. Throughout this paper, we'll expand on the code to continually improve our understanding of the actual specifics of the LibSSP mechanisms. For this code, the following assumptions are made:

  • A Linux based Intel PC, capable of running 64-bit and 32-bit software.
  • Ubuntu 10.04 (up-to-date as of Wed. March 22, 2011)
  • Access to the GNU C compiler collection
  • Access to the GNU Debugger

The following test program will be our first stab:

01 #include <stdio.h>
02 #include <string.h>
04 void foo (char *buf)
05 {
06     char vbuf[16] = {0};
08     memcpy(vbuf, buf, strlen(buf));
09     printf(vbuf); /* really vulnerable */
10 }
12 int main (int argc, char *argv[])
13 {
14     if (argc < 2) return -1;
16     printf("%s: %s\n", argv[0], argv[1]);
17     foo(argv[1]);
18     printf("\n%s: %s\n", argv[0], argv[1]);
19     return 0;
20 }

We'll compile the above code with: gcc -fstack-protector-all -m32 -o stack stack.c and then use gdb to see the assembler code that was generated.

Dump of assembler code for function foo:
0x080484a4 <+0>    :push   %ebp
0x080484a5 <+1>    :mov    %esp,%ebp
0x080484a7 <+3>    :sub    $0x48,%esp
0x080484aa <+6>    :mov    0x8(%ebp),%eax
0x080484ad <+9>    :mov    %eax,-0x2c(%ebp)
0x080484b0 <+12>   :mov    %gs:0x14,%eax
0x080484b6 <+18>   :mov    %eax,-0xc(%ebp)
0x080484bb <+23>:   movl    $0x0,-0x1c(%ebp)
0x080484c2 <+30>:   movl    $0x0,-0x18(%ebp)
0x080484c9 <+37>:   movl    $0x0,-0x14(%ebp)
0x080484d0 <+44>:   movl    $0x0,-0x10(%ebp)

The bolded lines above are where all of the important magic happens. We see that the eax register becomes populated with the value stored in %gs:14, and then that value is pushed onto the stack at -0xc(%ebp). Then, the absolute value '0' is moved, 4 bytes at a time, onto the stack just "after" it. So, our stack looks like:

  • 16 bytes of 0 (this corresponds to char vbuf[16] = {0};)
  • Canary
  • return ptr

The 64-bit version looks similar. The only real differences are the use of 64-bit registers (%rax, %rbp, etc.), and that the canary comes from %fs:0x28

So, lets run the beast and see what crashes we can invoke.

Last modified: 2011-03-02, 16:32

Copyright © 2011 me