DownUnderCTF Writeup

この大会は2020/9/18 18:00(JST)~2020/9/20 18:00(JST)に開催されました。
今回もチームで参戦。結果は5470点で1080チーム中21位でした。
自分で解けた問題をWriteupとして書いておきます。

Twitter (misc 10)

Twitterを遡ると、base64文字列があった。

RFVDVEZ7aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1YZlI5aVk1eTk0c30=
$ echo RFVDVEZ7aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1YZlI5aVk1eTk0c30= | base64 -d
DUCTF{https://www.youtube.com/watch?v=XfR9iY5y94s}
DUCTF{https://www.youtube.com/watch?v=XfR9iY5y94s}

Discord (misc 10)

Discordに入り、#rulesチャネルのルールの記載の下にある、旗のマークをクリックすると、他のチャネルがたくさん表示された。
その中の#generalチャネルのトピックにフラグが書いてあった。

DUCTF{c0n6r475_y0u_h4v3_n0w_j01n3d_0ur_4m4z1n6_d15c0rd}

Welcome! (misc 100)

Welcome文があちこちに表示される。moreで止めながら、画面を見ていく。

$ ssh ductf@chal.duc.tf -p 30301 | more
ductf@chal.duc.tf's password: 

f:id:satou-y:20200927221708p:plain

DUCTF{w3lc0m3_t0_DUCTF_h4v3_fun!}

16 Home Runs (misc 100)

base64デコードするだけ。

$ echo RFVDVEZ7MTZfaDBtM19ydW41X20zNG41X3J1bm4xbjZfcDQ1N182NF9iNDUzNX0= | base64 -d
DUCTF{16_h0m3_run5_m34n5_runn1n6_p457_64_b4535}
DUCTF{16_h0m3_run5_m34n5_runn1n6_p457_64_b4535}

In a pickle (misc 500)

Iから始まるASCIIコードをデコードする。

>>> chr(112)
'p'
>>> chr(49)
'1'
>>> chr(99)
'c'
>>> chr(107)
'k'
>>> chr(108)
'l'
>>> chr(51)
'3'
>>> chr(95)
'_'
>>> chr(121)
'y'
>>> chr(48)
'0'
>>> chr(117)
'u'
>>> chr(82)
'R'
>>> chr(95)
'_'
>>> chr(109)
'm'
>>> chr(51)
'3'
>>> chr(53)
'5'
>>> chr(53)
'5'
>>> chr(52)
'4'
>>> chr(103)
'g'
>>> chr(51)
'3'

結合すると、フラグになる。

DUCTF{p1ckl3_y0uR_m3554g3}

formatting (reversing 100)

$ ltrace ./formatting 
sprintf("d1d_You_Just_ltrace_296faa2990ac"..., "%s%02x%02x%02x%02x%02x%02x%02x%0"..., "d1d_You_Just_ltrace_", 0x29, 0x6f, 0xaa, 0x29, 0x90, 0xac, 0xbc, 0x36) = 37
puts("haha its not that easy}"haha its not that easy}
)                 = 24
+++ exited (status 0) +++
DUCTF{d1d_You_Just_ltrace_296faa2990acbc36}

Leggos (web 100)

右クリックメニューは出せない。Chromeデベロッパーツールで、disableMouseRightClick.jsを読んでいることがわかり、このコードを見るとコメントにフラグが書いてあった。

  //the source reveals my favourite secret sauce 
  // DUCTF{n0_k37chup_ju57_54uc3_r4w_54uc3_9873984579843}
DUCTF{n0_k37chup_ju57_54uc3_r4w_54uc3_9873984579843}

On the spectrum (forensics 100)

Audacityで開き、スペクトグラムを見ると、フラグが現れた。
f:id:satou-y:20200927225806p:plain

DUCTF{m4by3_n0t_s0_h1dd3n}

rot-i (crypto 100)

アルファベットのみシフト数を1ずつ増やして復号する。

import string

with open('challenge.txt', 'r') as f:
    ct = f.read().rstrip()

pt = ''
i = 0
for c in ct:
    if c in string.uppercase:
        index = (string.uppercase.index(c) - i) % 26
        pt += string.uppercase[index]
        i += 1
    elif c in string.lowercase:
        index = (string.lowercase.index(c) - i) % 26
        pt += string.lowercase[index]
        i += 1
    else:
        pt += c
        i += 1

print pt

復号結果は以下の通り。

You've solved the beginner crypto challenge! The flag is DUCTF{crypto_is_fun_kjqlptzy}. Now get out some pen and paper for the rest of them, they won't all be this easy :).
DUCTF{crypto_is_fun_kjqlptzy}

babyrsa (crypto 500)

s = pow(557*p - 127*q, n - p - q, n)
  = pow(557*p - 127*q, (p - 1) * (q - 1) - 1, n)
p * q = n
    ↓
s * (557*p - 127*q) = 1 mod n
p * q = n
    ↓
557 * p - 127 * q = inverse(s, n)
p * q = n
    ↓
557 * p - 127 * (n/p) =  inverse(s, n)
557 * p**2 - inverse(s, n) * p - 127 * n = 0

この2次方程式を解けば、pを求められ、qもわかる。あとはそのまま復号すればよい。

from Crypto.Util.number import *
from sympy import *

n = 19574201286059123715221634877085223155972629451020572575626246458715199192950082143183900970133840359007922584516900405154928253156404028820410452946729670930374022025730036806358075325420793866358986719444785030579682635785758091517397518826225327945861556948820837789390500920096562699893770094581497500786817915616026940285194220703907757879335069896978124429681515117633335502362832425521219599726902327020044791308869970455616185847823063474157292399830070541968662959133724209945293515201291844650765335146840662879479678554559446535460674863857818111377905454946004143554616401168150446865964806314366426743287
s = 3737620488571314497417090205346622993399153545806108327860889306394326129600175543006901543011761797780057015381834670602598536525041405700999041351402341132165944655025231947620944792759658373970849932332556577226700342906965939940429619291540238435218958655907376220308160747457826709661045146370045811481759205791264522144828795638865497066922857401596416747229446467493237762035398880278951440472613839314827303657990772981353235597563642315346949041540358444800649606802434227470946957679458305736479634459353072326033223392515898946323827442647800803732869832414039987483103532294736136051838693397106408367097
c = 7000985606009752754441861235720582603834733127613290649448336518379922443691108836896703766316713029530466877153379023499681743990770084864966350162010821232666205770785101148479008355351759336287346355856788865821108805833681682634789677829987433936120195058542722765744907964994170091794684838166789470509159170062184723590372521926736663314174035152108646055156814533872908850156061945944033275433799625360972646646526892622394837096683592886825828549172814967424419459087181683325453243145295797505798955661717556202215878246001989162198550055315405304235478244266317677075034414773911739900576226293775140327580
e = 0x10001

var('p')
eq = Eq(557 * p**2 - inverse(s, n) * p - 127 * n)
ans = solve(eq)

for p in ans:
    if p > 0:
        break

q = n // p
assert p * q == n

phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(c, d, n)
flag = long_to_bytes(m)
print flag
DUCTF{e4sy_RSA_ch4ll_t0_g3t_st4rt3d}

Extra Cool Block Chaining (crypto 500)

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

・flagをKEY,IVで暗号化した結果を16進数表示
・平文を入力(16進数)
 →入力データ先頭16バイトをKEY,IVで暗号化し、その結果を16進数表示
・暗号文を入力(16進数)
 →入力データ先頭16バイトをKEY,IVで復号し、その結果を16進数表示

■encrypt
・msgをパディング
・blocks: 16バイトごとのブロックを配列化
・ブロックごとに以下の処理
 ・enc: AES-ECB暗号化
 ・2ブロック目以降は前のブロックの暗号化とさらにXORしたものを暗号化
・各ブロックをIVとXOR

■decrypt
・blocks: 16バイトごとのブロックを配列化
・ブロックごとに以下の処理
 ・IVとXOR
 ・2ブロック目以降は前のブロックの暗号化とXOR
 ・dec: AES-ECB復号

このことから以下のようなイメージで暗号、復号が行われることがわかる。

<暗号>
平文1ブロック目 --(AES-ECB暗号化)--> 暗号1                 ^ IV  -> 暗号1ブロック目
平文2ブロック目 --(AES-ECB暗号化)--> 暗号2 ^ 暗号1         ^ IV --> 暗号2ブロック目
平文3ブロック目 --(AES-ECB暗号化)--> 暗号3 ^ 暗号2 ^ 暗号1 ^ IV --> 暗号3ブロック目
         :

<復号>
暗号1ブロック目 ^ IV                   --(AES-ECB復号)--> 平文1ブロック目
暗号2ブロック目 ^ IV ^ 暗号1ブロック目 --(AES-ECB復号)--> 平文1ブロック目

平文:'\x10' * 16の場合、2ブロックにパディングされ、以下のように暗号化される。

1ブロック目:'\x10' * 16 --(AES-ECB暗号化)--> 暗号1 ^ IV
2ブロック目:'\x10' * 16 --(AES-ECB暗号化)--> 暗号1 ^ 暗号1 ^ IV = IV

これでIVがわかる。あとはflagの暗号化の各ブロックについて、以下のようにして復号する。

・1ブロック目はそのまま復号
・2ブロック目以降は前のブロックとIVとXORをして、復号
#!/usr/bin/env python3
import socket
from Crypto.Util.strxor import strxor

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

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

flag = ''
for i in range(6):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('chal.duc.tf', 30201))

    data = recvuntil(s, b'\n').rstrip()
    print(data)
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    flag_enc = data.split(' ')[-1]

    try_pt = b'10' * 16
    data = recvuntil(s, b': ')
    print(data + try_pt.decode())
    s.sendall(try_pt + b'\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    IV = bytes.fromhex(data)[16:]

    if i == 0:
        ct = flag_enc[32*i:32*i+32]
    else:
        pre_block = bytes.fromhex(flag_enc[32*(i-1):32*i])
        cur_block = bytes.fromhex(flag_enc[32*i:32*(i+1)])
        ct = strxor(strxor(pre_block, cur_block), IV).hex()
    data = recvuntil(s, b': ')
    print(data + ct)
    s.sendall(ct.encode() + b'\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    flag += bytes.fromhex(data).decode()

flag = unpad(flag)
print(flag)

実行結果は以下の通り。

Welcome! You get 1 block of encryption and 1 block of decryption.
Here is the ciphertext for some message you might like to read: 7009345f5ff2ec47da0392986d296a7ee99e1d4ec81a3648e5b5ce194e781c8278b4e869231fed6be94655e21b0b38bf942a746cfeab0bbe0ec8461c4ce3296a2b1aecfa0cf29f75656911f2b985dcef3c066963ed131a573c947b88bf88b628
Enter plaintext to encrypt (hex): 10101010101010101010101010101010
e8d9f607f98a38d207f42dfc6ab3d47f5e957b2dc0083bea0ead59532a0167c4
Enter ciphertext to decrypt (hex): 7009345f5ff2ec47da0392986d296a7e
44554354467b346444316e475f72346e
Welcome! You get 1 block of encryption and 1 block of decryption.
Here is the ciphertext for some message you might like to read: 780ab81f0b15bbbd1289ae623a88083b43da7fac8e114f7986fe332e8e2b045da8d240fa383bbdae4a1026ed211a9321ff3f2c8e6743774f7458ac59cf01dce0234c28bcb9359621733e7cebf95986fe79ba5cc4af573c2f1a9c098259a1f105
Enter plaintext to encrypt (hex): 10101010101010101010101010101010
9b11b6918c6f20a953b91181ed7cc136fd429b28d189924c7b64e6d0fd3c60bf
Enter ciphertext to decrypt (hex): c6925c9b548d6688ef137b9c499f6cd9
64304d5f3472523077735f344e445f78
Welcome! You get 1 block of encryption and 1 block of decryption.
Here is the ciphertext for some message you might like to read: 832e4b9908c1626b5106a0770497edf4e284fbccea327e9ef7d7ce6c119ec2476b21f0f0eee49c30c6c60b1fee4e2c092a0e02f639eaa5edb7e45b015c04789e5a2d825ffa308ba0b430127716015e70a6cd46b96a249a4a646f32dc5688e2f1
Enter plaintext to encrypt (hex): 10101010101010101010101010101010
2e0eedf9a49f58a1b00ad832fb3d9c5a22d08bca5bdf8fee319a1c9fc85fdc0d
Enter ciphertext to decrypt (hex): ab7580f65f096d40008bd9ec378f3243
3052535f683372335f346e445f746833
Welcome! You get 1 block of encryption and 1 block of decryption.
Here is the ciphertext for some message you might like to read: 69e3b032f80be1fa0df4b61240e4dc64c1faee9f2d8c08932a393e1654d814c27e92eb045382b0dfd137d7f45f12bb6e0e39556976b023d3d168e27b90589e2f2c37a77250743a9e629dbebd5ce5f68efde7748eb9d40b5fcaf255c9be417fac
Enter plaintext to encrypt (hex): 10101010101010101010101010101010
b75ee59680bd3f92b236172c1f0319fbaf39a1491cafa325e72b35f5a6ceca1f
Enter ciphertext to decrypt (hex): df921f24399d3029e774007a6984ef5e
52335f553575344c6c795f48336c7073
Welcome! You get 1 block of encryption and 1 block of decryption.
Here is the ciphertext for some message you might like to read: 87bf6f3c04536e1e4d31967093dd9872c2070cd747500a2bbb6de0c274fad133fea0ad8ad9c5b8e1531fecaa06608196cfca32fa5798c3b4cc17867246cba586f8a823005e3f6a2fdc2b4a597a85302cb30627d69eef1aa1689a2e129a63258f
Enter plaintext to encrypt (hex): 10101010101010101010101010101010
59c7d9f2b308f4ccab8ef4c3fcc6f7ea84e912bd5446abfc0565adc72ae6cd5c
Enter ciphertext to decrypt (hex): b38b03475de10267155961ec16a858f6
5f4275375f6e30545f374831735f7431
Welcome! You get 1 block of encryption and 1 block of decryption.
Here is the ciphertext for some message you might like to read: 6adbaa3cc6f37cf80872ed6ed02c544e9cd43b4c95df58de0612a18ed1cc131a25fc22511ec1ddaea4e7f88e35cbe8b5ef06755f18fd0aff6cca2736c6df22adf863ae23f955f749c4897b669620a4f182993ab79334d805be8fd66c9aeccf80
Enter plaintext to encrypt (hex): 10101010101010101010101010101010
ac302310925ddc23a00047983b20c4c28a8888fa78be64883c197264fa972f58
Enter ciphertext to decrypt (hex): f0721c6e12df4bc4461fdf6ef65b4429
6d335f69375f7333336d7321217d0202
DUCTF{4dD1nG_r4nd0M_4rR0ws_4ND_x0RS_h3r3_4nD_th3R3_U5u4Lly_H3lps_Bu7_n0T_7H1s_t1m3_i7_s33ms!!}
DUCTF{4dD1nG_r4nd0M_4rR0ws_4ND_x0RS_h3r3_4nD_th3R3_U5u4Lly_H3lps_Bu7_n0T_7H1s_t1m3_i7_s33ms!!}

ceebc (crypto 500)

CBC-MACの問題。sampleのCBC-MACが表示される。solution_messageのCBC-MACを指定できればフラグが表示される。
MACにIVの値が含まれるので、以下のようになるよう調整すれば、MACは同じ値になる。

sample ^ iv_org = solution_message ^ iv
#!/usr/bin/env python3
import socket
from Crypto.Util.strxor import strxor

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

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('chal.duc.tf', 30202))

data = recvuntil(s, b'\n').rstrip()
print(data)
sig_org = data.split(' ')[-1][:-1]

iv_org = bytes.fromhex(sig_org)[16:]
sample = b'cashcashcashcash'
solution_message = b'flagflagflagflag'

iv = strxor(strxor(sample, iv_org), solution_message)
sig = sig_org[:32] + iv.hex()

data = recvuntil(s, b': ')
print(data + solution_message.decode())
s.sendall(solution_message + b'\n')
data = recvuntil(s, b': ')
print(data + sig)
s.sendall(sig.encode() + b'\n')

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

実行結果は以下の通り。

Hey there, have a message cashcashcashcash and its signature b14049c2168c7737329b8b1342abdb18d991e0c9073556fa8406cd351f6663f3!
Now give me your message: flagflagflagflag
Now the signature (in hex): b14049c2168c7737329b8b1342abdb18dc9cf2c6023844f5810bdf3a1a6b71fc
Signature valid!
DUCTF{WAT_I_THOUGHT_IV_IZ_G00D}
DUCTF{WAT_I_THOUGHT_IV_IZ_G00D}

Cosmic Rays (crypto 500)

少し試した結果を含め、整理すると以下のようになる。

blocks: 暗号ブロック配列(11ブロック)

平文0ブロック目 ^ フラグ前半 --(AES-ECB暗号化)--> 暗号0 ^ フラグ後半      --> 暗号0ブロック目
平文1ブロック目 ^ 暗号0      --(AES-ECB暗号化)--> 暗号1 ^ 平文0ブロック目 --> 暗号1ブロック目
平文2ブロック目 ^ 暗号1      --(AES-ECB暗号化)--> 暗号2 ^ 平文1ブロック目 --> 暗号2ブロック目
                    :

平文
0123456789abcdef
If Bruce Schneie
r multiplies two
 primes, the pro
duct is prime. O
n a completely u
nrelated note, t
he key used to e
ncrypt this mess
age is 0?9d0fe19
20ca?85e3851b162
b8cc9?5PPPPPPPPP

暗号文
ed5dd65ef5ac36e886830cf006359b30
0?1??7??????c?????a?????8???????
d6?????7????b????2?????????f?d?0
??????????????????????6?????????
????f?????????????????????d??b??
?a?????e??c?????2??????????0??3?
0c??f????????????1??7???????????
??1e??0?0?????9??c??e??2??4????7
?????0?????4????????f???7?????e?
b??9????4?f??1?c??6?0a?3a0e6?d7?
975d?1cde66e41791b?780988c9b8329

鍵
0?9d0fe1920ca?85e3851b162b8cc9?5

最後の暗号ブロックから求めていく。

平文10ブロック目 ^ 暗号9    --(AES-ECB暗号化)--> 暗号10 ^ 平文9ブロック目 --> 暗号10ブロック目

不明箇所数は以下の通りで、取る値は0~f。

暗号11ブロック目: 2
平文 9ブロック目: 1
平文10ブロック目: 1
平文11ブロック目: 1
鍵: 3

合計 5(鍵が平文に含まれている)

ブルートフォースで暗号9を求め、形式があっているかチェックし、鍵と平文、暗号10ブロック目、暗号11ブロック目を特定する。あとは順に暗号1ブロック目まで算出する。

平文0ブロック目 ^ フラグ前半 --(AES-ECB暗号化)--> 暗号0 ^ フラグ後半      --> 暗号0ブロック目

上記の暗号0を求めたら、暗号0ブロック目とのXORでフラグ後半がわかる。また暗号0を復号して、平文0ブロック目とのXORでフラグ前半がわかる。

from Crypto.Cipher import AES
from Crypto.Util.strxor import strxor
import itertools
import string

def check_c9(s):
    if s[0] != 'b':
        return False
    if s[3] != '9':
        return False
    if s[8] != '4':
        return False
    if s[10] != 'f':
        return False
    if s[13] != '1':
        return False
    if s[15] != 'c':
        return False
    if s[18] != '6':
        return False
    if s[20:22] != '0a':
        return False
    if s[23:28] != '3a0e6':
        return False
    if s[29:31] != 'd7':
        return False
    return True

msg = 'If Bruce Schneier multiplies two primes, the product is prime. On a completely unrelated note, the key used to encrypt this message is '

in_cs = []
for c in itertools.product(string.hexdigits[:16], repeat=5):

    c10 = '975d%s1cde66e41791b%s780988c9b8329' % (c[0], c[1])
    key = '0%s9d0fe1920ca%s85e3851b162b8cc9%s5' % (c[2], c[3], c[4])
    p8 = 'age is 0%s9d0fe19' % c[2]
    p9 = '20ca%s85e3851b162' % c[3]
    p10 = ('b8cc9%s5' % c[4]) + '\x09' * 9

    in_c10 = strxor(c10.decode('hex'), p9)
    in_p10 = AES.new(key.decode('hex'), AES.MODE_ECB).decrypt(in_c10)
    in_c9 = strxor(p10, in_p10)
    c9 = strxor(in_c9, p8).encode('hex')
    if check_c9(c9):
        KEY = key
        in_cs.append(in_c9)
        print '[+] KEY =', KEY
        print '[+] ct10 =', c10
        print '[+] ct9 =', c9
        break

msg += KEY[:25]
ps = [msg[i:i+16] for i in range(0, len(msg), 16)]

for i in range(9):
    in_p = AES.new(KEY.decode('hex'), AES.MODE_ECB).decrypt(in_cs[-1])
    in_c = strxor(ps[9-i], in_p)
    in_cs.append(in_c)
    if i < 8:
        c = strxor(in_c, ps[7-i]).encode('hex')
        print '[+] ct%d =' % (8 - i), c

c0 = 'ed5dd65ef5ac36e886830cf006359b30'
flag2 = strxor(c0.decode('hex'), in_cs[-1])
print '[+] flag2 =', flag2

in_p = AES.new(KEY.decode('hex'), AES.MODE_ECB).decrypt(in_cs[-1])
flag1 = strxor(ps[0], in_p)
print '[+] flag1 =', flag1

flag = 'DUCTF{%s}' % (flag1 + flag2)
print flag

実行結果は以下の通り。

[+] KEY = 0b9d0fe1920ca685e3851b162b8cc9e5
[+] ct10 = 975d21cde66e41791b8780988c9b8329
[+] ct9 = b7c9076140feb15c1e690a83a0e60d75
[+] ct8 = 55fca0ebd654e6962862f31270f414ea
[+] ct7 = ed1e8e0f0f5e33993c8ee28200461ad7
[+] ct6 = 0c0dff963317506a110a742feea43cf2
[+] ct5 = 3a1fe35ed6c0b03d2e1a8badb6c04e33
[+] ct4 = 3096f135f702fdda23b764c1bfde3b10
[+] ct3 = a45d08c7dee9da217412bb6edcdab75f
[+] ct2 = d6e5c397d1d4bd6f42539db06aff5de0
[+] ct1 = 0112c744b0aac58207aea28e804ec6ab
[+] flag2 = !_but_fl4gs_ar3!
[+] flag1 = IVs_4r3nt_s3cret
DUCTF{IVs_4r3nt_s3cret!_but_fl4gs_ar3!}
DUCTF{IVs_4r3nt_s3cret!_but_fl4gs_ar3!}

Survey (survey 10)

アンケートに答えたら、フラグが表示された。

DUCTF{th4nk_y0u_f0r_p4rt1c1pating_in_DUCTF!1!1}