DEV Community

chris
chris

Posted on

Pwnable.kr - Collusion: Write up

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:

#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;
}
Enter fullscreen mode Exit fullscreen mode

From here, there are a couple of interesting sections.

unsigned long hashcode = 0x21DD09EC;
Enter fullscreen mode Exit fullscreen mode

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

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;
}
Enter fullscreen mode Exit fullscreen mode

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 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:

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;
}
Enter fullscreen mode Exit fullscreen mode

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
>>> 
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Then:

568134124 / 5 = x
Enter fullscreen mode Exit fullscreen mode

But!

>>> 568134124 / 5
113626824.8
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

(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
Enter fullscreen mode Exit fullscreen mode

Then:

568134124 = 4 * 113626824 + x'
568134124 - (4 * 113626824) = x'
113626828 = x'
Enter fullscreen mode Exit fullscreen mode

Test it:

>>> 568134124 == (4 * 113626824) + 113626828
True
Enter fullscreen mode Exit fullscreen mode

All good. Let's write the exploit. For that, we are going to use pwntools.

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

p32(113626824, endian='little')
# and
p32(113626828, endian='little')
Enter fullscreen mode Exit fullscreen mode

Here is the exploit:

from pwn import *

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

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.

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

And it's pwned! Hope you enjoyed it!

Oldest comments (0)