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:

Download the Assignment 2 virtual machine from OWL Brightspace. Refer to the VM setup instructions to install and setup and access the assignment virtual machine on your host device.

Using username col and password col, use ssh to log into the Assignment 2 virtual machine. Once logged in, use ls -l to print the directory:

ssh col@<IP of virtual machine>
col@<IP of virtual machine>'s password:

NOTE: This is a *simulation* of a pwnable.kr CTF challenge

 ____  __    __  ____    ____  ____   _        ___      __  _  ____
|    \|  |__|  ||    \  /    ||    \ | |      /  _]    |  |/ ]|    \
|  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@box:~$

First let’s find out about ourselves:

col@box:~$ id
uid=1000(col) gid=1000(col) groups=1000(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@box:~$ ls -la
total 24
drwxr-x---    3 root     col            120 Feb  4 14:59 .
drwxr-xr-x    4 root     root            80 Feb  4 14:59 ..
d---------    2 root     root            40 Feb  4 03:41 .ash_history
-r-sr-x---    1 col_pwn  col          15448 Feb  4 04:42 col
-rw-r--r--    1 root     root           555 Feb  4 04:41 col.c
-r--r-----    1 col_pwn  col_pwn         27 Feb  4 00:44 flag

First we observe 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 = 0xC3EA9FF2;
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@box:~$ cp col.c mycol.c
cp: can't create 'mycol.c': Permission denied

Ah, yes. The permission of the directory is:

drwxr-x---    3 root     col            120 Feb  4 14:59 .

That means we are not allowed to create files here. But recall the login message made a mention to “make your directory under /tmp”. Let’s try that:

col@box:~$ mkdir /tmp/mytemp
col@box:~$ cp col.c /tmp/mytemp/mycol.c

Let’s have a look:

col@box:~$ cd /tmp/mytemp
col@box:/tmp/mytemp$ ls -la
total 4
drwxr-xr-x    2 col      col             60 Feb  4 15:54 .
drwxrwxrwt    5 root     staff          200 Feb  4 15:54 ..
-rw-r--r--    1 col      col            555 Feb  4 15:54 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@box:/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("Result: %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@box:/tmp/mytemp$ gcc -Wno-implicit mycol.c -o mycol

All the -Wno-implicit does is suppress a compiler warning about the use of the system() call, which is not particularly relevant to this challenge. Next, let’s see what we have:

col@box:/tmp/mytemp$ ls -la
total 20
drwxr-xr-x    2 col      col             80 Feb  4 15:56 .
drwxrwxrwt    5 root     staff          200 Feb  4 15:56 ..
-rwxr-xr-x    1 col      col          15452 Feb  4 15:56 mycol
-rw-r--r--    1 col      col            555 Feb  4 15:54 mycol.c

Here we see an executable file mycol was created. Ok, let’s run it:

col@boc:/tmp/mytemp$ ./mycol 
usage : ./mycol [passcode]

Let’s give it some input

col@box:/tmp/mytemp$ ./mycol 00001111
passcode length should be 20 bytes
col@box:/tmp/mytemp$ ./mycol 00001111222233334444
30303030
31313131
32323232
33333333
34343434
Result: 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@box:/tmp/mytemp$ python
Python 2.7.17 (default, Dec 11 2019, 16:49:43)
[GCC 9.2.0] 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'

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 0xC3EA9FF2. 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@box:/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
Result: f0f0f0f
wrong passcode.

Ok, now we have a little freedom to play around with these values to get them to add up to 0xC3EA9FF2.

So at this point you should have all the background knowledge you need. Time to go capture that flag!