Fatmike's Crackme #2
Crackme Information
- Difficulty: 2.7
- Rating: 5.4
- Platform: Windows
- Language: C/C++
We run the executable through DIE, find it's UPX packed, unpack it, then run it:
Register button does nothing, "Go" shows the information dialogue.
Analysis
Inside WinMain we find a bunch of routines, calls to virtualProtect, which suggest the executable might modify its own code at runtime.
We can see global variables being set in function get_fn_ptrs:
Here I have already examined the behaviors of each function and figured out what they do. Let's look at the important ones:
Functions
We see 11 iterations:
Looking around a bit I find this:
They all get set in the get_fn_ptrs function above. Curious, let's try inversing all bytes using cyberchef:
Reversing the Registration
Let's look at the DialogFunc. Inside we find a big switch case which corresponds to the buttons clicked in the program: "Register", "Go", "About", etc. The register button looks like as follows:
We can see that our name gets stored in the stack and the serial in the heap. Our input name must be at least of length 3, and the serial exactly 19 characters long, separated by dashes (i.e: XXXX-XXXX-XXXX-XXXX).
We then do some operations in case name is longer than 8 characters but those seem to just be compiler-optimizations again. We can just ignore it though assuming we use a short name.
Essentially we are hashing our input name and using it to make up parts of the serial:
- We add up all the name characters, then multiply the value by 0xDEADBEEF, finally store the value as hexadecimal string inside a buffer. Then we compare parts of this buffer with some bytes in memory that correspond to certain characters of our serial input.
For input name "ninja":
- Adding up all characters gets
0x210decimal - Multiplying by
0xDEADBEEFwe get0x1CB4659CCF0 wsprintfAtruncates and stores 8 characters as a string "4659CCF0"- The conditional statement makes sure these characters are present at certain positions of the serial input.
As seen here:
We end up with: X46X-X59X-XCCX-XF0X. Let's try that:
Cracking the Rest
Still, our "Go!" doesn't work yet. We know that when we pass those Register checks, we set p_serial_fn_maybe to a o_decrypt_addresses function. This function gets called when we click on the Go! button:
So let's analyze this function:
It's essentially XORing bytes in memory with parts of our serial input, which correspond exactly to the missing "X" parts of our serial address. Since we don't know X, we don't know how to decrypt the values correctly yet.
Then a checker gets called. Inside this checker function:
This function is checking if some of the values that were written in the XORing function match the address of some function, and a fixed number. If correct, the function overwrites itself with the values in memory (this is why we had VirtualProtect function calls) that were XORed earlier, then calls itself again.
Since we are checking against fixed values, we can actually XOR the result with the original bytes to figure out the XOR key strings.
So for xor_key_str_1:
And xor_key_str_2:
We now know the rest of our missing Xs, they spell out KEWL SHIT.
For name ninja, our serial X46X-X59X-XCCX-XF0X becomes K46E-W59L-SCCH-IF0T.
Let's try it out:






















