This cracking challenge was posted by Shad3 on crackmes.one. Thank you Shad3! The objective of this challenge was to determine how the executable was generating keys and write a program that would generate valid keys for the executable. For this crackme I will be using Radare2 for my analysis.
The first thing I did after unzipping the folder was list the files that came out of the directory and execute the program.

Now that we have a basic idea of what this program does lets take it apart. First we will look at some of the basic program information with the iI command.


Here I am telling radare2 to analyze the binary and then listing all of the functions. There are mostly library/standard functions with the exception of main and sym.check_key. Next lets see what main is doing.

Here we can see that we query the user for a password, store the password in local_70h, and then make a call to check_key. If we look a little further we see a success message but no failure message meaning that the program will probably print the failure message (if there is one) in the check_key function. Now lets look at check_key

Here is the first chunk of the function. The first thing to notice is that the user input is being stored in local_28h. Then a call to strlen is made. If the length of the string is <= 7 then a jump is made to 0x839 which prints the failure message and exits the program. If the string length is > 7 then continues on to make a second call to strlen and compare it to 0xa (decimal 10). If the string length is <= 10 then a jump is made to 0x854 and if length is > 10 then the program reaches the failure sequence and is terminated. Lets check out that jump to 0x854

Here we see the program loading a 0 into local_18h and then jumping to 0x877. At 0x877 we see local_18h is being loaded into eax and strlen is called again on the user input. There is some shuffling that happens between the mov and the call to strlen that basically amounts to preparing local_18h to be compared with the length of the user input at 0x889. If local_18h is < the length of the user input then the program jumps back up to 0x85d. At 0x85d local_18h is reloaded into eax and then sign extend mov’ed to rdx. local_28h (user input) is loaded into rax and then is incremented by the local_18h. Remember that in main it was the memory address of the user input buffer that was loaded into rdi – not the value itself. In the next two lines (0x86a and 0x86d) the ASCII value of the character located at the base address of the input buffer offset local_18h is loaded into eax. local_14h is then incremented by the value in eax and local_18h is incremented by 1. Based on the behavior of the program, we can infer that local_18h is a counter variable in a loop and local_14h is an accumulator variable that holds the sum of the ASCII values of the string. When we have looped through the whole input the accumulator is checked against 0x3e7 (999 decimal) and if the value of the accumulator is > 999 then we jump to the end of the function, set EAX to 1, and return to main.

In main if EAX is 1 then we will receive the success message. Now that we know how the keys checked we can move on to writing a generator. I will not be writing something that generates every single possible key here but all keys that are generated will be correct. The program will be written in C.
#include "stdio.h"
#include "stdlib.h"
#include "time.h"
#include "string.h"
void generateKeys()
{
int minASCIIValue = 100;
int maxASCIIValue = 127;
int keyLen = 10;
int numKeys = 100;
time_t t;
int charSetSize = maxASCIIValue - minASCIIValue;
int currASCIIValue = minASCIIValue;
int counter = 0;
char charSet[charSetSize];
char currKey[keyLen];
srand((unsigned) time(&t));
for(counter; counter < charSetSize; counter++)
{
charSet[counter] = currASCIIValue;
currASCIIValue++;
}
for(counter = 0; counter < numKeys; counter++)
{
for(int i = 0; i < keyLen; i++)
{
currKey[i] = charSet[rand() % charSetSize];
}
printf("%s\n", currKey);
}
return;
}
int main()
{
generateKeys();
return 0;
}
In lines 9-22 I am doing some initialization. In lines 9 and 10 I am setting the ASCII range of the characters that will be generated. Since our goal is to generate a string that will sum to be over 100 I am purposely choosing high value characters. On line 11 and 12 I set the key length and the number of keys that my program will generate. We know that the key length must be between 8 and 10 in length so I am choosing 10 to allow for a wider ASCII range. In lines 14-17 I create a time_t object to help with seeding the random number generator, set the size of the character set that will be used for key generation, set the current character set value to be the lowest character value, and initialize the loop counter. In lines 19-22 I create arrays to hold the charset and the generated key and I seed the random number generator.
The first for loop loads all of the ASCII values of the characters in the charset into the charSet array. The second forloop generates the keys by taking a random character in the charSet array and storing in the currKey array. Once the key has been generated it is printed to the console.
How do we check our work? Lets write a test function to automate the running of the program that we just cracked with the output of the program that we just wrote.
void testValue(char* key, int keyLen)
{
int counter = 0;
int accumulator = 0;
int tempCounter = 0;
char buff[100] = "";
for(counter; counter < keyLen; counter++)
{
tempCounter = counter;
accumulator += (int)key[counter];
}
if(accumulator > 999)
{
//printf("%s\n", key);
strcat(buff, "echo \"");
strcat(buff, key);
strcat(buff, "\" | ./keygen.bin");
printf("\nCalling %s\n", buff);
system(buff);
}
}
I won’t go through the tester line by line but it first tests to see if the generated key should be accepted by the program based on the constraints. If the key passes then a linux command that amounts to echo [keyvalue] | ./keygen.bin
is generated and is executed by the call to system(). Here is the full code for convenience.
#include "stdio.h"
#include "stdlib.h"
#include "time.h"
#include "string.h"
void testValue(char* key, int keyLen)
{
int counter = 0;
int accumulator = 0;
int tempCounter = 0;
char buff[100] = "";
for(counter; counter < keyLen; counter++)
{
tempCounter = counter;
accumulator += (int)key[counter];
}
if(accumulator > 999)
{
//printf("%s\n", key);
strcat(buff, "echo \"");
strcat(buff, key);
strcat(buff, "\" | ./keygen.bin");
printf("\nCalling %s\n", buff);
system(buff);
}
}
void generateKeys()
{
int minASCIIValue = 97;
int maxASCIIValue = 127;
int keyLen = 10;
int numKeys = 100;
time_t t;
int charSetSize = maxASCIIValue - minASCIIValue;
int currASCIIValue = minASCIIValue;
int counter = 0;
char charSet[charSetSize];
char currKey[keyLen];
srand((unsigned) time(&t));
for(counter; counter < charSetSize; counter++)
{
charSet[counter] = currASCIIValue;
currASCIIValue++;
}
for(counter = 0; counter < numKeys; counter++)
{
for(int i = 0; i < keyLen; i++)
{
currKey[i] = charSet[rand() % charSetSize];
}
testValue(currKey, keyLen);
}
return;
}
int main()
{
generateKeys();
return 0;
}