Pwnable.kr - Collusion: Write up
Tags: ctf, security
Published: 2021-02-03 21:37:16

This a fun challenge that is good to practice debugging and reading C code (which I don't really know). 

A collusion is giving a message that has the same signature than your encoded password. Like your password is saved in an encoded way in a database with a hashing function X, when you log in to the service, you send your password which is hashed to X and that is checked with the database. If you give directly the hash Y that is equal to X, then you managed to log in without finding the password. For example, MD5 is vulnerable to it (other ciphers too). It's due to the limitations of the calculation of the hash (I think).

So the main code is: 

```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];
        printf(res);
	}
	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;
}
```

From here, there are a couple of interesting sections. 

```C
unsigned long hashcode = 0x21DD09EC;
```

This section defines a global variable hashcode. We'll get back to it later. 

```C
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(res);
	}
	return res;
}
```

This function will get a character (i.e. a string) and then cast it into a vector (i.e. an array). That's the `int* ip = (int*)p;` section. You should definitely [read more](https://www.quora.com/What-does-int-p-mean) because it's quite central to the work here. 

Then, the loop will iterate over the vector and get all the values of `ip` and sum them to return the result. 

After that, the `main()` function: 

```c
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;
}
```
Which can be described in 3 steps:

* check if you have strictly one argument to the command line call
* argument should be 20 bytes long
* if the given password is equal to `hashcode` then we pwned the challenge.

# What do we do from here?

`0x21DD09EC` is our first clue. We can't do much with that, let's transform this in decimals (it makes sense later).

```
Python 3.9.1 (default, Dec 13 2020, 11:55:53)  [GCC 10.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 0x21DD09EC
568134124
>>> 
```

Then the `check_password` is our second step. 

The loop goes 5 times and return a result. This means that whatever we give the function will be added 5 fold, hence:

```
568134124 = 5x
```

Then:

```
568134124 / 5 = x
```

But! 

```
>>> 568134124 / 5
113626824.8
```

We need to work with integers here not floats. Then, let's modify the equations to enter the remainder or the function:

```
568134124 = 4x + x'
```

(There is probably a more mathy-way to do that, let me know in the comments, I left school quite a while ago)

```
>>> 568134124 // 5
113626824
```

Then: 

```
568134124 = 4 * 113626824 + x'
568134124 - (4 * 113626824) = x'
113626828 = x'
```

Test it:

```
>>> 568134124 == (4 * 113626824) + 113626828
True
```

All good. Let's write the exploit. For that, we are going to use [pwntools](https://pwntools.readthedocs.io).

The two decimals section will be in little endian and for a 32-bits architecture, thus: 

```python
p32(113626824, endian='little')
# and
p32(113626828, endian='little')
```

Here is the exploit: 

```python
from pwn import *

payload = p32(113626824, endian='little') * 4 + p32(113626828, endian='little')
p = process(['./col', payload])
ret = p.recvline()
print(ret)
```

Why the payload before the call to the process? Because we need to pass the payload as a command line argument.

Why the whole calculation and not just the result? Because we need a 20 bytes payload. We are passing the instruction and not just the result. The details of the how and the why are a bit obscure to me. I tried a couple of things and this worked in the end. 

```sh
~/.../pwnable.kr/2 >>> python boum.py                                                                                        
[+] Starting local process './col': pid 99792
b'/bin/cat: flag: No such file or directory\n'
```

And it's pwned! Hope you enjoyed it!