This is a beginner-friendly writeup for "A Simple SP Box!", a challenge from the recently concluded dCTF. It's under the Crypto category and is worth 300 points which puts it around the Medium difficulty. Before I started working on this challenge I did a quick search for what SP cryptography is. Google says that it stands for "Substitution Permutation" and is applied by certain block ciphers like AES. We will see how this applies to our challenge later.
It's just a simple SP-box, 150 tries should be enough for you.
If you are simply here for the code required to solve this problem, then look no further:
#!/usr/bin python3
from string import ascii_letters, digits
import socket
host = 'dctf1-chall-sp-box.westeurope.azurecontainer.io'
port = 8888
alphabet = ascii_letters + digits + "_!@#$%.'\\"+:;<=}{"
shuffled = []
s = socket.socket()
s.connect((host,port))
data = s.recv(2048).decode('utf-8')
print(data)
encryptedflag = data.split('\\n')[1]
for var in alphabet:
character = var*36
character += '\\n'
bytesobj = bytes(character, 'utf-8')
s.send(bytesobj)
data = s.recv(2048).decode('utf-8')
print(data)
encrypted = data.split('\\n')[1]
shuffled.append(encrypted[0])
substitution = {k : v for k, v in zip(shuffled, alphabet)}
decryptedshuffled = [substitution[c] for c in encryptedflag]
counter = 0;
index = 0;
decryptedflag = ''
while 1:
if (index >= 42):
counter += 1
index = 0
continue
if (counter < 7):
counter += 1
index += 1
continue
currentchar = decryptedshuffled[index]
decryptedflag += currentchar
if (currentchar == '}'):
break
index += 1
counter = 0
print(decryptedflag)
s.close()
Upon connecting to the host, we are greeted with a random string of letters and a message:
Seems like the flag is encrypted and we need to decrypt it. If we try to enter any other input besides the decrypted flag, we get the following message:
The server actually tells us what our input would look like if it was encrypted with the same algorithm that it used to encrypt the flag. We will use this fact later in our solution. For now let's talk about the other half of the problem.
This part discusses the source code and how it works. If you already understand the code or simply want to see my thought process, proceed to the next section.
We are given the code which contains the exact algorithm used to encrypt the flag.
from string import ascii_letters, digits
from random import SystemRandom
from math import ceil, log
from signal import signal, alarm, SIGALRM
from secret import flag
random = SystemRandom()
ALPHABET = ascii_letters + digits + "_!@#$%.'\\"+:;<=}{"
shuffled = list(ALPHABET)
random.shuffle(shuffled)
S_box = {k : v for k, v in zip(ALPHABET, shuffled)}
def encrypt(message):
if len(message) % 2:
message += "_"
message = list(message)
rounds = int(2 * ceil(log(len(message), 2)))
for round in range(rounds):
message = [S_box[c] for c in message]
if round < (rounds-1):
message = [message[i] for i in range(len(message)) if i%2 == 1] + [message[i] for i in range(len(message)) if i%2 == 0]
return ''.join(message)
def play():
print("Here's the flag, please decrypt it for me:")
print(encrypt(flag)) # 1
for _ in range(150): # 2
guess = input("> ").strip()
assert 0 < len(guess) <= 10000
if guess == flag: # 3
print("Well done. The flag is:")
print(flag)
break
else:
print("That doesn't look right, it encrypts to this:")
print(encrypt(guess)) # 4
def timeout(a, b):
print("\\nOut of time. Exiting...")
exit()
signal(SIGALRM, timeout) # 5
alarm(5 * 60)
play()
A high-level breakdown of the code shows that: