ICHSA CTF 2021 Writeup

この大会は2021/6/1 3:00(JST)~2021/6/3 2:00(JST)に開催されました。
今回もチームで参戦。結果は900点で80チーム中20位でした。
自分で解けた問題をWriteupとして書いておきます。

Pikatchu's Fault (Fault Injection 100)

RSA CRT Fault Attackで素因数分解する。

from Crypto.Util.number import *
import re

N = 14428106165822100311240179104702593030922229606910261061860621459798477042443217675708638511393783602097391544907229222395222465303690975922805833664159517707002845392996861764206707180372733989400577838887924912395532925065643238637812097531657082837158436848594708309238918390308191361234059534178077141595873018313112575974600539522798755895311426362091301356794727864093165846318233065617022292712839240223002241134094765005135404901074586741323378531189871102865921045940469715763216120732916020528427859371641401521785824628585853094123501351577248220567930583676467206786442597142305655569749177107891502253803
e = 65537
M = 0x4c6f766520616e64206b69737365732c206d697373696e6720796f7521000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2
M2 = 0x1a36d7ecad4b18bc765ff0e3d3ecde7cae84bf1cc445a6fdcb48c335d9d3a043817fc014d55bfad94b51eb522b4ff719d33f012afe1498efafe660b97d1cb806d4ff7985a40a14f9bec9d93e2eb20f3820423ba0e34302adc82156673fa164822779174e9b7a84a417873358d1d774ccdfdb1f36de6aa8249b00184aac2feb003782f16fb3f5d7b113387989f662062452793bbaad87014b1ebd4a020342a11a19d95f4d3b29dd5449c57ea0fd3b881542a7b8b0bdbc9dcb7b19337f40e723439365dc29625cb775e91aef886d79d1403725c5aefa21b3d69f8cacbbbafb8b35c86822113e71bd272ca7cdf68da0ab397c658cb6cc14225732bc07726155ecd1

C = pow(M, e, N)
C2 = pow(M2, e, N)

p = GCD(pow(C2 - C, e, N), N)
q = N / p

p_str = long_to_bytes(p)
q_str = long_to_bytes(q)

pattern = '(ICHSA_CTF{.+})'
m = re.search(pattern, p_str)
if m is not  None:
    flag = m.group(1)
    print flag

m = re.search(pattern, q_str)
if m is not  None:
    flag = m.group(1)
    print flag
ICHSA_CTF{who_needs_more_than_1_fault_with_crt}

Crime Cloud (Crypto 150)

同じ部分文字列が複数あると圧縮後のサイズが小さくなる性質を使って、一文字ずつ特定するCRIMEの問題。何回か試し、圧縮できると文字列長が95になることがわかったので、そのことを使った。

import socket
import string
import base64

def recvuntil(s, tail):
    data = ''
    while True:
        if tail in data:
            return data
        data += s.recv(1)

def send_req(s, inp):
    data = recvuntil(s, ': ')
    print data + inp
    s.sendall(inp + '\n')
    data = recvuntil(s, '\n').rstrip()
    print data
    result = base64.b64decode(data)
    return len(result)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('crime.ichsa.ctf.today', 8008))

flag = 'ICHSA_CTF{'
while True:
    found = False
    while True:
        for c in string.lowercase + '_}':
            length = send_req(s, flag + c)
            if length == 95:
                found = True
                flag += c
                break
        if found:
            break
    if '}' in flag:
        break

print flag

実行結果は以下の通り。

Wellcome!
Input an empty line to exit.

Your input: ICHSA_CTF{a
hYdfGqhcCslKVKOZs2IqD2a6Pkk5VY5xoXKL2+3xTtBphMYy32GlD+tqhwnC1bRsPq7Rmn+wI33WDXnmRdQytCBtdppS1PpHD6eFeM+eiS+r8jvGGNePa3/O2eZ9aC4Gmg==

Your input: ICHSA_CTF{b
4bMWx8xuv1WFm9L3CEYMVisIzRINZ+vQ6Des5vxVGwxQ5ejMyuS7qgnr9wULW0U+ZkK3pAEYIjAF4Og+eoegDvn8KTuveTen/PLh+noJqsBGL3F7Km8cZVhJK2DHobG7

Your input: ICHSA_CTF{c
XWOWujVqSDPHFD1iZbin3i6cjqc4TVyUI/RzzwjH9O5iJFTCNyYBPjbLwR+suKAKC2v6/FGoR5RS2FZGsdX9G4AZqCoC4HV3WsrCXPWe+WET2g6wz1zQBRC75b4D0yFB

              :

Your input: ICHSA_CTF{compressing_secret_with_input_is_a_crimex
5T8szXe3sbLs7QMqhPuLNvgMndeLdSNMTKVph61YSMZ0vFepclTdZLNBOpy3+3DiJK5+vZmomj0ZvUQMDGQNqu1xT1Q11Qz4+o94dEb6cYouIai48YtxZhxTRDElfPnnTQ==

Your input: ICHSA_CTF{compressing_secret_with_input_is_a_crimey
ipc/khikilO0Nol0u7WJzzq9SzzlgUukLXZywjDShyxVh3ccG66lWf9psRWlrLGOInPYnWWVZ+Htckvv9p5hlhDCN+KosGvxIomyw8dot7uJ1wc5dafI+fjK/Ec7fgwd

Your input: ICHSA_CTF{compressing_secret_with_input_is_a_crimez
7yEVcxMNh6VI9I59S5YwRycJo2KeKDuMtl3KeIupONCQPkSk72rEnXbwUoRuGHHk7S/fSYd7NrqSnveIfwTdvvBJAuo9GBmOV5gdhyY/22ZdEiNl0tjKwFy7uO0Zvb+ONw==

Your input: ICHSA_CTF{compressing_secret_with_input_is_a_crime_
+4kUG+Kr99nzEscVKE59NOXyAbKaNe1Lwm5y6cEkYxlw0OX/F9nX5liOTOAfedA5nUSynzBhqGuZA2y3e5I3pbChrUKLAwLFtQyfv3meZDvesXmL7fHo/EuTseD4tmJQ

Your input: ICHSA_CTF{compressing_secret_with_input_is_a_crime}
JGVzIGu6kyAvKFhETysHb39JpWUb/zhPNZ7E3cqCf9qhVZjC1vYivT2HTNgkJH4ZdfwkHR2UH9rDW75pv2+vrw5Z0e0ZxVsRxat2NDtaJoGL9pqmJCSaz5FBvgLbQdg=
ICHSA_CTF{compressing_secret_with_input_is_a_crime}
ICHSA_CTF{compressing_secret_with_input_is_a_crime}

Baby Homework (Crypto150)

入力+FLAGがAES暗号化される。1文字ずつはみ出させて該当する暗号ブロックを比較しながら、1文字ずつフラグを割り出す。

$ nc baby_homework.ichsa.ctf.today 8010
Hello! What do you want to encrypt today?
a
88d8390cee4a0b215d1b94e76240f9b07c35ee865d8de8676072d9b798c6e848

$ nc baby_homework.ichsa.ctf.today 8010
Hello! What do you want to encrypt today?
aa
c8b86dfb1d472aab7fc76e7840923af08fedccacc986bf96ad39a7218107079e

$ nc baby_homework.ichsa.ctf.today 8010
Hello! What do you want to encrypt today?
aaa
56bea9a8e003b77d540c509f89094fa3c1a68e6228cf6dcf80d3585f41a66f8d

$ nc baby_homework.ichsa.ctf.today 8010
Hello! What do you want to encrypt today?
aaaa
8d61319569762f580a991128cb825a795a8c7ec693f69900b71da9e365ee72f4

$ nc baby_homework.ichsa.ctf.today 8010
Hello! What do you want to encrypt today?
aaaaa
a93071ba224344256e4e6b461a7cb9e47929019756343aa26e8c2e15fc7ca914

$ nc baby_homework.ichsa.ctf.today 8010
Hello! What do you want to encrypt today?
aaaaaa
db3c7f3f162f1c478cc7acbb54357e5ff6ef6ff296c93f4f8d23a5a276489dad

$ nc baby_homework.ichsa.ctf.today 8010
Hello! What do you want to encrypt today?
aaaaaaa
8547849b18d7e0fca1aa795d52d956896a5121b13076b6afa043b02b0b930c1c3cf284e29ab154b48e8c11fb9e65cc04

以下のようなイメージになる。

0123456789abcdef
XXXXXXXFFFFFFFFF
FFFFFFFFFFFFFFFF
PPPPPPPPPPPPPPPP

以下のようなイメージで1文字ずつ割り出す。

0123456789abcdef
XXXXXXXXXXXXXXX?
XXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXF
FFFFFFFFFFFFFFFF
FFFFFFFFPPPPPPPP

比較するブロックは1ブロック目と3ブロック目。

import socket

def recvuntil(s, tail):
    data = ''
    while True:
        if tail in data:
            return data
        data += s.recv(1)

flag = ''
for i in range(25):
    for code in range(32, 127):
        if i < 16:
            try_inp = 'X' * (15 - i) + flag + chr(code) + 'X' * (31 - i)
        else:
            try_inp = flag[-15:] + chr(code) + 'X' * (31 - i)

        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect(('baby_homework.ichsa.ctf.today', 8010))

        data = recvuntil(s, '?\n').rstrip()
        print data
        print try_inp
        s.sendall(try_inp + '\n')

        data = recvuntil(s, '\n').rstrip()
        print data
        b0 = data[:32]
        b2 = data[64:96]
        if b0 == b2:
            flag += chr(code)
            break

print flag

実行結果は以下の通り。

         :
Hello! What do you want to encrypt today?
t_DeF4uL7_V4lu3vXXXXXXX
f7e2482f4ded017ed447307088a68c9a1964750ffb854741e05b71deb6afc0ef6a5121b13076b6afa043b02b0b930c1cf25ad892a7e0256af842d1c6a709e777
Hello! What do you want to encrypt today?
t_DeF4uL7_V4lu3wXXXXXXX
5d1c67ccc026686bea22df5f4c92020d1964750ffb854741e05b71deb6afc0ef6a5121b13076b6afa043b02b0b930c1c6134fd11f55e338884ec6bf1c50dfe3c
Hello! What do you want to encrypt today?
t_DeF4uL7_V4lu3xXXXXXXX
e9e27e8ec5f4cffd0e2936d4843789811964750ffb854741e05b71deb6afc0ef6a5121b13076b6afa043b02b0b930c1c1b646dd9b79f3f5352a8b8bde6ae65c2
Hello! What do you want to encrypt today?
t_DeF4uL7_V4lu3yXXXXXXX
7fd0994e1e4d361a39ad3647bee39d621964750ffb854741e05b71deb6afc0ef6a5121b13076b6afa043b02b0b930c1cc8b1fa40240694818015d8be8fd3b640
Hello! What do you want to encrypt today?
t_DeF4uL7_V4lu3zXXXXXXX
6a5121b13076b6afa043b02b0b930c1c1964750ffb854741e05b71deb6afc0ef6a5121b13076b6afa043b02b0b930c1c6cd49de5bb815da7f6f6dea44abb6efc
d0n7_7ruzt_DeF4uL7_V4lu3z
ICHSA_CTF{d0n7_7ruzt_DeF4uL7_V4lu3z}

Poodle1 (Crypto 200)

サーバの処理概要は以下の通り。

・flagを特定のkeyとランダムなivでAES-CBC暗号化して表示
・msg入力(hex)
・hexデコードし、AES-CBC復号
・パディングエラーになったら、メッセージ表示

CBC Padding Oracle Attackで復号する。

import socket

def recvuntil(s, tail):
    data = ''
    while True:
        if tail in data:
            return data
        data += s.recv(1)

def str_xor(s1, s2):
    return ''.join(chr(ord(a) ^ ord(b)) for a, b in zip(s1, s2))

def unpad(s):
    return s[:-ord(s[-1])]

def is_valid(s, enc):
    data = recvuntil(s, '>> ')
    print data + enc
    s.sendall(enc + '\n')

    data = recvuntil(s, '\n').rstrip()
    print data
    if data != 'invalid padding >:(':
        return True
    else:
        return False

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('poodle1.ichsa.ctf.today', 8003))

data = recvuntil(s, 'poodle?\n').rstrip()
print data
data = recvuntil(s, '\n').rstrip()
print data
data = recvuntil(s, '\n').rstrip()
print data
enc = data.decode('hex')

enc_blocks = []
for i in range(0, len(enc), 16):
    enc_blocks.append(enc[i:i+16])

xor_blocks = []
for i in range(len(enc_blocks)-1, 0, -1):
    xor_block = ''
    for j in range(16):
        for code in range(256):
            print '%d - %d - %d: %s' % (i, j, code, xor_block.encode('hex'))
            print '****', str_xor(xor_block, enc_blocks[i-1][-j:]), '****'
            try_pre_block = '\x00' * (16 - j - 1) + chr(code) + str_xor(xor_block, chr(j+1)*j)
            try_cipher = (try_pre_block + enc_blocks[i]).encode('hex')
            if is_valid(s, try_cipher):
                xor_code = (j+1) ^ code
                xor_block = chr(xor_code) + xor_block
                break

    xor_blocks.append(xor_block)

xor_blocks.reverse()

flag = ''
for i in range(len(xor_blocks)):
    flag += str_xor(enc_blocks[i], xor_blocks[i])

flag = unpad(flag)
print flag

実行結果は以下の通り。

hi there!
I am the oracle :D
searching for a poodle, huh?
what about that cute and encrypted poodle?

14bfcca6528034a7bb309e7a8fbd4299fbb3cecff54817a54bc1080965d4011b71fac255e72e39fa6d42baa6462c2531
2 - 0 - 0: 
****  ****

BTW, if you want to tell me something don't forget to encrypt it
(with my secret key, of course ;))


What do you want to tell me? ('nothing' to exit)
>> 0000000000000000000000000000000071fac255e72e39fa6d42baa6462c2531
invalid padding >:(
2 - 0 - 1: 
****  ****

What do you want to tell me? ('nothing' to exit)
>> 0000000000000000000000000000000171fac255e72e39fa6d42baa6462c2531
invalid padding >:(

         :

What do you want to tell me? ('nothing' to exit)
>> 4aec94e503cf67e3ed5bc735fb9d3cfdfbb3cecff54817a54bc1080965d4011b
invalid padding >:(
1 - 15 - 75: fc84f513df77f3fd4bd725eb8d2ced
**** CHSA_CTF{I_d0nt ****

What do you want to tell me? ('nothing' to exit)
>> 4bec94e503cf67e3ed5bc735fb9d3cfdfbb3cecff54817a54bc1080965d4011b
invalid padding >:(
1 - 15 - 76: fc84f513df77f3fd4bd725eb8d2ced
**** CHSA_CTF{I_d0nt ****

What do you want to tell me? ('nothing' to exit)
>> 4cec94e503cf67e3ed5bc735fb9d3cfdfbb3cecff54817a54bc1080965d4011b
invalid padding >:(
1 - 15 - 77: fc84f513df77f3fd4bd725eb8d2ced
**** CHSA_CTF{I_d0nt ****

What do you want to tell me? ('nothing' to exit)
>> 4dec94e503cf67e3ed5bc735fb9d3cfdfbb3cecff54817a54bc1080965d4011b
mmm... very interesting... thank you!
ICHSA_CTF{I_d0nt_like_oracl3s}
ICHSA_CTF{I_d0nt_like_oracl3s}

Poodle2 (Crypto 300)

考え方はPoodle1と同じ。"givemetheflag"は16バイト未満なので、適当な暗号1ブロックに対して、復号したものを求め、それとXORをとればよい。

import socket
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

def recvuntil(s, tail):
    data = ''
    while True:
        if tail in data:
            return data
        data += s.recv(1)

def str_xor(s1, s2):
    return ''.join(chr(ord(a) ^ ord(b)) for a, b in zip(s1, s2))

def is_valid(s, enc):
    data = recvuntil(s, '>> ')
    print data + enc
    s.sendall(enc + '\n')

    data = recvuntil(s, '\n').rstrip()
    print data
    if data != 'invalid padding >:(':
        return True
    else:
        return False

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('poodle2.ichsa.ctf.today', 8004))

data = recvuntil(s, 'it)\n').rstrip()
print data

enc_blocks = ['X' * 16, 'X' * 16]

xor_blocks = []
for i in range(len(enc_blocks)-1, 0, -1):
    xor_block = ''
    for j in range(16):
        for code in range(256):
            print '%d - %d - %d: %s' % (i, j, code, xor_block.encode('hex'))
            try_pre_block = '\x00' * (16 - j - 1) + chr(code) + str_xor(xor_block, chr(j+1)*j)
            try_cipher = (try_pre_block + enc_blocks[i]).encode('hex')
            if is_valid(s, try_cipher):
                xor_code = (j+1) ^ code
                xor_block = chr(xor_code) + xor_block
                break

    xor_blocks.append(xor_block)

xor_blocks.reverse()

msg = pad('givemetheflag', AES.block_size)
iv = str_xor(msg, xor_blocks[0])
cipher = (iv + enc_blocks[1]).encode('hex')

data = recvuntil(s, '>> ')
print data + cipher
s.sendall(cipher + '\n')
data = recvuntil(s, '\n').rstrip()
print data

実行結果は以下の通り。

hi there!
it's the oracle again :)
ok ok, I promise to give you your poodle this time - not encrypted
just send me the word: givemetheflag
encrypted
with my secret key
that only I know
>:)
(add padding if needed of course... I know you can do it)
1 - 0 - 0: 


What do you want to send me? ('nothing' to exit)
>> 0000000000000000000000000000000058585858585858585858585858585858
invalid padding >:(
1 - 0 - 1: 

What do you want to send me? ('nothing' to exit)
>> 0000000000000000000000000000000158585858585858585858585858585858
invalid padding >:(

        :

What do you want to send me? ('nothing' to exit)
>> 178d7ef913f2d24c87e9194c0f03f25c58585858585858585858585858585858
invalid padding >:(
1 - 15 - 24: 9d6ee903e2c25c97f9095c1f13e24c

What do you want to send me? ('nothing' to exit)
>> 188d7ef913f2d24c87e9194c0f03f25c58585858585858585858585858585858
invalid padding >:(
1 - 15 - 25: 9d6ee903e2c25c97f9095c1f13e24c

What do you want to send me? ('nothing' to exit)
>> 198d7ef913f2d24c87e9194c0f03f25c58585858585858585858585858585858
mmm... very interesting... thank you!

What do you want to send me? ('nothing' to exit)
>> 6ef4188c6e87b634f29f653d7810e14f58585858585858585858585858585858
nice work! b'ICHSA_CTF{y3s_y0u_c4n_als0_encrypt_in_tha7_w4y_a660a2}'
ICHSA_CTF{y3s_y0u_c4n_als0_encrypt_in_tha7_w4y_a660a2}