Moritz Finke · Blog

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.

Description of the 'delicious soup' challenge
Content of 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].