Crackme: 45exile's Shuffle Crackme: 45exile's Shuffle

Crackme: 45exile's Shuffle

Solving my first ever crackme Shuffle by 45exile

Reconnaissance

After downloading the crackme I quickly ran it.

Terminal window
./crackme
Enter the password please :
rentri
No... Maybe another time !

Let’s run strings on it.

Terminal window
strings crackme
.
.
Gommage
Enter the password please :
Ygta_u3G_t0h_0aG_r3
Good Job !
No... Maybe another time !
.
.

These strings look interesting, we can clearly see some strings used in the password logic.

However both Gommage and Ygta_u3G_t0h_0aG_r3 are not the password.

The next step is to open the binary in an online available decompiler explorer like dogbolt.

Using the Hex-Rays decompiler I got the pseudo code and located the main function.

Main Pseudocode
//----- (00000000000012E4) ----------------------------------------------------
__int64 __fastcall main(int a1, char **a2, char **a3)
{
unsigned int v4; // [rsp+4h] [rbp-23Ch]
int i; // [rsp+8h] [rbp-238h]
int v6; // [rsp+Ch] [rbp-234h]
char s[256]; // [rsp+20h] [rbp-220h] BYREF
char dest[264]; // [rsp+120h] [rbp-120h] BYREF
unsigned __int64 v9; // [rsp+228h] [rbp-18h]
v9 = __readfsqword(0x28u);
v4 = 0;
puts("Enter the password please :");
if ( !fgets(s, 256, stdin) )
return 1;
s[strcspn(s, "\r\n")] = 0;
strncpy(dest, s, 0xFFu);
dest[255] = 0;
for ( i = 0; i < strlen("Gommage"); ++i )
v4 = 31 * v4 + aGommage[i];
v6 = strlen(dest);
sub_1249((__int64)dest, v6, v4);
if ( !strcmp("Ygta_u3G_t0h_0aG_r3", dest) )
puts("Good Job !");
else
puts("No... Maybe another time !");
return 0;
}

Analysing main

We can see the declaration of variable s of type char of size 256 characters or 2048 bits as 1 character = 1 byte = 8 bits.
Same with dest.

fgets(s, 256, stdin) here fgets attempts to write 256 char to s from stdin.

We can infer that s is user input.
More on fgets.

s[strcspn(s, "\r\n")] = 0; here strcspn gives the index of \r\n1.

Thus we have s[index] = '\0'; where \02 is a null terminator, we are setting the character at given index to a null terminator.

strncpy(dest, s, 0xFFu); copies up to 255 characters from s to dest, 0xFFu is hexadecimal for 255. More on strncpy.

dest[255] = 0; is setting the last character in dest to a null terminator.

Now comes the for loop. The loop starts from i=0 and goes till i=6 as length of “Gommage” is 7.

The program had previously declared a variable v4 with value 0. Inside the loop v4 will have the value of whatever 31 * v4 + aGommage[i]; gives when the loop finishes.

aGommage[i] is quite confusing, it most probably is the ordinal value of characters in string “Gommage”.

Next we have v6 = strlen(dest);, which basically means that v6 is an int having value equal to the length of dest which is a cleaned user input.

Just right below we can see a mysterious function sub_1249 being called which is taking (__int64)dest, v6, v4 as parameters.

Let’s first calculate the value of v4 before exploring the mysterious function.

The equivalent of the for loop in main in python would look like:

keygen.py
v4 = 0
i = 0
ag = "Gommage"
for i in range(len(ag)):
v4 = 31 * v4 + ord(ag[i])

The above code gives the final value of v4 to be 66294604631.

We don’t know what this value is being used for yet, for that we need to analyse the mysterious function.

Analysing sub_1249

The sub_1249 function is right above our main funtion in the decompiler. Lets take a look.

sub_1249 Pseudocode
//----- (0000000000001249) ----------------------------------------------------
void __fastcall sub_1249(__int64 a1, int a2, unsigned int a3)
{
int v3; // eax
char v4; // [rsp+17h] [rbp-9h]
int i; // [rsp+18h] [rbp-8h]
if ( a1 && a2 > 0 )
{
srand(a3);
for ( i = a2 - 1; i > 0; --i )
{
v3 = rand();
v4 = *(_BYTE *)(i + a1);
*(_BYTE *)(i + a1) = *(_BYTE *)(v3 % (i + 1) + a1);
*(_BYTE *)(a1 + v3 % (i + 1)) = v4;
}
}
}

As we previously saw in main, the sub_1249 function takes 3 parameters, (__int64)dest, v6 and v4.

From this we can infer that a1 is (__int64)dest, a2 is v6 and a3 is v4.

(__int64)dest is a 64-bit pointer to a buffer. dest now a1 is being used as a pointer.

srand(a3) is interesting. From this we finally know what the value of v4 now a3 is a seed for rand().
More on srand

The for loop is starting from the length of user input a1, and decrements till i=1.

In the loop we first generate a random integer and assign its value to v3.

Now in sub_1249, v4 is above declared as of type char and it takes the value of *(_BYTE *)(i + a1); which basically means v4 = a1[i].

As a1 is user input, v4 = *(_BYTE *)(i + a1);3 is equivalent to v4 = a1[i].

The next line basically is a1[i] = a1[v3 % (i+1)], here v3 is a random number given by rand().

The value of v3 % (i+1) is in range of 0 <= v3 % (i+1) <= i.

Next line basically is a1[v3 % (i+1)] which is equal to v4.

This means that a1[v3 % (i+1)] = v4, which tells us that v4 is just a temp placeholder to save value of a1[i] at the start.

As we know, a1 is being used as a pointer to a buffer, by the end of the sub_1249 function, whatever value that buffer previously held would be mutated.

As in the above examples input is dest or a1, therefore the function sub_1249 mutates the value of user input.

Back to main

The final piece of the puzzle is:

Main Pseudocode
if ( !strcmp("Ygta_u3G_t0h_0aG_r3", dest) )
puts("Good Job !");
else
puts("No... Maybe another time !");
return 0;

The function strcmp compares Ygta_u3G_t0h_0aG_r3 with now mutated dest

So the password is such a string which when mutated by the funtion sub_1249 turns into Ygta_u3G_t0h_0aG_r3

Writing Keygen

Its time to write the keygen, we have to reverse the mutation done by sub_1249.

We already have the script to generate the seed, but the issue is that the rand function is in C and will give different randomness compared to python.

We need to look for a C equivalent in python4. Which is the ctypes module.

Lets try to emulate the sub_1249 function in python.

mutator.py
from ctypes import CDLL
libc = CDLL("libc.so.6")
user_input = input("Enter the input: ").strip("\r\n")
len_user_input = len(user_input)
seed = 0
i = 0
ag = "Gommage"
for i in range(len(ag)):
seed = 31 * seed + ord(ag[i])
libc.srand(seed)
user_input = (list(user_input))
for i in range(len_user_input-1, 0, -1):
rand = libc.rand()
temp = user_input[i]
x = rand % (i+1)
user_input[i] = user_input[x]
user_input[x] = temp
output = ''.join(user_input)
print(output)

Running the mutator with input rentri gives:

Terminal window
python3 mutator.py
Enter the input: rentri
rretin

So our keygen needs to be such that when we give input rretin we get output rentri.

One way to do that is to record each character swap in a set, this swap when done mutates the string so to unmutate the string we just need to peform the swap in reverse.

keygen.py
from ctypes import CDLL
libc = CDLL("libc.so.6")
user_input = input("Enter the input: ").strip("\r\n")
len_user_input = len(user_input)
seed = 0
i = 0
ag = "Gommage"
for i in range(len(ag)):
seed = 31 * seed + ord(ag[i])
libc.srand(seed)
user_input = (list(user_input))
set_of_swaps = []
for i in range(len_user_input-1, 0, -1):
rand = libc.rand()
x = rand % (i+1)
set_of_swaps.append((i, x))
for i, x in reversed(set_of_swaps):
temp = user_input[i]
user_input[i] = user_input[x]
user_input[x] = temp
output = ''.join(user_input)
print(output)

When we give the keygen the input rretin we expect an output of rentri

Terminal window
python3 keygen.py
Enter the input: rretin
rentri

We do indeed get the expected output rentri.

Final Solution

With this our keygen is done. Now all that’s left is to test it on Ygta_u3G_t0h_0aG_r3

Terminal window
python3 keygen.py
Enter the input: Ygta_u3G_t0h_0aG_r3
GG_Y0u_ar3_th3_g0at

Lets test the string GG_Y0u_ar3_th3_g0at.

Terminal window
./crackme
Enter the password please :
GG_Y0u_ar3_th3_g0at
Good Job !

And its done! The password is GG_Y0u_ar3_th3_g0at.

Footnotes

  1. StackOverflow: What is the difference between \r\n, \r, and \n?

  2. StackOverflow: What is a null-terminated string?

  3. StackOverflow: Reverse engineering ambiguous syntax

  4. StackOverflow: Does Python have a function to mimic the sequence of C’s rand()?


← Back to blog