# MCTF Final 2019 Writeup - Back to the Old Ways

• Tuesday, Jul 30, 2019

I played MCTF final 2019 with my team this week and wanted to share a writeup for the ‘Back to the old ways’ crypto challenge.

## Challenge description

There is a group of hackers that we’ve been trying to take down for a while, we had a spy among them but his cover was compromised, but he managed to leak a piece of code running on their main socket, try to get admin access.

nc 52.59.5.86 1111

## Solution

I first tried to use the public service

``````[youben@ym ~]\$ ncat 52.59.5.86 1111
[1] register
1
[+] name:
youben
[1] register
2
6625205b724457776f2e753f7c3463542a3f584254547d6a77493c0b09714c3a2761476358606a6e45653a5d
[-] access is for admins only
[1] register

It seems like our information are stored in that cookie and are checked when we try to login to see if we have admin rights. So the goal is to construct a valid admin cookie. Let’s check the provided code

``````#all what u need is here

from string import printable as alpha

def encrypt(msg,key,iv):
if len(msg) % 11 != 0:
msg += alpha[len(msg) % 11] * (11 - len(msg) % 11)
l = [msg[i:i+11] for i in range(0,len(msg),11)]
ct = []
for i in l:
tmp = viginereEncrypt(i,iv)
tmp = railFenceEncrypt(tmp,key)
ct.append(tmp)
iv = tmp
return ''.join(ct)

def decrypt(msg,key,iv):
l = [msg[i:i+11] for i in range(0,len(msg),11)]
pt = []
for i in l:
tmp = railFenceDecrypt(i,key)
tmp = viginereDecrypt(tmp,iv)
pt.append(tmp)
iv = i
return ''.join(pt)``````

The encryption can be illustrated in the figure below

It’s using vigenere and railfence in CBC mode, the keyspace for railfence here is computable (only 10 possible keys), so if we know the last plaintext then we can just try decrypting the last ciphertext with the possible keys until we found the correct one that decrypts to our know plaintext, thanks to the padding for that, if len(plaintext) % 11 == 1 then it will be padded with 10 ‘1’, however, we must first understand how it’s constructing the plaintext cookie.

Trying different inputs, I discovered that a name and password of length 6 + 11k gives us a padding of 10 ‘1’, so we can now decrypt the last block with the 10 possible keys and check until we get those 10 ‘1’ and decrypt the whole cookie except the first block cause we don’t have the IV.

Decrypting the cookie gave me ‘](yv\$zHX?B)&password=&admin=false&1111111111’, seems like we only need to replace that ‘false’ with a ‘true’ and reconstruct a valid cookie. You can check the complete code for doing that below

``````from string import printable as alpha
from binascii import unhexlify, hexlify

def encryptRailFence(text, key):
rail = [['\n' for i in range(len(text))]
for j in range(key)]
dir_down = False
row, col = 0, 0

for i in range(len(text)):
if (row == 0) or (row == key - 1):
dir_down = not dir_down

rail[row][col] = text[i]
col += 1
if dir_down:
row += 1
else:
row -= 1
result = []
for i in range(key):
for j in range(len(text)):
if rail[i][j] != '\n':
result.append(rail[i][j])
return("" . join(result))

def decryptRailFence(cipher, key):
rail = [['\n' for i in range(len(cipher))]
for j in range(key)]
dir_down = None
row, col = 0, 0

for i in range(len(cipher)):
if row == 0:
dir_down = True
if row == key - 1:
dir_down = False

rail[row][col] = '*'
col += 1

if dir_down:
row += 1
else:
row -= 1
index = 0
for i in range(key):
for j in range(len(cipher)):
if ((rail[i][j] == '*') and
(index < len(cipher))):
rail[i][j] = cipher[index]
index += 1
result = []
row, col = 0, 0
for i in range(len(cipher)):
if row == 0:
dir_down = True
if row == key-1:
dir_down = False
if (rail[row][col] != '*'):
result.append(rail[row][col])
col += 1
if dir_down:
row += 1
else:
row -= 1
return("".join(result))

def decryptVig(ciphertext, key):
key_length = len(key)
key_as_int = [alpha.find(k) for k in key]
ciphertext_int = [alpha.find(c) for c in ciphertext]
plaintext = ''
for i in range(len(ciphertext_int)):
p = (ciphertext_int[i] - key_as_int[i % key_length]) % len(alpha)
plaintext += alpha[p]
return plaintext

def encryptVig(plaintext, key):
key_length = len(key)
key_as_int = [alpha.find(k) for k in key]
plaintext_int = [alpha.find(p) for p in plaintext]
ciphertext = ''
for i in range(len(plaintext_int)):
c = (plaintext_int[i] + key_as_int[i % key_length]) % len(alpha)
ciphertext += alpha[c]
return ciphertext

def decrypt(msg,key,iv):
l = [msg[i:i+11] for i in range(0,len(msg),11)]
pt = []
for i in l:
tmp = decryptRailFence(i,key)
tmp = decryptVig(tmp,iv)
pt.append(tmp)
iv = i
return ''.join(pt)

if __name__ == '__main__':
# name: 'aaaaaa'
expected_chars = '1' * 10
ciphers = [cipher[i:i+11] for i in range(0, len(cipher), 11)]
last_cipher = ciphers[-1]
rf_key = -1
for i in range(2, 11):
p = decryptRailFence(last_cipher, i)
p = decryptVig(p, ciphers[-2])
if expected_chars in p:
print("Found RF key: %d" % i)
rf_key = i
print(decrypt(cipher, rf_key, "0"))
cons = encryptRailFence(cons, rf_key)
ciphers[-2] = cons
print(hexlify(''.join(ciphers[:-1]).encode()))``````

Finally, I only got a new cookie by registering, constructed a valid admin cookie from it, used it to login and get the flag :)

``````[youben@ym ~]\$ ncat 52.59.5.86 1111
[1] register
1
[+] name:
aaaaaa

[1] register