ebCTF 2013 Teaser | cry100 [Write-Up]

Cry100 était une épreuve de cryptographie, certainement l’épreuve la plus simple du CTF.
Plusieurs fichiers étaient donnés :
– crypto.py ; Le script python pour chiffrer / déchiffrer un fichier
– msg001.enc et msg002.enc ; les deux messages chiffrés à déchiffrer
– README ; contenant des informations importantes :

« We suspect an employee of one of the embassies has leaked confidential information to a foreign intelligence agency. We've managed to capture an individual whom we assume to be the recipient of the info. Our forensics department has managed to recover two messages from his outbox, which appear to be encrypted using some crypto tool. Along with each email our suspect also received an SMS message containing a password, however we were only able to recover one - "SieR1mephad7oose". Could you help us decrypt both messages? »

On sait donc que la clé du premier message msg001.enc est « SieR1mephad7oose ».
Bon bah… il suffit d’utiliser le script donné :

$ crypto.py decrypt SieR1mephad7oose msg001.enc msg001.txt

Voici le résultat :

« From: Vlugge Japie <vl.japie@yahoo.com>
To: Baron van Neemweggen <b.neemweggen@zmail.com>

Boss,

Sorry, I failed to get my hands on the information you
requested. Please don't tell the bureau - I'll have it
next week, promise!

Vlugge Japie »

Regardons le script un peu mieux.

#!/usr/bin/python2
import hashlib, string, sys

ROUNDS = 20000

def xor(a, b):
    l = min(len(a), len(b))
    return ''.join([chr(ord(x) ^ ord(y)) for x, y in zip(a[:l], b[:l])])

def h(x):
    x = hashlib.sha256(x).digest()
    x = xor(x[:16], x[16:])
    return x

def verify(x):
    return all([ord(c) < 127 for c in x])

def crypt(msg, passwd):
    k = h(passwd)

    for i in xrange(ROUNDS):
        k = h(k)

    out = ''
    for i in xrange(0, len(msg), 16):
        out += xor(msg[i:i+16], k)
        k = h(k + str(len(msg)))

    return out

def encrypt(msg, passwd):
    msg = crypt(msg, passwd)

    return msg.encode('base64')

def decrypt(msg, passwd):
    msg = crypt(msg.decode('base64'), passwd)

    if verify(msg):
        return msg
    else:
        sys.stderr.write('Looks like a bad decrypt\n')
        sys.exit(1)

if len(sys.argv) < 5 or sys.argv[1] not in ('encrypt', 'decrypt'):
    print 'Usage:\tcrypto.py encrypt <password> <infile> <outfile>'
    print '\tcrypto.py decrypt <password> <infile> <outfile>'
    sys.exit(1)

op, passwd, infile, outfile = sys.argv[1:]

inp = open(infile).read()

if op == 'encrypt':
    ct = encrypt(inp, passwd)
    open(outfile, 'w').write(ct)
elif op == 'decrypt':
    pt = decrypt(inp, passwd)
    open(outfile, 'w').write(pt)

Apparemment, le chiffrement et le déchiffrement sont la même chose (excepté que le message chiffré est encodé en base64).
La fonction xor() xor les deux strings donnés en paramètre (de la même manière que PHP ; c’est à dire, en arrêtant le XOR à la fin de la string la plus courte).
La fonction h() hash le password donné en sha256, puis XOR les 16 premiers caractères avec les 16 caractères suivants.
La fonction verify() vérifie que le texte déchiffré est bien de l’ASCII ; c’est à dire que ses octets sont tous inférieurs à 127.
Enfin, la fonction de chiffrement / déchiffrement : encrypt().
Elle hash le password avec la fonction h() (qui retourne donc une clé de 16 octets), et répète cette action 20000 fois.
Mais on s’en fout un peu de ça : au final, la clé retournée fait quand même 16 octets, c’est ce qui comptera pour la cryptanalyse.
Ensuite, le texte est chiffré avec la clé. Tous les 16 caractères, la clé est « actualisée ».

Nous supposons que le deuxième message commence, comme le premier, par « From: Vlugge Japie … ».
Prenons les 16 premiers octets : « From: Vlugge Jap ». Faisons un xor de ce string avec les 16 premiers octets du msg002. On obtient (en hexadécimal) :
ea28b9a847b19d6100cc4943f22deff1

Ceci est la clé pour les 16 premiers octets. À partir de là, maintenant qu’on connait la clé pour le premier passage de la boucle, on peut connaître les autres clés pour les autres passages dans la boucle (le déchiffrement se faisant 16 octets par 16 octets).
On peut déchiffrer le msg002 ainsi simplement en modifiant le script python.
En modifiant la fonction crypt() comme ceci :

def crypt(msg, passwd):
    k = 'ea28b9a847b19d6100cc4943f22deff1'.decode('hex')
    out = ''
    for i in xrange(0, len(msg), 16):
        out += xor(msg[i:i+16], k)
        k = h(k + str(len(msg)))
    return out

Puis, en lançant le script python avec un password bidon (de toute façon il n’est pas utilisé. D’ailleurs, au final, on n’aura pas trouvé le vrai password pour déchiffrer msg002) :
crypto.py decrypt bidon msg002.enc msg002.txt

Résultat dans le fichier txt crée :

« From: Vlugge Japie <vl.japie@yahoo.com>
To: Baron van Neemweggen <b.neemweggen@zmail.com>
Subj: Found it!

Boss,

I found some strange code on one of the documents.
Is this what you're looking for?

ebCTF{21bbc4f404fa2057cde2adbf864b5481}

Vlugge Japie »

Il ne reste plus qu’à valider l’épreuve avec le flag obtenu.
Done 🙂

Publicités

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s