Crackme: 45exile's Shuffle
Solving my first ever crackme Shuffle by 45exile
Reconnaissance
After downloading the crackme I quickly ran it.
❯ ./crackmeEnter the password please :rentriNo... Maybe another time !Let’s run strings on it.
❯ strings crackme..GommageEnter the password please :
Ygta_u3G_t0h_0aG_r3Good 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.
//----- (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:
v4 = 0i = 0ag = "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.
//----- (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:
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.
from ctypes import CDLLlibc = CDLL("libc.so.6")
user_input = input("Enter the input: ").strip("\r\n")len_user_input = len(user_input)
seed = 0i = 0ag = "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:
❯ python3 mutator.pyEnter the input: rentrirretinSo 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.
from ctypes import CDLLlibc = CDLL("libc.so.6")
user_input = input("Enter the input: ").strip("\r\n")len_user_input = len(user_input)
seed = 0i = 0ag = "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
❯ python3 keygen.pyEnter the input: rretinrentriWe 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
❯ python3 keygen.pyEnter the input: Ygta_u3G_t0h_0aG_r3GG_Y0u_ar3_th3_g0atLets test the string GG_Y0u_ar3_th3_g0at.
❯ ./crackmeEnter the password please :GG_Y0u_ar3_th3_g0atGood Job !And its done! The password is GG_Y0u_ar3_th3_g0at.
Footnotes
← Back to blog