こんとろーるしーこんとろーるぶい

週末にカチャカチャッターン!したことを貼り付けていくブログ

SECCON Beginners CTF 2018 - Streaming

問題文

streaming_fa5287b1a4f1a48025b4ade8aaa044866aa09d99.zipの添付ファイルのみ。

writeup

添付ファイルを展開すると、encrypt.pyとenctyptedの2ファイル。
フラグ文字列をencrypted.pyで暗号化したファイルがencryptedのようだ。

root@kali:streaming# cat encrypt.py 
import os
from flag import flag


class Stream:
    A = 37423
    B = 61781
    C = 34607
    def __init__(self, seed):
        self.seed = seed % self.C

    def __iter__(self):
        return self

    def next(self):
        self.seed = (self.A * self.seed + self.B) % self.C
        return self.seed

g = Stream(int(os.urandom(8).encode('hex'), 16))

encrypted = ''
for i in range(0, len(flag), 2):
    a = int(flag[i:i+2].encode('hex'), 16) ^ g.next()
    encrypted += chr(a % 256)
    encrypted += chr(a / 256)

open('encrypted', 'wb').write(encrypted)

encrypt.pyを読み解くと、フラグ文字列を2バイトずつ取得して暗号化処理をかけている。 初回2バイトの暗号化処理は、乱数から生成したシードとのXORを実施しており、2回目以降は前回のXOR処理で使用したシードを元に、次のシードを再計算している。

乱数が何を使われたかはわからないが、初回のシードはos.urandom(8)から取得した乱数の34607の剰余を使っているため、復号のスクリプトを書いてフラグ文字列らしきものが復号できるまで1~34607までブルートフォースすればよさそうだ。

以下、復号のスクリプト

root@kali:streaming# cat decrypt.py 
import binascii

class Stream:
    A = 37423
    B = 61781
    C = 34607
    def __init__(self, seed):
        self.seed = seed % self.C

    def __iter__(self):
        return self

    def next(self):
        self.seed = (self.A * self.seed + self.B) % self.C
        return self.seed

encrypted = open('encrypted').read()

print(encrypted)

for C in range(34607):
    g = Stream(C)

    flag = ''
    for i in range(0, len(encrypted), 2):
        b = ord(encrypted[i:i+1]) + ord(encrypted[i+1:i+2])*256
        try:
            flag += binascii.unhexlify(hex(b ^ g.next())[2:]).decode("utf-8")
        except:
            continue

    if "ctf" in flag:
        print("C=" + str(C))
        print("flag=" + flag)
        break

実行する。

root@kali:streaming# python decrypt.py 
?e
   _?:WI?Y??@?J1?&>Q??RV
C=32495
flag=ctf4b{lcg-is-easily-predictable}

フラグゲット。
ctf4b{lcg-is-easily-predictable}