Source: https://crackmes.one/crackme/6214dd6233c5d46c8bcbff61
In previous crackmes we learned how to use Ghidra basic internal tools like the disassembler view and decompiler to solve those puzzles. But to be honest those riddles were pretty easy and had no other goal but to teach you how to do BASIC REVERSE ENGINEERING analysis. On the contrary in this, 3rd crackme, we've finally come to something serious.
Are you ready? Good. Let's have a look at this one.
First, run the program.
And give it some input. What should we choose? Maybe 1?
Author claims that this crackme has to be solved only by keygen creation. We should run this program again to look into the password input process.
The program asks for username (4 characters length minimum) and then asks for password. As you can see our naive endeavor had a mythological size of failure. But is it the end of our journey for solving this mystery? Of course not! And that is why we have GHIDRA!
For the sake of readability and to make this lesson more about reversing and problem solving I've already renamed the variables and function names in some decompiled sections of the crackme, so you, dear reader, can concentrate on the main thing: KNOWLEDGE.
First, we have to find the main()
function if it's signature isn't missing of course.
We use the Symbol Tree window to find the symbols in our program and the main()
function is there. Like in other 2 lessons, this crackme will be solved using static analysis approach and mostly interpreting the decompiled code.
In the decompiler window we have the "bricks" of C code of our program or something that GHIDRA tried to understand and give us some essay about the history of crackmes origin.
Most of the decompiled code is very self explanatory. Right from the start inside the main()
function we can observe the usage of standard C library functions for input/output like puts()
, printf()
, scanf()
. Section [13-15] takes us to the rules segment of program.
Here we can see the rules that author gives us which has to be followed by us, the Knights of Decompiled Enigmas.
Return to main()
function. Section [16-18] brings us to the inner part of mechanism that processes the input of user/password strings: function tryToKeygenMe()
with number 10 as parameter. This number will be used further as attempts left for password input.
In the name of all cybernetic gods! We have a lot to dig in! Don't worry, because every single step of this program's logic will be uncovered.
- Lines [5-10] variables declaration
- Lines [12-18]
do {...} while (the length of username < 4)
cycle where the name length is checked with_strlen()
function and where our name string is stored toinput_name_str
variable. Interesting thing thatis_Equal
variable will be used again for another comparison - Lines [19-36]
do {...} while (attempts_left != 0)
cycle is the main domain, the castle of the whole crackme where everything happens.- Line 20 each time user enters the password the variable
attempts_left
is decreased by 1 - Line 22
scanf()
function writes the user password input toinput_pass_str
string variable - Line 24 is the heart of the whole program! It is the place where the REAL password is generated by
getEncrytKey()
function usinginput_name_str
variable and then the valid password is copied toreal_pass_str
variable. We'll return to understanding what is going on inside this "encryption" function later - Line 25 again the program uses
is_Equal
variable to store the result of_strcmpKey()
action wherereal_pass_str
andinput_pass_str
strings are compared - Lines [26-29] if
is_Equal
is 0 (our password and real password ARE NOT THE SAME) then the program outputs current attempt number and the message usingprintf()
which informs us that input password is incorrect - Lines [30-32] else if
is_Equal
is 1 (input_pass_str == real_pass_str
) then the program gives us nice goodbye usingputs()
function and exits the program - Lines [33-35] if there are no attempts left then exit the program. This time "goodbye" is not very nice for us
- Line 20 each time user enters the password the variable
The things we know are:
- Valid password is somehow generated by calling
getEncrytKey()
and it usesinput_name_str
string variable - And only then
_strcmpKey()
function is executed where the program decides are we the one who is destined to meet his princess or to meet something worse (dragon maybe?)
And while the inner composition of _strcmpKey()
function is pretty linear and easy to understand
- Lines [5-8] variables declaration
- Line 10 is where
is_Equal
(it's local variable, and it's not the sameis_Equal
that's intryToKeygenMe()
) is initialized with 1 by default, meaning that until proven wrong theinput_pass_str
and thereal_pass_str
ARE THE SAME STRINGS. - Lines [11-12]
_strlen()
function is called to evaluate the length of both the input and real password strings and then assign the length values to its corresponding variables - Lines [13-19] if these length values are equal ONLY THEN dive into the
for (...) {...}
cycle where every single character of bothinput_pass_str
andreal_pass_str
are compared through accessing the arrays ([counter + input_pass_str]
and[counter + real_pass_str]
where the 0 index of both arrays (beginning) is used and then incremented bycounter
) - Line 15 if the current processed characters of these strings are not the same
_strcmpKey()
immediately returns 0 - Lines [20-22] is the place where
is_Equal
is re initialized with 0 if the check at line 13 returns false - Line 23 return the value of
is_Equal
The parts of getEncrytKey()
is where we have to unleash the full power of understanding the logic behind this crackme
- Lines [5-8] variables declaration
- Line 10 is the place where special variable
key_str_len
is initialized with the result of_strlen()
evaluation of some mysterious value from unknown place in memory. What's that variable for? It will be used to generate the real password string. Nevertheless, we should take a look what is hidden inside this adress0x4051f8
Very interesting! So the ds
section in assembly representation gives us the string tryHarderToMakeAGoodKeyGen
. We already know that this string will be taken as a parameter to _strlen()
at line 10 of our encryption function. So the length will be 26. Good. Now we have key_str_len = 26
-
Line 11 the length of
input_name_str
is written toinput_name_str_len
by calling_strlen()
later it will be used the main encrypted password generation cycle -
Lines [12-15] demonstrates the memory allocation process using
malloc()
function. It is a standard procedure for a program to determine memory chunk it can work with -
Lines [16-21] the moment of truth! Here everything happens. It is the place where the real password is generated
- Line 16 classic
for (...) {...}
cycle that goes through all the length ofinput_name_str
- Line [17-20] is the encryption algorithm. If we get rid of decompiler interpreted type casting and just write the expression we'll have this:
- Line 16 classic
The principle is very simple. Every cycle iteration (through input_name_str
length):
- We take current
input_name_str
char representation as byte (accessing by indexing mechanism through addingcounter
to the beginning ofinput_name_str
) - Raise the value we got to the power of 26 (
key_str_len
) - Take the result and apply modulo operation using
input_name_str_len
- Assign the calculated value to the current index of
real_pass_str
(yes, address arithmetic)
- Line 22 finally we get the real password stored in
real_pass_str
which is then returned by function
There is only thing left. We have to write the keygen program that generates password for every name we want. And we'll do this in a right way. In C.
Create the source file with this code (you can always find keygen.c in this crackme folder):
#include <stdio.h>
#include <string.h>
int main(int argc, char const *argv[])
{
char input_name_str[100] = "";
char real_pass_str[100] = "";
int input_name_str_len;
int key_str_len = 26;
int index;
printf("Enter name: \n");
scanf("%s", input_name_str);
input_name_str_len = strlen(input_name_str);
printf("%d\n", input_name_str_len);
for (int i=0; i < input_name_str_len; i++)
{
int char_int = input_name_str[i];
index = (char_int ^ key_str_len) % input_name_str_len;
real_pass_str[i] = input_name_str[index];
}
printf("Your password is: \n");
printf("%s", real_pass_str);
return 0;
}
Yeah, yeah, this program does not have all this "true blood C" things like memory management and pointers, but it does what it has to. And the main thing, the encryption algorithm, is implemented here:
input_name_str_len = strlen(input_name_str);
printf("%d\n", input_name_str_len);
for (int i=0; i < input_name_str_len; i++)
{
int char_int = input_name_str[i];
index = (char_int ^ key_str_len) % input_name_str_len;
real_pass_str[i] = input_name_str[index];
}
We use the same approach as in the crackme.
- Get the length of
input_name_str
string and assign it toinput_name_str_len
- Open the
for (...) {...}
cycle which goes through all the lengthinput_name_str
- On every iteration of this cycle we convert the current input name string character to
int
representation and assign the value tocahr_int
variable - Calculate the
index
variable value using the formula which we deducted above - Use the
index
value we found to get theinput_name_str[index]
cahracter and write it toreal_pass_str[i]
The result password string will be printed here
printf("Your password is: \n");
printf("%s", real_pass_str);
Then we need to compile this file. I use GCC
compiler
gcc keygen.c -o keygen.exe
Why don't we test our keygen? Let's say we want to generate password for the name Ares (remember the 4 characters minimum length requirement)
And it is time to run the crackme to prove that we've made it!
Who is the Master of binary spirits? You're! Congratulations!