/ crypto

abctf - Encryption Service

Enoncé :
See if you can break this!!
You can connect with nc 107.170.122.6 7765 and the source can be found here.

Solution:
On commence par lancer la commande netcat:

$ nc 107.170.122.6 7765
Send me some hex-encoded data to encrypt:

On récupére le code source du serveur pour l'analyser et voir ce qu'il attend:

#/usr/bin/env python
from Crypto.Cipher.AES import AESCipher

import SocketServer,threading,os,time
import signal

from secret2 import FLAG, KEY

PORT = 7765

def pad(s):
  l = len(s)
  needed = 16 - (l % 16)
  return s + (chr(needed) * needed)

def encrypt(s):
  return AESCipher(KEY).encrypt(pad('ENCRYPT:' + s.decode('hex') + FLAG))

class incoming(SocketServer.BaseRequestHandler):
    def handle(self):
        atfork()
        req = self.request

        def recvline():
            buf = ""
            while not buf.endswith("\n"):
                buf += req.recv(1)
            return buf
        signal.alarm(5)

        req.sendall("Send me some hex-encoded data to encrypt:\n")
        data = recvline()
        req.sendall("Here you go:")
        req.sendall(encrypt(data).encode('hex') + '\n')
        req.close()

class ReusableTCPServer(SocketServer.ForkingMixIn, SocketServer.TCPServer):
  pass

SocketServer.TCPServer.allow_reuse_address = True
server = ReusableTCPServer(("0.0.0.0", PORT), incoming)

print "Server listening on port %d" % PORT
server.serve_forever()

La partie qui nous intéresse :

AESCipher(KEY).encrypt(pad('ENCRYPT:' + s.decode('hex') + FLAG))

On sait donc que nous devons envoyer une chaine hexa qui est décodée puis concaténée avec 'ENCRYPT:' et le FLAG avant d'être chiffrée avec AES.
Regardons le format du chiffré:

$ nc 107.170.122.6 7765
Send me some hex-encoded data to encrypt:
4141414141414141
Here you go:cd7985389a47184ce3f957b15a1c45f3248ebbc75342e2750717224bf4faac93d342aade957372e5683ddfdf760c850c

En envoyant plusieurs fois le même hexa on remarque que le chiffré est toujours le même, on en déduit que le chiffrement est de l'aes-ecb.

Quelques jours avant le début du CTF je suis tombé sur l'article suivant :
https://c0nradsc0rner.wordpress.com/2016/07/03/ecb-byte-at-a-time/
L'attaque qui y est décrite semble bien être celle à employer pour résoudre ce challenge !

C'est parti !

Si on décompose les blocs du chiffré :

cd7985389a47184ce3f957b15a1c45f3 = ENCRYPT:AAAAAAAA
248ebbc75342e2750717224bf4faac93 = début du flag
d342aade957372e5683ddfdf760c850c = fin du flag

On peut déduire que le flag fait entre 17 et 32 caractères.

Nous allons donc appliquer un padding suffisant (39), en envoyant l'hexa adéquat, afin d'obtenir ceci :

bloc1 = ENCRYPT:AAAAAAAA
bloc2 = AAAAAAAAAAAAAAAA
bloc3 = AAAAAAAAAAAAAAA? 
(? premier caractère du flag)
bloc4 = milieu du flag
bloc5 = fin du flag

On note le bloc3 puis on remplace le ? par chaque caractère imprimable de la table ascii jusqu'à ce qu'on retrouve le bloc3.
Puis on réduis notre de padding en ajoutant le caractère trouvé à la fin et on refait la même chose pour trouver le caractère suivant et ainsi de suite.

bloc1 = ENCRYPT:AAAAAAAA
bloc2 = AAAAAAAAAAAAAAAA
bloc3 = AAAAAAAAAAAAAAx? 
(x premier caractère trouvé)
(? deuxième caractère du flag)
bloc4 = milieu du flag
bloc5 = fin du flag

On comprend donc pourquoi un padding de 39 est nécessaire pour être sur de toujours avoir un seul caractère à trouver en fin de bloc, puisque le flag fait maximum 32 caractères.

Voici le script python (optimisé par rapport à celui utilisé pour la première résolution :p) qui permet de trouver le flag caractère par caractère :

import socket
import time

hote = "107.170.122.6"
port = 7765


def padding(size):
	return '41'*size

padsize = 40
hexflag=""
flag=""

for i in range(1,33):
	print "[+] Search plain character",i
	s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	s.connect((hote, port))

	rep = s.recv(1024)
	s.send(padding(padsize-i)+"\n")
	time.sleep(2)
	data = s.recv(999999999).strip().split(':')[1]
	truebloc = data[63:96]
	s.close()

	for ascii in range(33,127):
		s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		s.connect((hote, port))
		rep = s.recv(1024)
		s.send(padding(padsize-i)+hexflag+hex(ascii)[2:]+'\n')
		time.sleep(2)
		cipher = s.recv(999999999).strip().split(':')[1]
		bloc = cipher[63:96]

		if bloc == truebloc:
			hexflag+=hex(ascii)[2:]
			flag+=chr(ascii)
			s.close()
			print "[+] character found :",chr(ascii)
			break
		s.close()
	
	if chr(ascii)=='}':
		break

print "[+] FLAG :",flag

Output

[+] FLAG : ABCTF{p4dding_4_fun}