Introduction
This keygenme is the 11th file under the Binaries (Windows/Linux) challenges at
www.ringzer0team.com. A keygenme is a challenge where you need to reverse engineer a serial checksum algorithm. At the time of writing only one other person has solved this particular challenge.
Launching the Program
I start by launching the program and seeing what it does, keeping an eye out for strings and certain API calls I will want to investigate once I start debugging.
I know right away two places I can start looking. The first is the location where the “Wrong Authentication code” string is loaded into memory. Backtracing from there, I should be able to find the reasons why the input I gave has failed. The next obvious place would be any calls to Win32 APIs which retrieve text from a textbox. There are at least 3 methods: each of them symbol exports from user32.dll.
GetDlgItemText()
SendMessage()
GetWindowText()
Opening it in a Debugger
I start the program in Immunity Debugger. Going straight after the string would be easy, and luckily finding the Win32 API calls is as well.
Go to
View -> Executable Modules, and a list of all loaded DLLs is shown. Right click on C:\WINDOWS\SYSTEM32\USER32.DLL and click
View Names. Scroll down to GetWindowTextA, right click and select
View Call Tree. I chose the first one, right clicked and selected
Follow Command in Disassembler.
We are taken right where we want to be:
From here we see that after the textbox values are grabbed, there is a call instruction to the function at 0x004014A0. If the result of the function is equal to 0 (false), the JE instruction jumps us to the “Wrong Authentication code” fail state. So we know we must get the condition where the call to 0x004014A0 returns true.
If we were only interested in bypassing the authentication, we could easily replace the JE instruction with two NOPs (0x90) by editing the hex of the .exe. This is how pirated software is commonly nulled or cracked. Unfortunately, we have to enter a valid key on the RingZer0Team website to get the flag, so we will have to delve a bit deeper and reverse the key check algorithm.
The “Validate” Function
When we take a brief look at the function in question, we realize it has the structure of a traditional type of validation function.
if (!test_condition1) return false;
if (!test_condition2) return false;
...
return true;
The first test is pretty easy:
The pre-defined ASCII string loaded into a register and the REPE CMPS assembly instruction is a give-away if you know what the instruction does. It is a mnemonic for “Repeat while Equal, CoMPare Strings”. The JNZ instruction will be true if the strings are not equal, and is a goto to a fail state (return 0) of the function. Here is the equivalent C code for the above assembly:
if (strcmp(username, "RingZer0") != 0)
return false;
The next test is also another commonly seen pattern when dealing with strings:
This snippet uses the REPNE SCAS instruction. This mnemonic means “Repeat while Not Equal, SCAn String”. It is defined to loop over the string until a character equals the value in the eax register, incrementing ecx for each iteration. Here is a C equivalent:
if (strlen(password) != 16)
return false;
Intel’s x86 assembly is full of shortcuts like this, where a single instruction can perform the work of many instructions, as long as the right data is placed in the right registers. This one was still a little hard to follow though, and is either the result of obfuscation techniques or an optimization by the compiler.
Next we come to a more interesting test:
This, like the previous snippet, has a bit of obfuscation to it. However it simply iterates over the characters and makes sure the first 16 (0x0 to 0xF) are between than ASCII hex codes 0x30 and 0x39, or the numbers 0 through 9.
for (int i = 0; i < 16; ++i)
if (password[i] > 0x39 || password[i] < 0x30)
return false;
The “Checksum” Functions
Next we come to a section of the code where two functions are called:
I investigated both of these functions in IDA Pro and after some time pouring through them deduced the following prototypes:
char* username_checksum(const char* username);
char* authcode_checksum(const char* password);
The username_checksum() function returns 16 characters, while the authcode_checksum() one only returns 5. After these functions are called, some of the values between the results are compared. More information on that in a second though.
I put a breakpoint on the return value of the username_checksum() function, then tested to see if changing the password had any effect on it (which would be possible if a global variable was modified somewhere else in the code). It did not change, and since I already knew the username had to equal to “RingZer0” from the first validation test, I decided not to focus any more attention on this function.
I set about reversing the authcode_checksum() function.
I was able to reconstruct the following C code:
static char buf[6] = { '\0' };
for (int i = 0; i < 5; ++i)
buf[i] = (96 - key[i * 3 + 1]) +
(-70 * (key[i * 3] - 48)) +
(13 * (key[i * 3 + 2] - 48));
return buf;
Now we can look at how the two checksums are compared, which is the last test of the “validate” function.
The equivalent C code paints a more obvious picture:
if ( auth_check[0] == user_check[1] &&
auth_check[1] == user_check[5] &&
auth_check[2] == user_check[8] &&
auth_check[3] == user_check[14] )
return true;
This is the return true we need to pass the “validate” function. I looked at the memory to find that the characters being compared in the user_check string were 0x98, 0x97, 0x78, 0x0f, and 0x15. This meant I needed to find an input for authcode_checksum() where it would return a string with those bytes in it.
Inversing the Checksum Algorithm
I opened up Qt Creator, in my opinion the best free C++ IDE, and wrote an inverse function for authcode_checksum(). It takes in the key values compared from the username checksum in order to generate the appropriate crack. This is a simple brute force since entropy was not very high.
When I ran the inverse checksum program, I got the following output:
Which I entered on the RingZer0Team website:
Thanks to @ekse0x for creating this fun challenge.