CTF: Challenge 45 from Dennis YurichevBruteforcing copyprotection with PythonIntroductionOn 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:
This is a software copy protection imitation, which uses a key file. The key file contain a user (or customer) name and a serial number.
Patching would probably be too easy, so clearly we need to aim to modifying the key properly.
There are two tasks: - (Easy) with the help of any debugger, force the program to accept a changed key file. - (Medium) your goal is to modify the user name to another, without patching the program. ReversingWe 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
Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 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] 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. SolutionMy solution was:
file>super_mega_protection.exe sample3.key 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. |