Matrix of Hell, 992 pt
This one is a classic crackme: run the binary, it asks for a password, and getting the right password prints the flag. I opened it in IDA and renamed the variables to make the decompiler output easier to follow.
The main function breaks down into three steps.
Step 1: Building the reference table
First the binary fills a buffer, which I renamed pass_cmp, by walking two nested loops and writing letters into it.
v14 = 0;
for ( i = 0; i <= 4; ++i )
{
for ( j = 0; j <= 4; ++j )
{
if ( v14 == 9 )
{
v14 = 10;
--j;
}
else
{
a2 = (char **)j;
a3 = (char **)(4 * (j + 6LL * i));
*(_DWORD *)((char *)pass_cmp + (_QWORD)a3) = v14++ + 65;
}
}
}
Reimplementing the same loop in Python gives us the table it’s comparing against:
s = ['?']*100
v3=0
v4=0
for i in range(5):
for j in range(5):
if v14==9:
v14=10
j = j-1
else:
a3= (j+6*i)
s[a3]=chr(v14+65)
v14 += 1
print(''.join(s))
output--> ABCDE?FGHI??LMNOP?QRSTU?VWXYZ???? <-- pass_cmp
So pass_cmp ends up as ABCDE?FGHI??LMNOP?QRSTU?VWXYZ????, basically the alphabet laid out with a few gaps.
Step 2: The matrix check
Next the binary reads the input, requires it to be exactly 14 characters, and then walks it byte by byte. For each input byte it searches the table as if it were a 5x5 matrix, records the matching (row, col) coordinates as characters, XORs the result, and compares it against a fixed string s1 from the data section.
printf("PASSWORD:", a2, a3);
gets(password);
if ( strlen(password) != 14 || (sub_83A(), !v3) ) <-- check password length
{
printf("ACCESS DENIED");
exit(0);
}
v16 = 0;
for ( k = 0; k < strlen(password); ++k ) <-- iterate password byte by byte
{
for ( l = 0; l <= 4; ++l )
{
for ( m = 0; m <= 4; ++m )
{
if ( pass_cmp[m + 6LL * l] == password[k] ) <-- Compare password byte at k index
with above output.
{
new_string[v16] = l + 65; <-- form new string
v4 = v16 + 1;
new_string[v4] = m + 49; <-- The length of new string is double
of password.
v16 = v4 + 1;
}
}
}
}
for ( n = 0; n < strlen(new_string); ++n )
s2[n] = n % 4 ^ new_string[n]; <-- doing some xor operation and form new
string s2
if ( strcmp(s1, s2) ) <-- cmp s2 with s1 and s1 is in data
section
{ <-- s1 = 'B0C2A2C6A3A7C5@6B5F0A4G2B5A2'
printf("ACCESS DENIED", s2);
exit(0);
}
So s2 is built from the coordinates via XOR and checked against s1. To go backwards, I first undo the XOR on s1 to recover the string the coordinates encode:
s1 = "B0C2A2C6A3A7C5@6B5F0A4G2B5A2" <-- s1
new_string = ""
for i in range(28):
new_string += chr(i%4^ord(s1[i]))
print(f)
output--> 'B1A1A3A5A2C4C4B5B4D3A5E1B4C1' <-- new_string
From that string I can read off the (l, m) coordinates that satisfy the check if ( pass_cmp[m + 6LL * l] == password[k] ):
cord = []
for i in range(0,28,2):
l,m=0,0
l = ord(f[i])-ord('A')
m = ord(f[i+1])-ord('1')
cord.append((l,m))
print(cord),
output--> [(1, 0), (0, 0), (0, 2), (0, 4), (0, 1), (2, 3), (2, 3), (1, 4), (1, 3), (3, 2), (0, 4), (4, 0), (1, 3), (2, 0)]
With the coordinates in hand, indexing back into pass_cmp recovers the password:
cord = [(1, 0), (0, 0), (0, 2), (0, 4), (0, 1), (2, 3), (2, 3), (1, 4), (1, 3), (3, 2), (0, 4), (4, 0), (1, 3), (2, 0)]
pass_cmp = 'ABCDE?FGHI??LMNOP?QRSTU?VWXYZ????'
password = ""
for i in range(len(cord)):
password += pass_cmp[cord[i][1]+6*cord[i][0]]
print(password)
output --> 'FACEBOO?ISEVIL' <-- one byte is missing '?' we know what it is :)
show password is --> 'FACEBOOKISEVIL'
Step 3: The flag
One byte lands on a ? gap in the table, but the surrounding text makes it obvious: the password is FACEBOOKISEVIL. Feed that in and the binary spits out the flag:
flag : 1337_FD_DDLLLKMO_KUWRRRVL_HAHAHA