@Raaquilla
Crack Me V2
0
0m read
Challenge description
EvilCorp has upgraded their license validator! It’s now AI powered quantum resistant dark blockchain infused!
Approach
Decompiling the binary gives us a few key pieces of information.
- An array of bytes that is likely the encrypted flag.
_BYTE byte_2500[66] = {
84, 107, 83, 119, 11, 30,
-126, -45, 19, -29, 90, -95,
-4, -52, 3, 40, -24, 109, 1,
-66, 72, 101, -30, -66, 87,
-96, -120, -2, 116, 48, -124,
-119, 5, -8, -41, -6, -20, 2,
120, -98, -2, 31, -115, -124,
-76, -3, -110, 92, 45, -58,
-126, -89, 106, -22, -18, 77,
-68, 118, -51, -43, -89, 35,
-43, 34, 107, 69
};
- The main function.
__int64 __fastcall main(int a1, char **a2, char **a3) {
char s[136];
unsigned __int64 v5;
v5 = __readfsqword(0x28u);
setvbuf(stdout, 0, 2, 0);
setvbuf(stderr, 0, 2, 0);
printf("serial: ");
if (!fgets(s, 128, stdin))
return 0;
s[strcspn(s, "\n")] = 0;
if (sub_13D2(s, 66)) {
if (sub_1526(s))
puts("you got it!");
else
puts("nope");
return 0;
} else {
puts("nope");
return 0;
}
}
- The password checks.
_BOOL8 __fastcall sub_1526(const char *a1)
{
if ( strlen(a1) != 66 )
return 0;
if ( sub_1457((__int64)a1) )
return (unsigned int)sub_13FE((__int64)a1) == 1301556880;
return 0;
}
_BOOL8 __fastcall sub_1457(__int64 a1)
{
char v2;
unsigned __int8 v3;
unsigned __int64 i;
v2 = 90;
for ( i = 0; i <= 0x41; ++i )
{
v3 = sub_13B8((unsigned __int8)(29 * i + *(_BYTE *)(a1 + i)) ^ (unsigned __int8)(7 * v2 + 61));
if ( (unsigned __int8)sub_1383(v3, (i + 3) & 7) != byte_2500[i] )
return 0;
v2 = *(_BYTE *)(a1 + i);
}
return *(_BYTE *)(a1 + 66) == 0;
}
__int64 __fastcall sub_1383(unsigned __int8 a1, char a2)
{
return (a1 << (a2 & 7)) | (unsigned int)((int)a1 >> (8 - (a2 & 7)));
}
__int64 __fastcall sub_13B8(unsigned __int8 a1)
{
return -59 * (unsigned int)a1 + 101;
}
_BOOL8 __fastcall sub_13D2(const char *a1, __int64 a2)
{
return a2 == strlen(a1);
}
__int64 __fastcall sub_13FE(__int64 a1)
{
unsigned int v2;
__int64 i;
v2 = -2128831035;
for ( i = 0; *(_BYTE *)(a1 + i); ++i )
v2 = 16777619 * (*(unsigned __int8 *)(a1 + i) ^ v2);
return v2;
}
Lets clean up the logic. Here is a not quite equivelant (due to unsigned ints), but more understandable version.
bool sub_1457(int *a1) {
int v3;
char v2 = 90;
for (int i = 0; i <= 65; ++i) {
v3 = sub_13B8(29 * i + a1[i]) ^ (7 * v2 + 61);
if (sub_1383(v3, (i + 3) & 7) != byte_2500[i])
return 0;
v2 = a1[i];
}
return a1[66] == 0;
}
int sub_1383(int a1, char a2) { // rol8 from Crack Me V1
return (a1 << (a2 & 7)) | (a1 >> (8 - (a2 & 7)));
}
int sub_13B8(int a1) { return -59 * a1 + 101; }
int sub_13FE(int a1) {
int v2 = -2128831035;
for (int i = 0; a1[i]; ++i)
v2 = 16777619 * a1[i] ^ v2);
return v2;
}
int main() {
char s[136]; // our input
if (strlen(s) == 66 && sub_1457(a1) && sub_13FE(a1) == 1301556880)
puts("you got it!");
else
puts("nope");
}
We can reverse the logic in python to get the flag.
Python implementation
flag_bytes = [
84, 107, 83, 119, 11, 30, 130,
211, 19, 227, 90, 161, 252, 204,
3, 40, 232, 109, 1, 190, 72,
101, 226, 190, 87, 160, 136, 254,
116, 48, 132, 137, 5, 248, 215,
250, 236, 2, 120, 158, 254, 31,
141, 132, 180, 253, 146, 92, 45,
198, 130, 167, 106, 234, 238, 77,
188, 118, 205, 213, 167, 35, 213,
34, 107, 69
]
def sub_1383_inv(a1, a2):
# (a1 << (a2 & 7)) | (a1 >> (8 - (a2 & 7)))
return ((a1 >> a2) | (a1 << (8 - a2))) & 0xFF
def sub_13B8_inv(y):
for x in range(256):
# sub_13B8
if (-59 * x + 101) & 0xFF == y:
return x
flag = []
v2 = 90
# sub_1457
for i in range(66):
# sub_1383(v3, (i + 3) & 7) != byte_2500[i])
b = sub_1383_inv(flag_bytes[i], (i + 3) & 7)
# sub_13B8(...) ^ ...;
x = sub_13B8_inv(b)
# ... ^ (7 * v2 + 61);
input_byte = x ^ ((7 * v2 + 61) & 0xFF)
# sub_13B8(29 * i + a1[i]) ^ ...;
input_byte = (input_byte - 29 * i) & 0xFF
flag.append(input_byte)
v2 = input_byte
print(bytes(flag).decode("ascii", errors="ignore"))
Running the script gives us our flag:
RISC{nextgen_totally_unbreakable_7a3211c840e8d7a4e9bd76f5539bd29d} index