My First Crackme's RE Challenge

Photo by Shane Aldendorff on Unsplash

I was thinking about doing a Reverse Engineering challenge for a while, but I was having trouble finding the right time for it. Well, this moment has finally come and I found this website called Crackme where there are a bunch of these challenges along with their solution. So, I thought of giving them a try.

In this article, I document the process I followed to solve this challenge. This challenge helped me understand more Windows APIs and improve my general RE skills. The goal of this post is not to teach you how to solve this challenge, nor any special RE trick. Instead, it is a way for me to recap the process I followed from the start till the end.

Okay, ready?

About The Challenge

You can download the challenge at this link. This challenge is designed for Windows systems. The goal is to find out which of the 84 buttons is the one that triggers the serial checking routine, and then write a valid key.

I mainly used IDA 8.2 disassembler.

First analysis

Challenge screenshot

First, I tried clicking a few buttons. Each time I click the wrong button, the program shows a message box with an error message and the program exits. I looked at the strings view, and I tracked the location of the two strings used in the error message box. I also noticed another string, but its role was not very clear at the beginning so I wanted to investigate more.

String section view

The first goal is to find the button-checking routine. At this point, I started researching about how a button click can be handled in a Windows application. I found this page where I learned a lot. I was looking for a WNDPROC callback function. A WNDPROC callback processes messages sent to a window depending on the message code specified. Specifically, a button click sends a message with code WM_COMMAND, as documented here.

Since the function table contains a few entries, it was easy to find it. I renamed the function as WndProcHandler and this was the result:

WndProcHandler code graph view

Okay, now I analyzed the disassembled code a bit. A WM_COMMAND message is identified by the code 0111 in hexadecimal, so I followed the switch-case style blocks above until I reached this:

Button message click with params

Now, at first I didn’t understand the reason of those params. After a while, I found this example in the Windows documentation at this link:

BOOL AboutDlg (
    HWND hDlg, 
    UINT message, 
    WPARAM wParam, 
    LPARAM lParam)
    BOOL bRet = FALSE;
    switch (message) 
        case WM_INITDIALOG:
            bRet = TRUE;

        case WM_COMMAND: //here the switch-case statement I am looking for
            if (wParam == IDOK || 
                wParam == IDCANCEL) 
                EndDialog(hDlg, TRUE);
                bRet = TRUE;
    return bRet;

I noticed that the wParam specifies additional information about the event, so perhaps this might be the button number. I tried running it with the debugger, and the next code block is:

cmp     ax, 0Dh
jnz     loc_4018B6

Which indeed compares a byte taken from EAX with 0D, the hexadecimal for 13. The 13th button triggers the key validation procedure.


Key validation

At this point, I need to find a valid username/password to win the challenge. I started doing some dynamic analysis of the software to understand it better.

I started with the “Congratulation…” message. My idea was to follow the code in the opposite direction to track the exact location of the key validation section.

First, I got into this block:

push    dword_403258
push    offset aD       ; "%d"
push    offset byte_4032C4 ; LPSTR
call    wsprintfA
add     esp, 0Ch
push    dword_40325C
push    offset aD       ; "%d"
push    offset byte_403328 ; LPSTR
call    wsprintfA
add     esp, 0Ch
push    5Ah ; 'Z'       ; nMaxCount
push    offset String2  ; lpString
push    dword_403168    ; hWnd
call    GetWindowTextA
push    5Ah ; 'Z'       ; nMaxCount
push    offset byte_4033F0 ; lpString
push    dword_40316C    ; hWnd
call    GetWindowTextA
push    5Ah ; 'Z'       ; nMaxCount
push    offset byte_403454 ; lpString
push    dword_403170    ; hWnd
call    GetWindowTextA
push    offset String2  ; lpString2
push    offset lengthString ; lpString1
call    lstrcmpA
or      eax, eax
jz      short loc_40186D

From the three GetWindowTextA, I realized that this part extracted the password from the text boxes. Nice, but first I need some info about the username.

A few blocks above in the code graph I found what I was looking for:

getUsernameFromBox:     ; nMaxCount
push    5Ah ; 'Z'
push    offset usernameString ; lpString
push    hWnd            ; hWnd
call    GetWindowTextA
cmp     eax, 2
jnb     short loc_40175A

The code above contains a few names I put myself, like getUsernameFromBox as the name of the procedure and usernameString. Anyway, the call to GetWindowTextA confirms that this part was responsible for getting the username from the textbox.

At this point, I started debugging with IDA to follow the steps and operations put in place to get the username and password. The goal was to catch, eventually, some insights about the credentials evaluation procedure.

The validation algorithm

By following the execution step by step with IDA I was able to reproduce the validation algorithm.

You can put whatever username you like (string length greater than one). Depending on the username you put, a password is generated in this way:

  1. The first part of the password is the number of chars in the username
  2. The second part is obtained by multiplying each digit of the username (converted to ASCII code) by a constant factor and adding it all together
  3. The third part is obtained by summing each digit of the username (converted to ASCII code)

While following the execution I wrote on paper the operations I was doing, but in the end I wrote a Python script that serves as key generator:

USERNAME = "123"
MUL_CONST = 1337 #539h
pwd_1 = str(len(USERNAME))
pwd_2 = 0
pwd_3 = 0

for i in USERNAME:
  bt = ord(str(i)) * MUL_CONST
  pwd_2 += bt

  bt = ord(str(i))
  pwd_3 += bt

print("{} {} {}".format(pwd_1, pwd_2, pwd_3))

And, after using this script to generate the credentials, we are done!

Congratulations message

I hope you enjoyed this post and please write to me if you have some tips about RE or some feedback on this article.