SECCON CTF 2019 Crypto Writeup(+3) Crypto道場八[13/100]
どうも。duckです。
今回はCrypto全部解きました。及第点といったところでしょう。
本戦への壁は厚いっすね。くそう。
1.coffee_break
2.ZKPay
3.Crazy Repetition of Codes
1.coffee_break
貼るけど一応元ファイルもおいとく。
file:encrypt.py
Dropbox - encrypt.py_b7d6c2e28d7f4eee9db8673b5c82191e54d1fea1 - Simplify your life
暗号文は問題文に書いてあった。
cipher.txt
FyRyZNBO2MG6ncd3hEkC/yeYKUseI/CxYoZiIeV2fe/Jmtwx+WbWmU1gtMX9m905
encrypt.py
import sys from Crypto.Cipher import AES import base64 def encrypt(key, text): s = '' for i in range(len(text)): s += chr((((ord(text[i]) - 0x20) + (ord(key[i % len(key)]) - 0x20)) % (0x7e - 0x20 + 1)) + 0x20) return s key1 = "SECCON" key2 = "seccon2019" text = sys.argv[1] enc1 = encrypt(key1, text) cipher = AES.new(key2 + chr(0x00) * (16 - (len(key2) % 16)), AES.MODE_ECB) p = 16 - (len(enc1) % 16) enc2 = cipher.encrypt(enc1 + chr(p) * p) print(base64.b64encode(enc2).decode('ascii'))
自作encrypt関数とAESで暗号化してますがAESのkeyがすでにわかっているのでAESはないも同然。Base64は暗号化ですらない。一瞬でデコードできる。
よって、自作encrypt関数を復号する関数を書くだけでよい。
数式書いてた紙をなくしたので、珍しくコードを貼っておく。なんかちょっとしたmodだった気がする。
def decrypt(key,enc): dec = '' for i in range(len(enc)): num = ord(enc[i]) + 32 - ord(key[i % len(key)]) if 126 > num > 33: dec += chr(num) elif 126 > num + 95 > 33: dec += chr(num+95) return dec
アスキーコードで文字を表してるのが33~126くらいだったと記憶している。
だから上のような書き方だと思われる。たぶん。しかもこれなんか不備があった気がするけど、答え出たから無視した気がする。
from Crypto.Cipher import AES import base64 key1 = "SECCON" key2 = "seccon2019" C = "FyRyZNBO2MG6ncd3hEkC/yeYKUseI/CxYoZiIeV2fe/Jmtwx+WbWmU1gtMX9m905" enc2 = base64.b64decode(C) cipher = AES.new(key2 + chr(0x00) * (16 - (len(key2) % 16)), AES.MODE_ECB) enc1 = cipher.decrypt(enc2) ans = decrypt(key1,enc1) print(ans)
これで答えが出るはず。たぶん。
2.ZKPay
URLにアクセスして、Registerしたあとログインする。
home画面はこんな感じ。1000000$むしり取ればフラッグが見れるっぽい。最初はrootが500くれてるだけの画面だったが、いろいろいじってたら今はこんな感じになってる。
send画面。人にお金を送るためのQRコードを発行できる。ただし持ってるお金以上送ろうとすると怒られる。
もらったQRコードをここで読んでみた。(QRコードをパソコンで読み取る(インストール不要))
username=egg&amount=100&proof=MI0bWQyatoUpSAwexg5YfHsb16bVb/HvGO9S73dOnlUiMSAwjDWHNyfVBJu4LboVjYNRHX7Amd268Hn42CJCz9ACog8wCjDxUF86bhqo/gNRrX2hVEmmr3TVD5y/oyWcV0qhdI9sCgfs4LBHdYEZ88Y2t9mQ+y8reD9wKBg50HvHYLs+wgwZMSAwEqtINttKiFkBf1z0oRDZVNzlDMHIcNBmpWPqsspe5hYwCjChQs4pLc1BI+RcZEcwnlIVXKMg3DtLCpmb2ae68wVpFjAgMICbAKUpCUTEkvPqCgAyEn495kmmuIHHwvAMOcgoBBckMAowc88QQ3fzC/fScQ/bYjgWatbrwSv6oC5b7FKjGVe8UgkxCjCFM8gQREFJNGRpfKk0oxjHDid21FI+kyBx8grtsf0vKjAK&hash=c728621fcb2fc4fffb72f4ce548a5d04b8a7424199f022d0377c2f99e76564d9
これをrecieve画面で読み込むとお金をゲットできる。
ユーザーの初期の手持ちは500しかなく、ユーザーをたくさん作って送り付けてもいいが、ちまちまやってられないので、借金を背負ってもらってゲームを終わらせる。
username=egg&amount=-1000000&proof=MI0bWQyatoUpSAwexg5YfHsb16bVb/HvGO9S73dOnlUiMSAwjDWHNyfVBJu4LboVjYNRHX7Amd268Hn42CJCz9ACog8wCjDxUF86bhqo/gNRrX2hVEmmr3TVD5y/oyWcV0qhdI9sCgfs4LBHdYEZ88Y2t9mQ+y8reD9wKBg50HvHYLs+wgwZMSAwEqtINttKiFkBf1z0oRDZVNzlDMHIcNBmpWPqsspe5hYwCjChQs4pLc1BI+RcZEcwnlIVXKMg3DtLCpmb2ae68wVpFjAgMICbAKUpCUTEkvPqCgAyEn495kmmuIHHwvAMOcgoBBckMAowc88QQ3fzC/fScQ/bYjgWatbrwSv6oC5b7FKjGVe8UgkxCjCFM8gQREFJNGRpfKk0oxjHDid21FI+kyBx8grtsf0vKjAK&hash=c728621fcb2fc4fffb72f4ce548a5d04b8a7424199f022d0377c2f99e76564d9
amount=-1000000となるQRコードはsendからだと作れない仕様なので、自分で作ってreceiveに流せばおけ。
認証に使われってるっぽいproofとhashを復元するのかと思って無駄に時間食った。つらい。
3.Crazy Repetition of Codes
code:
import os from Crypto.Cipher import AES def crc32(crc, data): crc = 0xFFFFFFFF ^ crc for c in data: crc = crc ^ ord(c) for i in range(8): crc = (crc >> 1) ^ (0xEDB88320 * (crc & 1)) return 0xFFFFFFFF ^ crc key = b"" crc = 0 for i in range(int("1" * 10000)): crc = crc32(crc, "TSG") assert(crc == 0xb09bc54f) key += crc.to_bytes(4, byteorder='big') crc = 0 for i in range(int("1" * 10000)): crc = crc32(crc, "is") key += crc.to_bytes(4, byteorder='big') crc = 0 for i in range(int("1" * 10000)): crc = crc32(crc, "here") key += crc.to_bytes(4, byteorder='big') crc = 0 for i in range(int("1" * 10000)): crc = crc32(crc, "at") key += crc.to_bytes(4, byteorder='big') crc = 0 for i in range(int("1" * 10000)): crc = crc32(crc, "SECCON") key += crc.to_bytes(4, byteorder='big') crc = 0 for i in range(int("1" * 10000)): crc = crc32(crc, "CTF!") key += crc.to_bytes(4, byteorder='big') flag = os.environ['FLAG'] assert(len(flag) == 32) aes = AES.new(key, AES.MODE_ECB) encoded = aes.encrypt(flag) assert(encoded.hex() == '79833173d435b6c5d8aa08f790d6b0dc8c4ef525823d4ebdb0b4a8f2090ac81e')
なかなかにクレイジー。
range(int("1" * 10000))
とくにここ。(10000個並んでる。)回も crc32を使って計算している。
方針は単純。どっかでループすると思われるので、それを特定する。ランダム要素がないので最悪でもcrc32を回ぶん回せばループが発覚する。鳩ノ巣原理。
あとはを計算して、ループの先だけ計算すればよし。
回ぶん回すのも頭おかしいと思ったが、ほとんどbit演算だったので意外に行けた。高速化するためにC++で書きなおしたけど。
一応アルゴリズムごと高速化できるアイデアもあったが、実装しなくてもいけたのでお蔵入りした。
こつこつcrcを計算したらあとは以下のコードで復号できた。
import os from Crypto.Cipher import AES key = b"" crc = 0xb09bc54f key += crc.to_bytes(4, byteorder='big') crc = 3836056187 key += crc.to_bytes(4, byteorder='big') crc = 2369777541 key += crc.to_bytes(4, byteorder='big') crc = 3007692607 key += crc.to_bytes(4, byteorder='big') crc = 1526093488 key += crc.to_bytes(4, byteorder='big') crc = 3679021396 key += crc.to_bytes(4, byteorder='big') text='79833173d435b6c5d8aa08f790d6b0dc8c4ef525823d4ebdb0b4a8f2090ac81e' bin=binascii.unhexlify(text) aes = AES.new(key, AES.MODE_ECB) flag = aes.decrypt(bin) print(flag)
眠すぎて何を書いてるかもよくわからない。お休み。