Collision Writeup (Pwnable.kr)
About Pwnable.kr
pwnable.kr is a free, non-commercial wargame site that hosts hands-on binary-exploitation challenges you solve by SSH’ing into remote challenge accounts, analysing provided binaries/source, and exploiting them to read flag files. It’s ideal for learning and practicing low-level hacking techniques (buffer overflows, format strings, heap/stack bugs) with community writeups and a ranking system.
Overview
Collision is an easy binary exploitation challenge from “pwnable.kr”. This challenge’s main theme are MD5 hash collisions, which is also the reason why MD5 algorithm is deprecated nowadays.
Accessing the binary
We can access the binary via SSH:
┌──(root㉿kali)-[/home/kali]
└─# ssh col@pwnable.kr -p2222
[password: guest]Looking inside the current directory, we can find the binary, it’s source code and the irresistible flag.
col@ubuntu:~$ ls -la
total 44
drwxr-x--- 5 root col 4096 Apr 2 2025 .
drwxr-xr-x 118 root root 4096 Jun 1 12:05 ..
d--------- 2 root root 4096 Jun 12 2014 .bash_history
-r-xr-sr-x 1 root col_pwn 15164 Mar 26 2025 col
-rw-r--r-- 1 root root 589 Mar 26 2025 col.c
-r--r----- 1 root col_pwn 26 Apr 2 2025 flag
dr-xr-xr-x 2 root root 4096 Aug 20 2014 .irssi
drwxr-xr-x 2 root root 4096 Oct 23 2016 .pwntools-cacheAfter running the “col” program couple times, we know that we need to provide a passcode with the length of 20 bytes.
col@ubuntu:~$ ./col
usage : ./col [passcode]
col@ubuntu:~$ ./col pass1234
passcode length should be 20 bytes
col@ubuntu:~$ ./col password1234567890*#
wrong passcode.Analysing the source code
Now let’s look at the source code and analyse it closely.

After close inspection, we can get a good idea on what’s this program all about. Let’s break it down and explain each code section separately.
1st part (prologue):
#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC;Imports necessary libraries and defines global “hashcode” variable with certain hex value. Relatively straight forward.
2nd part (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;
}Resuming in the “main” function, the code above just checks if we had provided an argument with the length of exactly 20 bytes. If not, fail message will display.
3rd part:
if(hashcode == check_password( argv[1] )){
setregid(getegid(), getegid());
system("/bin/cat flag");
return 0;
}
else
printf("wrong passcode.\n");
return 0;The program then runs the user-supplied string through custom hashing function and compares the result to “hashcode” (”0x21DD09EC”). If those match, the flag gets printed out.
4th part (hashing function):
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;
}This one might feel chaotic, but it really isn’t. It starts with converting our string to associate ASCII decimal numbers. Then, it runs through a loop that adds the first 5 numbers to the “res” variable, which gets returned and compared afterwards. Quite simple hashing function.
Wait, but why are there only 5 iterations?
But wait, why only the first 5 numbers? Don’t we have a string with 20 characters? Well, it’s important to remember that 1 character = 1 byte and that 1 integer = 4 bytes. Those 20 characters actually perfectly fit within those 5 integers (20 characters / 4 bytes of integer = 5 integers).
![]() |
| explanation by ChatGPT |
Exploiting the hash collision vulnerability & getting the flag
Let’s construct a plan based on our analysis. To pass the comparison, we have to supply the program with passcode that produces the “21DD09EC” hash in hex. Since we now know that the hash consists of summed ASCII decimal values, we can pull up a ASCII table and construct a valid passcode. Remember, that the length must be 20 bytes (~ 20 characters).
But before all that, let’s just convert the hex value of “hashcode” into decimal, which is 568,134,124. Damn, what a large number.

Since the result consists of the sum of 5 integers, let’s divide the number by 5. Looks like we get number 0x6C5CEC8 with the reminder of 0x4.

That means that we can construct 4 identical integers with the 5th being higher by 4 (that means 4 times 0x6C5CEC8 and 0x6C5CECC). But how do we achieve that?
NOTE: Why can’t we simply pass “0x21DD09EC” value with 16 null bytes (”0x00”)? The length is 20 bytes. Well, that wouldn’t work due to the fact that the null byte indicates the end of a string. Reaching the first null byte, the program would stop reading the rest of the data.
![]() |
| the null byte terminated the input read |
Going back to our payload, we might be able to use the ASCII table to construct it. But looking at the hex values, we quickly realise that we don’t have the range we need.

That’s why we have to use raw bytes in out payload. To pass raw bytes in binary exploitation challenges like these, I like to use Python2.
We can use Python directly in our terminal. Example usage below:
col@ubuntu:~$ echo `python2 -c "print '\x48\x65\x6c\x6c\x6f'"`
HelloGoing back to the calculated values, we can use 4 times “0x6C5CEC8” plus “0x6C5CECC” to get the expected hashcode “0x21DD09EC”. Using Python2, we can print those bytes like this:
`python2 -c "print '\xc8\xce\xc5\x06'*4 + '\xcc\xce\xc5\x06'"`We can pass our byte payload as argument to the “col” binary with backticks. Aaaaaand… we get the flag.
![]() |
| final payload, causing hash collision and passing the check |
And that’s the entire Collision challenge! If you came all the way down here, well done!
Hash collisions in the real world
This was a very simple example of hash collision. Our payload was one of many that would pass the check in the program (subtracting a few bytes and adding them elsewhere wouldn’t alter the result). This is why hash collisions are a problem. Multiple inputs can produce the same hash, the same signature.
Hashing is widely popular method of verifying the integrity of files (single change in the file would change the entire hash as well). If we change a file in some way and it’s signature/hash stays the same because of a collision, we cannot say with 100% certainty that the file has not been modified. That’s why hashing algorithm MD5 is deprecated nowadays and is not being used anymore, because it produced quite short hashes and collisions were a big problem. Modern algorithms like SHA-256 produce much longer hashes, and thus avoid the hash collisions, which makes them secure and trust-worthy for creating digital signatures.
Summary & final thoughts
Collision is an easy binary exploitation challenge from “pwnable.kr”. This program uses simple hashing function to simulate weak real-world hashing algorithm. It’s biggest weakness is the fact that different input can result in the exact same output. After code analysis, we understand how the algorithm works and find one of the input strings that result in a valid hash.
Once collisions are detected, the used hashing algorithm becomes insecure and eventually deprecated. This is a big deal for the real world, so it’s crucial every cybersecurity warrior understands it. Overall, very nice and simple challenge. New cybersecurity enthusiasts should definitely give this one a shot.



Comments
Post a Comment