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}