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!