RACTF 2020 Crypto - B007L36 CRYP70 A641N


B007L36 CRYP70… 4641N

After finally solving B007l3G CRYP70, we are again presented with another variant. The description reads as follows:-

As you continue your pentest of MEGACORP, you make your way to an 
admin-only subnet of the network. There, you find yet another custom
crypto implementation. You also previously found this zip file on a 
user's desktop. Solving this may be the last step to gaining full 
access to the company's network

And encryption_service.zip, the contents of which are :-




To test the encryption service, encrypt this file with your company 
issued secret key and ensure that it results in the ciphertext.txt file.

Aand a password.txt


Lets first begin with by checking some stuff like we did in B007l3G CRYP70 with the encryption service

Please enter the secret key to encrypt the data with: a
Please enter the data that you would like to encrypt: a
Your encrypted message is: w4I=

Please enter the secret key to encrypt the data with: b
Please enter the data that you would like to encrypt: a
Your encrypted message is: w4M=

Notice the encrypted message appears to be base64 encoded.
Lets check the decoded values

from base64 import b64decode as d64
# b'\xc3\x83'
# b'\xc3\x82'

EWW, looks odd probably because of unicode. Lets deal with string-objects instead of byte-objects by simply calling decode()

from base64 import b64decode as d64
# 'Ã'
# 'Â'
# 195
# 194

Hehe, we start getting a hint already, changing the key by 1, encryption changes by 1. There is probably something Very Linear about the encryption.

To help with encryption process, I created a small helper function encryptwhich calls encrypt on the server and return the ord values of base64 decoded string.

from pwn import remote
from base64 import b64decode as d64
from base64 import b64encode as e64

HOST, PORT = "", 60246

with open('password.txt', 'r') as password_file:
    password = d64(password_file.read().strip()).decode()

with open('ciphertext.txt', 'r') as ciphertext_file:
    ct = d64(ciphertext_file.read().strip()).decode()

with open('plaintext.txt', 'r') as plaintext_file:
    pt = plaintext_file.read().strip()

REM = remote(HOST, PORT)

def encrypt(pt, key):
    REM.recvuntil(b'data with:')
    data = REM.recvuntil(b'\n\n')
    encrypted = data.split(b'message is: ')[-1].strip()
    return [ord(i) for i in d64(encrypted).decode()]

Lets play around a little bit more…

>>> encrypt(b'aaaa','a')
[194, 194, 194, 194]
>>> encrypt(b'aaaa','ab')
[194, 195, 194, 195]
>>> encrypt(b'aaaa','abcd')
[194, 195, 196, 197]
>>> encrypt(b'abcd','aaaa')
[194, 195, 196, 197]

We can make the following observations :-

  • key is repeated if plaintext is longer than the key
  • The encryption is changed only by 1 on varying either key or plaintext by 1
  • The key, plaintext and ciphertext are related just by an addition modulo 256

So, all we need to do now is to figure out what the provided files mean.
Let us try checking the key with which ciphertext.txt and plaintext.txt are related.

ct_arr = [ord(i) for i in ct]
pt_arr = [ord(i) for i in pt]
diff_arr = [ct_arr[i] - pt_arr[i] for i in range(len(ct_arr))]
print("".join(chr(i) for i in diff_arr))


YES, it indeed verifies all our hypothesis and we now know the company issued secret key is ractf{n0t_th3_fl49_y3t}

Lets quickly decrypt the contents of password.txt with the key

key = "ractf{n0t_th3_fl49_y3t}"
flag = "".join( chr(ord(password[i]) - ord(key[i%len(key)])) for i in range(len(password)) )

And we finally get our flag ractf{f00l_m3_7w1c3_5h4m3_0n_m3}