Skip to content

Fatmike's Crackme #2

Crackme Information
  • Difficulty: 2.7
  • Rating: 5.4
  • Platform: Windows
  • Language: C/C++

Download / View on crackmes.one

We run the executable through DIE, find it's UPX packed, unpack it, then run it:

image_1766506183229_0.png

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.

image_1766506526035_0.png

We can see global variables being set in function get_fn_ptrs:

image_1766506652763_0.png

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:

image_1766273067514_0.png

Looking around a bit I find this:

image_1766273122277_0.png

They all get set in the get_fn_ptrs function above. Curious, let's try inversing all bytes using cyberchef:

image_1766272976665_0.png

We find a similar function (more complex) for the MessageBox text, decrypting we obtain:

image_1766274626391_0.png

We also find this, it's just XORing bytes in memory with 0xC8 using some tricks with xmmwords, but the logic is the same:

image_1766319539850_0.png

Copying bytes with export:

image_1766319626836_0.png

Now we need to find what triggers this function, but there's one issue, neither it nor the pointer to it is used anywhere else in the entire program!

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:

image_1766603959045_0.png

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 0x210 decimal
  • Multiplying by 0xDEADBEEF we get 0x1CB4659CCF0
  • wsprintfA truncates 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:

image_1766675367197_0.png

We end up with: X46X-X59X-XCCX-XF0X. Let's try that:

image_1766676537412_0.png

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:

image_1766676726016_0.png

So let's analyze this function:

image_1766676827569_0.png

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.

image_1766677252026_0.png

Then a checker gets called. Inside this checker function:

image_1766677148560_0.png

image_1766677178264_0.png

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.

image_1766682821316_0.png

image_1766682919723_0.png

image_1766683060359_0.png

So for xor_key_str_1:

image_1766683913480_0.png

And xor_key_str_2:

image_1766684271697_0.png

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:

image_1766684609013_0.png