pwnable.kr (Collision)
Tutorial Notes
What you’ll learn:
- Basic command-line operations (
ssh
,ls
,gcc
, etc.) - Unix based file permissions (owner, group, suid, etc.)
- Variable representation in memory (the difference between char arrays, integers, etc.)
Directions:
First visit pwnable.kr and click on the “Collision” icon. Next we log in via ssh
as per the instructions:
$ ssh col@pwnable.kr -p2222
col@pwnable.kr's password:
____ __ __ ____ ____ ____ _ ___ __ _ ____
| \| |__| || \ / || \ | | / _] | |/ ]| \
| o ) | | || _ || o || o )| | / [_ | ' / | D )
| _/| | | || | || || || |___ | _] | \ | /
| | | ` ' || | || _ || O || || [_ __ | \| \
| | \ / | | || | || || || || || . || . \
|__| \_/\_/ |__|__||__|__||_____||_____||_____||__||__|\_||__|\_|
- Site admin : daehee87.kr@gmail.com
- IRC : irc.netgarage.org:6667 / #pwnable.kr
- Simply type "irssi" command to join IRC now
- files under /tmp can be erased anytime. make your directory under /tmp
- to use peda, issue `source /usr/share/peda/peda.py` in gdb terminal
col@ubuntu:~$
First let’s find out about ourselves:
col@ubuntu:~$ id
uid=1005(col) gid=1005(col) groups=1005(col)
Ok so we have username col
and we belong to group col
. Next let’s see what’s contained in our home directory:
col@ubuntu:~$ ls -la
total 36
drwxr-x--- 5 root col 4096 Oct 23 2016 .
drwxr-xr-x 87 root root 4096 Dec 27 23:17 ..
d--------- 2 root root 4096 Jun 12 2014 .bash_history
-r-sr-x--- 1 col_pwn col 7341 Jun 11 2014 col
-rw-r--r-- 1 root root 555 Jun 12 2014 col.c
-r--r----- 1 col_pwn col_pwn 52 Jun 11 2014 flag
dr-xr-xr-x 2 root root 4096 Aug 20 2014 .irssi
drwxr-xr-x 2 root root 4096 Oct 23 2016 .pwntools-cache
So there’s an executable file named col
that we can run, as well as a source file col.c
that presumably contains the code to the executable. We can also see the flag file flag
, but we don’t have permission to read from it. User col_pwn
can read from it, and interestingly the executable col
has the SUID bit set as shown by the “s
” in -r-sr-x---
.
suid stands for “Set owner User ID up on execution,” meaning if we run the program, it will execute at the privilege level of its owner (i.e., col_pwn
). So if (for whatever reason) col
tried to read from flag
, it could, because col
would be running with the user ID col_pwn
and thus would be allowed to access flag
. Ok, so let’s take a look at the code in col.c
:
#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
int* ip = (int*)p;
int i;
int res=0;
for(i=0; i<5; i++){
res += ip[i];
}
return res;
}
int main(int argc, char* argv[]){
if(argc<2){
printf("usage : %s [passcode]\n", argv[0]);
return 0;
}
if(strlen(argv[1]) != 20){
printf("passcode length should be 20 bytes\n");
return 0;
}
if(hashcode == check_password( argv[1] )){
system("/bin/cat flag");
return 0;
}
else
printf("wrong passcode.\n");
return 0;
}
In the main
function we see that if the check_password
function returns the same values as hashcode
, then the program will print the contents of the flag.
Awesome. So we somehow need to get check_password
to return the value 0x21DD09EC
. But how?
Maybe we could make a copy col.c
and add in some printf statements to help us see what the program is doing. Let’s try to make a copy of col.c
:
col@ubuntu:~$ cp col.c mycol.c
cp: cannot create regular file 'mycol.c': Permission denied
Oh right. The permission of the directory is:
drwxr-x--- 5 root col 4096 Oct 23 2016 .
That means we can’t create files here. But recall the login message made a mention to “make your directory under /tmp”. Let’s try that:
col@ubuntu:~$ mkdir /tmp/mytemp
col@ubuntu:~$ cp col.c /tmp/mytemp/mycol.c
Let’s have a look:
col@ubuntu:~$ cd /tmp/mytemp
col@ubuntu:/tmp/mytemp$ ls -la
total 384
drwxrwxr-x 2 col col 4096 Jan 25 18:49 .
drwxrwx-wt 2962 root root 380928 Jan 25 18:48 ..
-rw-r--r-- 1 col col 579 Jan 25 18:49 mycol.c
It worked. Ok, now let’s edit our file and stick in a couple of printf statements so we can see what’s happening:
col@ubuntu:/tmp/mytemp$ vim /tmp/mytemp/mycol.c
I’m using Vim, a command-line based text editor. If you’ve never used Vim, check out this interactive tutorial. Ok, let’s add those printf statements:
unsigned long check_password(const char* p){
int* ip = (int*)p;
int i;
int res=0;
for(i=0; i<5; i++){
res += ip[i];
printf("%x\n",ip[i]); // <---- print statement we added
}
printf("%x\n",res); // <---- print statement we added
return res;
}
Here the statement printf("%x\n",ip[i]);
causes the program to print out the integer stored in ip[i]
, and the %x
means format it as a hexidecimal value. We also will print out the sum stored in res
to see how close we come to the target value.
Now let’s compile our modified program:
col@ubuntu:/tmp/mytemp$ gcc mycol.c -o mycol
col@ubuntu:/tmp/mytemp$ ls -la
total 396
drwxrwxr-x 2 col col 4096 Jan 25 18:52 .
drwxrwx-wt 2962 root root 380928 Jan 25 18:51 ..
-rwxrwxr-x 1 col col 8840 Jan 25 18:52 mycol
-rw-r--r-- 1 col col 579 Jan 25 18:49 mycol.c
Here we see an executable file mycol
was created. Ok, let’s run it:
col@ubuntu:/tmp/mytemp$ ./mycol
usage : ./mycol [passcode]
Oops. Forgot to give it any input. Let’s try again:
col@ubuntu:/tmp/mytemp$ ./mycol 00001111
passcode length should be 20 bytes
col@ubuntu:/tmp/mytemp$ ./mycol 00001111222233334444
30303030
31313131
32323232
33333333
34343434
fafafafa
wrong passcode.
Interesting. If we look up the ASCII code for the character “0”, we see that it’s 30
. Similarly “1” is 31
, “2” 32
, etc. And somehow these numbers are adding up to fa
.
Let’s just check this in Python:
col@ubuntu:/tmp/mytemp$ python
Python 2.7.12 (default, Jul 1 2016, 15:12:24)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> hex(int("30",16) + int("31",16) + int("32",16) + int("33",16) + int("34",16))
'0xfa'
Cool. So it looks like the program is consuming single byte char
types from standard in, and casting them into 4-byte integers types with int* ip = (int*)p;
. So a 20 byte char array bytes becomes an integer array of 5 elements, and the program adds these 5 integers together and compares them to the target value of 0x21DD09EC
. So we need to somehow manufacture the sum of these 5 integers to equal the target value.
But inputting characters makes things tricky for us. The lowest ASCII value we can manage is “!” which has a hex value of 31
. It would be nice if we could input, say, a 01
. There’s no character we can type out directly, but we can tell the program to input a byte value of 01
using the following notation:
$ ./program $'\x01'
There’s one caveat: you can’t input \x00
. This is a null byte and denotes the end of the string. The program will read until it hits a null byte and stop. But we can still input a byte value between 01
and FF
in this matter, so let’s try running our program again:
col@ubuntu:/tmp/mytemp$ ./mycol $'\x01\x01\x01\x01\x02\x02\x02\x02\x03\x03\x03\x03\x04\x04\x04\x04\x05\x05\x05\x05'
1010101
2020202
3030303
4040404
5050505
f0f0f0f
wrong passcode.
Ok, now we have a little freedom to play around with these values to get them to add up to 0x21DD09EC
.
So at this point you should have all the background knowledge you need. Time to go capture that flag!