Search Site ...

Search Site ...

Pwnable

Published

Oct 18, 2024

5

-

min read

Pwnable.kr: collision Solution

This is the 2nd puzzle in the pwnable.kr series that focuses on hash collisions

This is the 2nd puzzle in the pwnable.kr series that focuses on hash collisions

So beginning with this puzzle, the hint the website provides us with is another SSH command

Daddy told me about cool MD5 hash collision today.

I wanna do something like that too!

ssh col@pwnable.kr -p2222 (pw:guest)

MD5:

MD5 is a hashing algorithm that has been going around since the 1990s. It generates a 128 bit hash output for any given input. It is generally used to check the authenticity of files or content served over the internet to check whether the data received is exactly what the server meant to sent. To do this we compare the hashes of the received data and the data sent by the server. However it has been broken many times since 2005 and any malicious actor can alter the contents of the data/file while maintaining the same hash which defeats its purpose. This is what we call a collision. When 2 different inputs provide us with the same hash output we have a hash collision.

Walkthrough:

Let's start with the SSH credentials provided to us and login to the server via SSH. Upon logging in we'll be greeted by this.

ssh col@pwnable.kr -p2222
col@pwnable.kr's password:
 ____  __    __  ____    ____  ____   _        ___      __  _  ____
|    \|  |__|  ||    \  /    ||    \ | |      /  _]    |  |/ ]|    \
|  o  )  |  |  ||  _  ||  o  ||  o  )| |     /  [_     |  ' / |  D  )
|   _/|  |  |  ||  |  ||     ||     || |___ |    _]    |    \ |    /
|  |  |  `  '  ||  |  ||  _  ||  O  ||     ||   [_  __ |     \|    \
|  |   \      / |  |  ||  |  ||     ||     ||     ||  ||  .  ||  .  \
|__|    \_/\_/  |__|__||__|__||_____||_____||_____||__||__|\_||__|\_|

- Site admin : daehee87@khu.ac.kr
- 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
You have mail.
Last login: Sun Oct 13 06:42:17 2024 from 2.34.130.63
col@pwnable:~$

Now like usual we start searching for the flag. We type ls into the terminal and are greeted by this

col@pwnable:~$ ls
col  col.c  flag
col@pwnable:~$

And like before the ls -la command shows that we cannot open the flag file directly

col@pwnable:~$ ls -la
total 36
drwxr-x---   5 root    col     4096 Oct 23  2016 .
drwxr-xr-x 116 root    root    4096 Oct 30  2023 ..
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
col@pwnable:~$

And exactly like before we have permission to use the col file which is an executable

col@pwnable:~$ file col
col: setuid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=05a10e253161f02d8e6553d95018bc82c7b531fe, not stripped
col@pwnable:~$

And we have the source code for this file in col.c

col@pwnable:~$ cat 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;
}

col@pwnable:~$

Reading the source code we can infer the following about the col file

  • It requires 1 argument to be passed because of the following check if(argc<2) { … return 0;}

  • The argument passed must be of length 20 as each character takes 1 byte

  • This file can open the flag file via a system call to the cat command system("/bin/cat flag")

  • The file can only be opened if the check_password function for the argument we passed equals the hashcode which is set to 0x21DD09EC

  • The check_password function appears to be using a reference to a string then changing that to integer array form and then looping through its first 5 integers and then adding them to return a value.

Now 1 integer is worth 4 bytes and we need 5 of them so a total of 20 bytes, which is the exact length of our input being passed on. Looking at the code closely we can say that it combines 4 characters to make an integer then add them. So we just need to calculate 5 integers that when added equal the hashcode and we will be able to successfully read the flag.

0x21DD09EC is a hexadecimal representation of the number 568134124 which I found with a python one liner

col@pwnable:~$ python -c "print(0x21DD09EC)"
568134124
col@pwnable:~$

These one liners will be really helpful as we go on. Now to find 5 numbers that add up to create 568134124 I use another small python one-liner

col@pwnable:~$ python -c 'print(" ".join([str(568134124 // 5)] * 4), 568134124 - (568134124 // 5) * 4)'
('113626824 113626824 113626824 113626824', 113626828)
col@pwnable:~$

The formatting is a little weird for sure but we get 5 numbers that will add up to form the hashcode. Each number has 4 bytes here and we need to each number to 4 characters of 1 byte each. Lets first convert them to binary

col@pwnable:~$ python -c "print(bin(113626824), bin(113626828))"
('0b110110001011100111011001000', '0b110110001011100111011001100')
col@pwnable:~$

Not too readable, lets segregate them into bytes

col@pwnable:~$ python -c 'a = 113626824; b = 113626828; print([bin((a >> i) & 0xFF)[2:].zfill(8) for i in range(0, 32, 8)], [bin((b >> i) & 0xFF)[2:].zfill(8) for i in range(0, 32, 8)])'
(['11001000', '11001110', '11000101', '00000110'], ['11001100', '11001110', '11000101', '00000110'])
col@pwnable:~$

That is more readable. The keen amongst you might be noticing that somehow the 1st byte is different between the 2 numbers instead of the last one. Well this is because the machine is little endian. If you used an external converter instead of the python one liner you might get a different result and would have to change the ordering of bytes to little endian. To check whether the machine is little endian or big endian, you can use this command

col@pwnable:~$ lscpu | grep "Byte Order"
Byte Order:            Little Endian
col@pwnable:~$

Now since we have the bytes time to convert them to characters

col@pwnable:~$ python -c 'a = 113626824; b = 113626828; print([chr((a >> i) & 0xFF) for i in range(0, 32, 8)], [chr((b >> i) & 0xFF) for i in range(0, 32, 8)])'
(['\xc8', '\xce', '\xc5', '\x06'], ['\xcc', '\xce', '\xc5', '\x06'])
col@pwnable:~$

And we have the ASCII characters associated with them ready. Now we can simply pass them into our program by piping

col@pwnable:~$ ./col "`python -c 'a = 113626824; b = 113626828; result_a = "".join([chr((a >> i) & 0xFF) for i in range(0, 32, 8)]); result_b = "".join([chr((b >> i) & 0xFF) for i in range(0, 32, 8)]); print(result_a * 4 + result_b)'`"
daddy! I just managed to create a hash collision :)

or simply

col@pwnable:~$ ./col "`python -c 'print("\xc8\xce\xc5\x06" * 4 + "\xcc\xce\xc5\x06")'`"
daddy! I just managed to create a hash collision :)

The last line is our flag and we have successfully solved this puzzle

Follow Me

Follow Me

Follow Me

Follow Me

© 2024 Rohan Goyal

© 2024 Rohan Goyal

© 2024 Rohan Goyal

© 2024 Rohan Goyal