hack.lu CTF 2017: SALSA

,

problem

Salsa20と呼ばれる暗号。 暗号文と暗号化oracleが与えられるので複合せよ。

solution

暗号化はdataを送るとencrypt(key, nonce, counter, JSON({ "cnt": counter, "data": base64(data) }))が得られるというもの。 複数回oracleを利用するときの変化はcounterだけ。 結果の形が{"cnt": ...で固定であるため分かりにくいが、counterがひとつ増えると同じ文字を暗号化した結果はひとつずれるだけ。 つまりencrypt(counter, text)[: -1] == encrypt(counter + 1, text)[1 :]のようになっている。 これが分かればやるだけ。 $64$回の再接続をするような実装だと楽。

implementation

#!/usr/bin/env python2
from pwn import * # https://pypi.python.org/pypi/pwntools
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('host', nargs='?', default='flatearth.fluxfingers.net')
parser.add_argument('port', nargs='?', default=1721, type=int)
parser.add_argument('--log-level', default='debug')
args = parser.parse_args()
context.log_level = args.log_level

import json
def getmsgtext(msg_counter, text):
    msg = {"cnt" : msg_counter, "data" : text[: 128]}
    return json.dumps(msg)
msgtext_offset = getmsgtext(0, '__DATA__').index('__DATA__')

import base64
b64flag = [ '?' ] * 128
b64letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
for i, c in enumerate(b64letters):
    log.info('letter = %c', c)

    with remote(args.host, args.port) as p:
        time.sleep(0.2)
        encoded_flag = p.recv()
        assert len(encoded_flag) == 0x8e
        log.info(fiddling.hexdump(encoded_flag[msgtext_offset :]))

        def query(s):
            p.send(s)
            time.sleep(0.1)
            return p.recv()

        s = query(base64.b64decode(c * 128))
        log.info(fiddling.hexdump(s[msgtext_offset :]))
        for j, (a, b) in enumerate(zip(encoded_flag[msgtext_offset + 1 :], s[msgtext_offset :])):
            if a == b:
                b64flag[1 + j] = c

    log.info('base64(flag) = %s', ''.join(b64flag))