魚脳の池

CTF:Little Twoos

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
どういうものかというと

f:id:Gyonou:20190409111856p:plain
本来CBCモードの復号化手順

f:id:Gyonou:20190409112006p:plain
今回アレンジした暗号文

$$ 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); \\
$$

本来の暗号化された暗号文は↑の感じ
ct1ct2のところ00...00ct0に替える

$$
\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});\\ $$

最後新しいpt0pt2xorを取れば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(に切り替えました。

f:id:Gyonou:20190409135007j:plain
一つ目

OCRが結構遅いので、回している間目grepでやってみたら、
スクリプトより早くフラグを取れました。(スクリプトもちゃんと答えを出せる)

Ghidra Release flag{l34kfr33_n4tion4l_s3cur1ty}