redpwn CTf 2021 | yahtzee

#redpwnctf2021

#!/usr/local/bin/python

from Crypto.Cipher import AES
from Crypto.Util.number import long_to_bytes
from random import randint
from binascii import hexlify

with open('flag.txt','r') as f:
    flag = f.read().strip()

with open('keyfile','rb') as f:
    key = f.read()
    assert len(key)==32

'''
Pseudorandom number generators are weak!
True randomness comes from phyisical objects, like dice!
'''
class TrueRNG:

    @staticmethod
    def die():
        return randint(1, 6)

    @staticmethod
    def yahtzee(N):
        dice = [TrueRNG.die() for n in range(N)]
        return sum(dice)

    def __init__(self, num_dice):
        self.rolls = num_dice

    def next(self):
        return TrueRNG.yahtzee(self.rolls)

def encrypt(message, key, true_rng):
    nonce = true_rng.next()
    cipher = AES.new(key, AES.MODE_CTR, nonce=long_to_bytes(nonce))
    return cipher.encrypt(message)

'''
Stick the flag in a random quote!
'''
def random_message():
    NUM_QUOTES = 25
    quote_idx = randint(0,NUM_QUOTES-1)
    with open('quotes.txt','r') as f:
        for idx, line in enumerate(f):
            if idx == quote_idx:
                quote = line.strip().split()
                break
    quote.insert(randint(0, len(quote)), flag)
    return ' '.join(quote)

banner = '''
============================================================================
=            Welcome to the yahtzee message encryption service.            =
=  We use top-of-the-line TRUE random number generators... dice in a cup!  =
============================================================================
Would you like some samples?
'''
prompt = "Would you like some more samples, or are you ready to 'quit'?\n"

if __name__ == '__main__':
    NUM_DICE = 2
    true_rng = TrueRNG(NUM_DICE)
    inp = input(banner)
    while 'quit' not in inp.lower():
        message = random_message().encode()
        encrypted = encrypt(message, key, true_rng)
        print('Ciphertext:', hexlify(encrypted).decode())
        inp = input(prompt)

CTRモード / PRNG

RNGのエントロピーが小さいので同じキーストリームが再生成されるのでmultiple times pad

1 - Connect to server and collect a huge amount of ciphered messages.

2 - Create buckets with messages of the same size.

3 - AES-CTR should not use the same nonce. Since the nonce is the sum of two dices, it will repeat sometimes and because of that AES(m1) ^ AES(m2) == m1 ^ m2

4 - Each message has the flag, so I XOR(m1 ^ m2, 'flag{') for all pair of messages in the same bucket and got some partial clear messages.

5 - List of messages where the flag was put at the begining:

178 - Life

180 - Every

184 - I did

190 - Life

192 - I att

194 - Defin

196 - We mu

200 - Build

204 - The q

210 - Too m

212 - The o

216 - The b

224 - Remem

6 - Since the messages are from quotes.txt, I tried to find some quote (from "Albert Einstein") that fits to what I had.

178 - Life is what we make it, always has ben, always will be