Guenael

CTF, challenges & writeup

ebCTF - CRY100: Clear text attack

The second challenge for the ebCTF teaser was very easy too, and solved by a very large number of participants. For this crypto challenge named CRY100, the Zip file provided was self-contained and it can be started offline (available here ).

After unpacking, the README file drop a password, useful to decrypt the first encrypted message, but not the second. So, we have to decrypt this second message without additional help, but the answer is not very far :) Indeed, watching the code gives interesting details, and the most important one should be about the ciphering function, where a XOR is simply used. Another interesting point was about the input key, hashed many time before to be used on the XOR function.

The context given with the previous decrypted message illustrate a perfect case of plaintext attack : With the previous decrypted message, we can bet than the second ciphered message have the same header. Since the ciphering function was a simple XOR, we only have to take the first bloc of the ciphered text and XOR it with the clear text bloc to get the key. Of course, it’s not the original text key, but the computed key. For sure we cannot deduce easily the initial text key (hash = one way-function), but we don’t need to know this key, deciphering the second message is good enough :)

Well, adding only 2 lines of code allows to retrieve the computed key and get the content of the ciphered message.

clearText = "From: Vlugge Jap"
k = xor(msg[0:16], clearText)

In the application code:

#!/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 = ''

    # Clear text attack - ebCTF-CRY100 (2 lines)
    clearText = "From: Vlugge Jap"
    k = xor(msg[0:16], clearText)

    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)

And finally, the result:

ebCTF CRY100 Decoded message

Thanks again Eindbazen!