swamp ctf 2019 write-up
チームm1z0r3として参加しました。
解けた問題
We Three Keys [Crypto 144pts]
In this modern world of cyber, keys are the new kings of the world. In fact, we will let you use the 3 best keys that I could find, make sure to see what they offer to you!
nc chal1.swampctf.com 1441
#!/usr/bin/env python2 from Crypto.Cipher import AES # from keys import key1, key2, key3 def pkcs7_pad(msg): val = 16 - (len(msg) % 16) if val == 0: val = 16 pad_data = msg + (chr(val) * val) return pad_data def encrypt_message(ptxt,key, IV): print("What message woud you like to encrypt (in hex)?") # ptxt = raw_input("<= ") ptxt = pkcs7_pad(ptxt.decode('hex')) print ptxt.encode('hex') print len(ptxt) cipher = AES.new(key, AES.MODE_CBC, IV) ctxt = cipher.encrypt(ptxt) print ctxt.encode('hex') print len(ctxt) def decrypt_message(key, IV): print("What message would you like to decrypt (in hex)?") ctxt = raw_input("<= ") ctxt = ctxt.decode('hex') if (len(ctxt) % 16) != 0: print "What a fake message, I see through your lies" return cipher = AES.new(key, AES.MODE_CBC, IV) ptxt = cipher.decrypt(ctxt) print ptxt.encode('hex') def new_key(): print("Which key would you like to use now? All 3 are great") key_opt = str(raw_input("<= ")) if key_opt == "1": key = key1 elif key_opt == "2": key = key2 elif key_opt == "3": key = key3 else: print("Still no, pick a real key plz") exit() return key def main(): print("Hello! We present you with the future kings, we three keys!") print("Pick your key, and pick wisely!") key_opt = str(raw_input("<= ")) if key_opt == "1": key = key1 elif key_opt == "2": key = key2 elif key_opt == "3": key = key3 else: print("Come on, I said we have 3!") exit() while True: print("1) Encrypt a message") print("2) Decrypt a message") print("3) Choose a new key") print("4) Exit") choice = str(raw_input("<= ")) if choice == "1": encrypt_message(key, key) elif choice == "2": decrypt_message(key, key) elif choice == "3": key = new_key() else: exit() if __name__=='__main__': main()
pkcs7があったため、一瞬padding oracleだと思い込んだ。
でも実際知りたいのは平文ではなく、あくまでkey
だけなので、
padding oracleではないと気付いた。
一通り見終わってから得た情報は↓:
- AES問題
- 暗号化や復号化も全部自分でやれる
- ただただ平文を暗号/復号する
- keyは合計3つ
- IVのところはkeyになってる
crypto.stackexchange
で答えを探したら↓にヒットした
crypto.stackexchange.com
どういうものかというと
$$
C_0 = E_K(IV\oplus P_0); \\
C_1 = E_K(C_0\oplus P_1); \\
C_2 = E_K(C_1\oplus P_2); \\
$$
本来の暗号化された暗号文は↑の感じ
ct1
とct2
のところ00...00
とct0
に替える
$$
\dot{C_0} = C_0;\\
\dot{C_1} = (0...0);\\
\dot{C_2} = C_0;\\
$$
そうすると復号化された平文は↓のようになります:
$$
P_0 = IV\oplus D_K(\dot{C_0});\\
P_1 = \dot{C_0}\oplus D_K(0);\\
P_2 = 0\oplus D_K(\dot{C_0});\\
$$
最後新しいpt0
とpt2
をxor
を取ればkey
は出てくる。
$$ P_0\oplus P_2 = IV\oplus D_K(\dot{C_0})\oplus D_K(\dot{C_0})\\ = IV = K $$
↓はsolver
from m1z0r3 import * from Crypto.Util.number import long_to_bytes,bytes_to_long ip = "chal1.swampctf.com" port = 1441 flag = '' for i in range(1,4): s,f = sock(ip,port) print recv_until(f,'<= ') # choose key s.send('{}\n'.format(i)) # send i to choose i print recv_until(f,'<= ') # choose cmd s.send('1\n') # send 1 to choose enc print recv_until(f,'<= ') # ready to input ptext = hex(bytes_to_long('a'*32))[2:-1].zfill(64) s.send(ptext+'\n') # send ptext ctext1 = recv_line(f).strip() # get ciphertext ctext1_0 = ctext1[:32] print recv_until(f,'<= ') # choose cmd s.send('2\n') # send 2 to dec print recv_until(f,'<= ') # ready to input ct = ctext1_0 + '00'*16 + ctext1_0 s.send(ct+'\n') # send to dec pt1 = recv_line(f).strip().decode('hex') # get pt print pt1 pt1_0 = pt1[:16] pt1_2 = pt1[32:] print xor(pt1_0,pt1_2) flag += xor(pt1_0,pt1_2) print flag
We Three Keys flag{w0w_wh4t_l4zy_k3yz_much_w34k_crypt0_f41ls!}
Ghidra Release [Misc 310pts]
https://static.swampctf.com/ghidra_nsa_training.mp4
Ghidraに関する約15hのチュートリアルビデオがひたすら流されています。
多分SECCONのホテル問題と似たような感じだと推測して、
どこかで表示されるだと思うので、とりあえずffmpegで静止画に変換します。
ffmpeg -i ghidra_nsa_training.mp4 -vf fps=1/5 ghidra/img%08d.jpg
チュートリアルビデオと言ってもほとんど文字だけなので、
OCRに読んでもらうのが最初思いついたこと。
実装↓:
from PIL import Image import sys import pyocr import pyocr.builders tools = pyocr.get_available_tools() if len(tools) == 0: print("No OCR tool found") sys.exit(1) # The tools are returned in the recommended order of usage tool = tools[0] print("Will use tool '%s'" % (tool.get_name())) # Ex: Will use tool 'libtesseract' langs = tool.get_available_languages() print("Available languages: %s" % ", ".join(langs)) lang = langs[0] print("Will use lang '%s'" % (lang)) # Ex: Will use lang 'fra' # Note that languages are NOT sorted in any way. Please refer # to the system locale settings for the default language # to use. for i in range(1,11151): #import sys #sys.stdout.write("\033[2K\033[G%s" % str(i)) #sys.stdout.flush() if i % 1000 == 0: print(i) txt = tool.image_to_string( Image.open('ghidra/img{}.jpg'.format(str(i).zfill(8))), lang=lang, builder=pyocr.builders.TextBuilder() ) # txt is a Python string if 'FLAG(' in txt : print(txt) print(i)
もともとflag{
を基づいて探していましたが、
最初にヒットした画像のところにフラグが四つに分かれていることが判明して、
検索ワードをFLAG(
に切り替えました。
OCRが結構遅いので、回している間目grepでやってみたら、
スクリプトより早くフラグを取れました。(スクリプトもちゃんと答えを出せる)
Ghidra Release flag{l34kfr33_n4tion4l_s3cur1ty}