Python Reversing
Author: GabiTulbaContest: TJCTF 2018
Problem statement:
Found this flag checking file and it is quite vulnerable
Source
My opinion:
I still don’t understand why this problem was tagged as Reverse Engineering
, it was a simple cryptography problem with a classic attack, in my opinion it was way easier than Caesar’s Complication, the number of solves talk for themselves.
Understanding the encryption
We were given the following code:
import numpy as np
flag = 'redacted'
np.random.seed(12345)
arr = np.array([ord(c) for c in flag])
other = np.random.randint(1,5,(len(flag)))
arr = np.multiply(arr,other)
b = [x for x in arr]
lmao = [ord(x) for x in ''.join(['ligma_sugma_sugondese_'*5])]
c = [b[i]^lmao[i] for i,j in enumerate(b)]
print(''.join(bin(x)[2:].zfill(8) for x in c))
# original_output was 1001100001011110110100001100001010000011110101001100100011101111110100011111010101010000000110000011101101110000101111101010111011100101000011011010110010100001100010001010101001100001110110100110011101
Ok, let’s see what’s happening step by step:
- The
flag
is transformed into an array calledarr
. - Then another array
other
of the same length asarr
with random values from 1 to 4 is generated using a constant seed (clearly a bad idea since now the encryption is deterministic). - The array
arr
is multiplied with the arrayother
byarr[i]*=other[i]
. - Then the array
b=arr
is xored element by element with the arraylmao
. - The encrypted flag is the concatenation of the binary representation of every value in array
c=b
.
One thing that makes this problem a little harder (the encryption isn’t easily reversable) is that when the array arr
is multiplied with the array other
, in some cases the result is bigger than 256. I saw that when I saw that the length of the output wasn’t divisible by 8:
>>> <br>
x='1001100001011110110100001100001010000011110101001100100011101111110100011111010101010000000110000011101101110000101111101010111011100101000011011010110010100001100010001010101001100001110110100110011101'
>>> len(x)%8
2
But still, since we know every variable (including other
) we can ecnrypt, so we can guess the flag character by character by encrypting every possible byte and checking if it matches the desired output, this is known as a Chosen-plaintext attack.
Decrypting the flag:
Here’s the code:
import numpy as np
enc='1001100001011110110100001100001010000011110101001100100011101111110100011111010101010000000110000011101101110000101111101010111011100101000011011010110010100001100010001010101001100001110110100110011101'
flag= ''
def encrypt(flag):
np.random.seed(12345)
arr = np.array([ord(c) for c in flag])
other = np.random.randint(1,5,len(arr))
arr = np.multiply(arr,other)
b = [x for x in arr]
lmao = [ord(x) for x in ''.join(['ligma_sugma_sugondese_'*5])]
c = [b[i]^lmao[i] for i,j in enumerate(b)]
y=(''.join(bin(x)[2:].zfill(8) for x in c))
p=0
while(p<len(y) and enc[p]==y[p]):
p+=1
return p
L=0
while(L<len(enc)):
for j in range(256):
if(L<=encrypt(flag+chr(j))-8): #at least 8 new bits have to mach the output
flag+=chr(j)
L=encrypt(flag)
print L
break
print flag
NOTE : It just happened for that my solution worked, since there’s not necesarily a one to one mapping to plaintext-ciphertext. Flag: tjctf{pYth0n_1s_tr1v14l}