Solving the “HardCrypt” Crackme Challenge

Hello World,

Today we will be solving a Crackme challenge, it’s an easy and interesting RCE practice.
What drew my attention is the key generation part, my approach is to try to avoid the bruteforce solution as possible and instead focus on the mathematical approach for the key generation algorithm. The target application uses an old tricky way to avoid the reversing of the key generation algorithm.

We are going to reverse engineer the Crackme, we are going as well to discuss some other solutions that were shared on the challenge page.

So take a seat, bring your coffee and enjoy the ride.

First of all, you can get a copy of that Crackme on the famous crackmes.one website, it’s named (HardCrypt), written by The Mentor.

Now, let’s load our binary into PEiD to check if it was packed, luckily we found some information that no packer was used on this binary after the compile step.
Of course, that might be a false positive, experienced reverse engineers should know about “custom packers”, a kind of packer that is able to fool a PE scanner tool.

Let’s start our static analysis by loading the binary into any disassembly/debugging tool, I like to use Immunity debugger, let’s make the very usual steps anyone would begin with, finding references to known strings in the memory space of the binary.

As can be seen, we found some reference strings that seem familiar. Let’s double click the “wrong pass” string, that will take us to the text section that contains a pointer to this buffer.

We can see that the application uses some GLibc functions, such as gets(), puts(), and system(). One special function that should have caught attention is the strcmp().

strcmp() takes 2 parameters and returns zero if they had the same bytes. Let’s take a further look in that strcmp() call, we are going to place a breakpoint on the strcmp() call, and restart our application within the debugger, and as soon as a string is input, the debugger should pause at the call to strcmp() instruction, we would then look on the stack memory, and hopefully find the pushed parameters in there.

We are able to see two references to the provided strings, one reference to our input string, and another to an unknown string passed to the strcmp() function. At that point, we can easily patch the Crackme application and we’ll be done to always return zero at the strcmp() call, however we will be doing this the more fun way, by trying to figure out how that apparently random string is built, then write an algorithm that is able to generate valid strings. Modifying the execution flow must always be our final resolve, at least in my own perspective.

Now let’s take a deeper dive into this unknown string, a reference is situated at EBP-137 on the stack, which is the address 0x0022FE11 during my execution of the program, also we can notice that this string is generated even before we provide our input to the Crackme. We can scroll up to the beginning of the current procedure, and place a break point at that address, then restart the application within the debugger. We can quickly notice that the breakpoint was hit before getting to the gets() call, i.e. before even providing our input string, that means that the key is generated in the prologue of the application, that implies that it doesn’t have any mathematical relation with our input, which also means the key is generated using one (or many) of our system’s resources (date, RAM/CPU usage, CPU ticks, etc…).

Let’s examine that by stepping over the instructions, and the place where we placed a breakpoint the last time, until hitting a call to time(), it’s a win32 API function that returns the system time, it’s used with NULL as parameter, and the returned value is stored somewhere in memory. After that we can also notice another win32 API function, localtime() which takes a time value and corrects it to the local zone time. localtime() function expects one input, a pointer to the location where the system time call returned value was stored.
On MSDN, localtime() returns a struct that contains precise values about current time, this struct is named TM structure, defined on MSDN as:

struct tm {
int tm_sec;
int tm_min;
int tm_hour;
int tm_mday;
int tm_mon;
int tm_year;
int tm_wday;
int tm_yday;
int tm_isdst;
};

After the call to localtime(), a pointer to the TM structure is saved into the EAX, then the struct values are copied into some memory locations.

So far so good, after that a busy wait loop is executed, doing nothing for 190 million times, a not so fancy way to sleep.
After that loop, we can find a very interesting iteration starting at address 0x004013CF in the following screenshot, this loop iterates 3 times, each iteration a call to the win32 API function GetCursorPos() is executed. This function according to the MSDN expects one parameter, a pointer to a POINT structure. Again, looking at POINT structure on MSDN, it’s a structure that contains 2 objects holding the current mouse cursor coordinates. After the function GetCursorPos() is done, it multiplies the x and y coordinates, adding the result into an object initialized with the hexadecimal value 0x0022FFF0, and stored in memory.

After the three iterations, the resulting value is multiplied by the sixth member of the TM structure, the tm_mon (month), then subtracted by the third member of the structure, which is the tm_hour (hour). It then calls the win32 API function _itoa() that expects an integer variable, a pointer to a string, and a numbering system, converting the integer in the number system provided to a string.

Finally, it appends the string “H!J” to the resulting string, and we now have the key that’s going to be compared with the user input string.

As mentioned above, we could have just patched our strcmp function earlier in the application, and that’s the easy way, but that won’t always be the case. Sometimes, some binaries may contain some CRC or a type of checksum algorithms to protect the binary image of any potential alteration/modification. After our analysis we are now able to write the code to generate a valid key.

However, even such a solution isn’t that straightforward. The generation function takes input from the time() and mouse cursor position, which wouldn’t be the same at the time of execution of the Keygen, the time and the cursor position would be different in the keygen and the crackme. One way shared by the users who solved that Crackme was to inject a process that suspends the execution of the Crackme right at the start, injects a DLL, which reads the memory location containing the calculated values of the valid key, and restores the execution flow of the Crackme afterwards. This is a good solution in my opinion. Another solution was code caving the same logic, however that modifies the binary.

Finally, here is a C-like pseudocode of the Key generation algorithm.

var1 = time(NULL);
struct tm *x;
x = localtime(&var1);
var2 = 0x0022FFF0;
struct tagPOINT *pos;
for(i=0;i<3;i++) {
    GetCursorPos(pos);
    var2 += (pos->x)*(pos->y);
}
var2* = x->tm_mon;
var2 += (0-(x->tm_hour))*2;
char str[30];
_itoa(var2,str, 0x0A);
strcat(str,"H!J");

Leave a comment