Problem description
First of all, the CTF is from pwnable.kr (problem name: passcode). The problem description is as follows:
“Mommy told me to make a passcode based login system. My initial C code was compiled without any error! Well, there was some compiler warning, but who cares about that?”
There is ssh to the problem server where you can find:
We have 3 files including the flag file which can only be read by the user or group root. The other text file is a source code “passcode.c” which we can view. There is one executable that is compiled from the source code provided. This executable has read accessibility to the flag. However, the source code looks following:
#include <stdio.h> #include <stdlib.h> void login() { int passcode1; int passcode2; printf("enter passcode1 : "); scanf("%d", passcode1); fflush(stdin); // ha! mommy told me that 32bit is vulnerable to bruteforcing :) printf("enter passcode2 : "); scanf("%d", passcode2); printf("checking...\n"); if (passcode1 == 338150 && passcode2 == 13371337) { printf("Login OK!\n"); system("/bin/cat flag"); } else { printf("Login Failed!\n"); exit(0); } } void welcome() { char name[100]; printf("enter you name : "); scanf("%100s", name); printf("Welcome %s!\n", name); } int main() { printf("Toddler's Secure Login System 1.0 beta.\n"); welcome(); login(); // something after login... printf("Now I can safely trust you that you have credential :)\n"); return 0; }
If we take a quick look, the first unusual fact we may see is how it use scanf to user input for passcode1 and passcode2 in login(). We usually send an address to libc to write down the user input, instead, here we send an arbitrary value to let the user write down. This indicates if an attacker can overwrite either passcode1 or passcode2 with an address, they can be able to overwrite any address to that address. This sounds like we have an opportunity to jump from one address to another address and then another to reach an attacker targeted region. The jump has to just after the scanf is in use. Our final target will be: execute the system call inside the executable.
Details about the executable
There are multiple
It is ELF32, indicates builds with -m32. There is also Type Exec, indicates builds with -no-pie. So, I build the passcode.c in my local machine with
gcc -g -m32 -no-pie passcode.c -o passcode
It is almost looks like same. Okay, now we have exact same executable. We have a buffer in welcome(), so there could be canary. Let’s make sure disassembling welcome() in remote server.
Look into instruction address (0x08048612). This executable has an active canary. So, buffer overflow is not possible. However, from source code, we can also observe that number of acceptable bytes for user input is also verified in scanf(“%100s”, name), i.e. only 100 characters are acceptable.
So, where to focus?
We should focus on login() local variables passcode1 and passcode2. As we have mentioned before they can be overwritten with attacker provide address to jump into arbitrary location. But, before that, we have to overflow either of them with a legitimate address. The interesting part is that although buffer overflow is not possible for welcome local variable name[100], there could be overlap memory between welcome() and login() local variables.
The above three snippet indicates that there should be an overlap between welcome.name[100] and login.passcode1. Why? We know when a function is called, the system will allocate memory for its local variables (like for welcome() and for its only local variable name[100]). When the function call will return, this allocated memory will be rolled-back, but the data will be persistent. In next call (call to login()) will allocate memory similarly, which will be overlapped with some memory with welcome.name[100]. Question is how much? Theoretically, it will be after 0x70 – 0x10 bytes, which is 0x60 and in decimal 96. So, the data written to 97th character to 100th character in welcome.name[100] will be available in login.passcode1. Want to prove it? Let’s do it following way.
Okay, but now what?
The next to scanf(“%d”, passcode1) is fflush(stdin). This is definitely a jump we can use, what we need is to change one of the target from this jump. This particular jump has a direct address. What about where it is targeted? This will jump into .plt table for fflush().
So, this is (0x804a010) is the memory where an attacker can write down his desired target. To do that, let’s give scanf(“%d”, passcode1) the expected memory address for passcode1 by writing welcome.name[97-100] with the indirect memory address. We can do by:
python -c "print 'A'*96+'\x10\xa0\x04\x08'" | ./passcode
Now, when the
I choose 0x08048651 instruction address. So, my attack looks like:
python -c "print 'A'*96+'\x10\xa0\x04\x08'+str(0x08048651)" | ./passcode
if we do debug, we will see, we first call
So, what we basically achieve?
Because the .got table target is decided by