CTF: Challenge 45 from Dennis Yurichev

Bruteforcing copyprotection with Python



Introduction

On Dennis Yurichev's website it is possible to find a wide range of reverse engineering challenges. I randomly picked something for x86 on windows, that is, Challenge n. 45. It does not look like there is any solution on the internet, other than the original source code. So I figured this write up might be of interest to someone.

Let's check the readme: Patching would probably be too easy, so clearly we need to aim to modifying the key properly.

Reversing

We are given a PE and a key file. Running the PE, super_mega_protection.exe asks us to provide as argument a key file. Apparently the sample.key file contains a working key:

files>super_mega_protection.exe sample.key
Super-mega-protected software
Registration info:
Username=Dennis Yurichev
Serial number: 12345678

Let's inspect with an hex viewer the content of sample.key:

Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00000000 44 65 6E 6E 69 73 20 59 75 72 69 63 68 65 76 00 Dennis Yurichev.
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000080 4E 61 BC 00 Na┬╝.

We can see that there is the username that appears once the key is read. Following a 0 padding, and in the end some values.

Analyzing 0x00BC614E (little endian) as a 32bit int, we get 12345678. We would be tempted to think that, simply changing the name with our ASCII string would work. It would be too easy, and a quick test reveals that it does not work. Interestingly enough, changing the SN (while keeping the same user) does work. This should make us understand quickly that there is something going on with the username, rather than the SN.

Let's start reversing the binary to see what is happening. Firing up Cutter we can start from main() and see from there: This is rather simple: it checks if the arguments provided are enough. If it is true, it calls a function passing the filename:

0x00407a55 lea eax, [var_1ch]
0x00407a59 mov dword [var_4h], eax ; int32_t arg_34h
0x00407a5d mov eax, dword [esi + 4]
0x00407a60 mov dword [esp], eax ; char *filename
0x00407a63 call fcn.00401720
0x00407a68 cmp dword [var_1ch], 0x84

After the call, a global variable is compared to 0x84. If it's not equal, an error message is returned. A quick inspection of the sample.key file provided, reveals us that indeed the size of the content is 0x84.

Disassembling the function fcn.00401720 (last part in the following image) it results clear that it simply reads the file, and returns the pointer of the read content to eax. The comparison of eax is with 1, because fread returns the number of chunks read, and the size of the chunk used in this case is the length of the file (previously found in the function, not shown).

Going on in the main(), if the comparison with the correct size (0x84) is successful, we have this: We are passing our read buffer, and the length calculated by strlen, to the function fcn.004015f0. The reader should note that strlen does only calculate the length of the string up to 0, this means that the rest of the read buffer is completely ignored. Once more, we are tempted with the idea that the SN is not checked, and the control is only performed on the username.

Opening up the aforementioned function shows us a long algorithm:
As it is evident from our previous graph, the result from these operations is compared to 0xe425. In case of success, the key is accepted, and the info about the SN are printed. The "tricky" part of this challenge was to realize that the SN is just a decoy, left there to make you think that in order to change the username, it has to be changed. In reality, all the checks are performed only on the username.

So how can we put our name there?

A more knowledge reverser might have realized that this algorithm is actually CRC16, in particular X-25. Indeed, CRC16-X-25("Dennis Yurichev") = 0x25E4 (which is then saved little endian, thus 0xe425).

My approach was to replicate the algorithm in python (trying to simplify it as much as possible), and then exploit that.

But, how can we change with our name? The answer is: hash collisions. Function fcn.004015f0 calculates a hash given a string. Then checks if the hash is 0xe425 (0x25e4). In case it is, it considers it to be valid.
Therefore, we can bruteforce hashing strings, up to get to our desired value.

Solution

My solution was:

file>super_mega_protection.exe sample3.key
Super-mega-protected software
Registration info:
Username=Michele!!!!!.fM
Serial number: 1337


Following, the python code used to replicate the hashing function, and to bruteforce a collision. We can consider as a correct solution, something that contains our desired name. For this reason, the script starts the loop with a defined string ("Michele!!!!!!!!"). Indeed, the '!' padding is there to assure that the collision will happen with our name not altered.