A Delicious Soup
April 25, 2019
This challenge is part of the Cyber Security Contest qualifications by ASIS. It is a warmup challenge about the weaknesses of security by obscurity in proprietary encryption algorithms.
The challenge provides two files: flag.enc and simple_and_delicious.py. The former contains a permutation of the flag string that was encrypted by the simple_and_delicious script.
simple_and_delicious.py
:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import random
from flag import flag
def encrypt(msg, perm):
W = len(perm)
while len(msg) % (2*W):
msg += "."
msg = msg[1:] + msg[:1]
msg = msg[0::2] + msg[1::2]
msg = msg[1:] + msg[:1]
res = ""
for j in xrange(0, len(msg), W):
for k in xrange(W):
res += msg[j:j+W][perm[k]]
msg = res
return msg
def encord(msg, perm, l):
for _ in xrange(l):
msg = encrypt(msg, perm)
return msg
W, l = 7, random.randint(0, 1337)
perm = range(W)
random.shuffle(perm)
enc = encord(flag, perm, l)
f = open('flag.enc', 'w')
f.write(enc)
f.close()
Investigating the encryption algorithm
The 'key' used for encryption is a tuple of the number of iterations (0 - 1337) used for running encrypt and the permutation list (length=7) used for shuffling the flag's characters.
Because of the low number of iterations and possible permutations, the used values can be brute forced (6738480 possibilities). The following function runs such brute force attack. All that is left to do is write a corresponding decrypt function.
import itertools
def decord(msg):
for k in list(itertools.permutations([0,1,2,3,4,5,6])):
k = list(k)
msg_ = msg
for i in range(1338):
msg_ = decrypt(msg_, k)
if msg_.startswith("ASIS"):
return i, msg_, k
Mirroring the encryption function
The 'encryption algorithm' in use is symmetric and can be easily reversed. All that has to be done is to reverse the order of execution of the single program lines inside the encryption function.
Instead of running this deterministic shuffle algorithm
def encrypt(msg, perm):
#...#
msg = msg[1:] + msg[:1] # [1,2,3,4] -> [2,3,4,1]
msg = msg[0::2] + msg[1::2] # [2,3,4,1] -> [2,4,3,1]
msg = msg[1:] + msg[:1] # [2,4,3,1] -> [4,3,1,2]
before the permutation algorithm,
res = ""
for j in xrange(0, len(msg), W):
for k in xrange(W):
res += msg[j:j+W][perm[k]]
msg = res
#...#
the shuffle algorithm needs to be run at last. Additionally, some minor adjustments need to be applied so that the algorithm really runs backwards:
def decrypt(msg, perm):
#...#
msg = msg[-1] + msg[:-1] # [4,3,1,2] -> [2,4,3,1]
msg = "".join(i for j in zip(msg[:len(msg)//2], \
msg[len(msg)//2 if len(msg)%2==0 else ((len(msg)//2)+1):]) \
for i in j) # [2,4,3,1] -> [2,3,4,1]
msg = msg[-1] + msg[:-1] # [2,3,4,1] -> [1,2,3,4]
return msg
The complete brute force algorithm (brute_force.py
) looks like this:
import itertools
def decrypt(msg, perm):
W = len(perm)
res = ""
for j in xrange(0, len(msg), W):
for k in xrange(W):
res += msg[j:j+W][perm[k]]
msg = res
msg = msg[-1] + msg[:-1] # [4,3,1,2] -> [2,4,3,1]
msg = "".join(i for j in zip(msg[:len(msg)//2], \
msg[len(msg)//2 if len(msg)%2==0 else ((len(msg)//2)+1):]) \
for i in j)
# [2,4,3,1] -> [2,3,4,1]
msg = msg[-1] + msg[:-1] # [2,3,4,1] -> [1,2,3,4]
return msg
def decord(msg):
for k in list(itertools.permutations([0,1,2,3,4,5,6])):
k = list(k)
msg_ = msg
for i in range(1338):
msg_ = decrypt(msg_, k)
if msg_.startswith("ASIS"):
return i, msg_, k
f = open('flag.enc', 'r')
enc = f.read()
f.close()
print "Encrypted flag: {}".format(enc)
iteration, flag, permutation = decord(enc)
print "Decrypted flag: {}".format(flag)
print "Decrypted with l={} and perm={}".format(iteration, permutation)
With the help of the brute force script, the flag was obtained:
ASIS{2n54n3ly_Simpl3_And_d3lic1Ous_5n4ckS_eVEn_l4zY_Pe0pL3_Can_Mak3}
The number of iterations used for encryption is 760 and the permutation in use is [5,6,2,0,3,4,1].