SEETF 2023 Writeup

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

Join the Discord (Misc)

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

SEE{1_h4v3_r34d_th3_rul3s

#faqチャネルのメッセージを見ると、フラグの後半があった。

_4nd_f4qs_30543ee20a21ececf3962724f7ad5549}
SEE{1_h4v3_r34d_th3_rul3s_4nd_f4qs_30543ee20a21ececf3962724f7ad5549}

1337 Word Search (Misc)

1行1337文字の適当な文字が1337行分テキストファイルに書いてある。縦横斜めで"SEE{"と並ぶ箇所を見つける。

#!/usr/bin/env python3
def get_flag(s):
    if '}' in s:
        index = s.index('}')
        return s[:index + 1]
    return ''

with open('wordsearch1.txt', 'r') as f:
    lines = f.read().splitlines()

tbl = []
for line in lines:
    tbl.append(list(line))

#### horizontally ####
for i in range(len(tbl)):
    for j in range(len(tbl[0]) - 3):
        if ''.join(tbl[i][j:j+4]) == 'SEE{':
            flag = ''.join(tbl[i][j:])
            flag = get_flag(flag)
            if flag != '':
                print(flag)
        if ''.join(tbl[i][j:j+4]) == '{EES':
            flag = ''.join(tbl[i][:j+3])[::-1]
            flag = get_flag(flag)
            if flag != '':
                print(flag)

#### vertically ####
for i in range(len(tbl[0])):
    for j in range(len(tbl) - 3):
        if tbl[j][i] == 'S' and tbl[j+1][i] == 'E' and tbl[j+2][i] == 'E' \
            and tbl[j+3][i] == '{':
            flag = ''
            for k in range(j, len(tbl)):
                flag += tbl[k][i]
            flag = get_flag(flag)
            if flag != '':
                print(flag)
        if tbl[j][i] == '{' and tbl[j+1][i] == 'E' and tbl[j+2][i] == 'E' \
            and tbl[j+3][i] == 'S':
            flag = ''
            for k in range(j+3, -1. -1):
                flag += tbl[k][i]
            flag = get_flag(flag)
            if flag != '':
                print(flag)

#### diagonally (left top <-> right bottom)
for i in range(len(tbl) - 3):
    for j in range(len(tbl[0]) - 3):
        if tbl[i][j] == 'S' and tbl[i+1][j+1] == 'E' and tbl[i+2][j+2] == 'E' \
            and tbl[i+3][j+3] == '{':
            l = len(tbl) - max(i, j)
            flag = ''
            for k in range(l):
                flag += tbl[i+k][j+k]
            flag = get_flag(flag)
            if flag != '':
                print(flag)
    for j in range(len(tbl[0]) - 3):
        if tbl[i][j] == '{' and tbl[i+1][j+1] == 'E' and tbl[i+2][j+2] == 'E' \
            and tbl[i+3][j+3] == 'S':
            l = min(i, j) + 1
            flag = ''
            for k in range(l):
                flag += tbl[i+3-k][j+3-k]
            flag = get_flag(flag)
            if flag != '':
                print(flag)
SEE{you_found_me_now_try_the_1337er_one}

NoCode (Misc)

2つ目のコードエリアの箇所をコピペして、code.txtに貼り付けし保存する。スペースの種類が2つ混ざっているので、以下の変換をしてデコードする。

"\xe2\x80\x8b" -> "0"
" " -> "1"
#!/usr/bin/env python3
with open('code.txt', 'rb') as f:
    data = f.read()

data = data.replace(b'\xe2\x80\x8b', b'0')
data = data.replace(b' ', b'1')
data = data.decode()

flag = ''
for i in range(0, len(data), 8):
    flag += chr(int(data[i:i+8], 2))
print(flag)
SEE{vanilla.js.org_dfe6a05ccbec9bda49cd1b70b2692b45}

decompile-me (Rev)

pycをデコンパイルする。

$ decompyle3 decompile-me.pyc 
# decompyle3 version 3.9.0
# Python bytecode version base 3.7.0 (3394)
# Decompiled from: Python 3.10.6 (main, May 29 2023, 11:10:38) [GCC 11.3.0]
# Embedded file name: decompile-me.py
# Compiled at: 2023-04-25 00:58:34
# Size of source mod 2**32: 433 bytes
from pwn import xor
with open('flag.txt', 'rb') as f:
    flag = f.read()
a = flag[0:len(flag) // 3]
b = flag[len(flag) // 3:2 * len(flag) // 3]
c = flag[2 * len(flag) // 3:]
a = xor(a, int(str(len(flag))[0]) + int(str(len(flag))[1]))
b = xor(a, b)
c = xor(b, c)
a = xor(c, a)
b = xor(a, b)
c = xor(b, c)
c = xor(c, int(str(len(flag))[0]) * int(str(len(flag))[1]))
enc = a + b + c
with open('output.txt', 'wb') as f:
    f.write(enc)
# okay decompiling decompile-me.pyc

逆算してフラグを得る。

#!/usr/bin/env python3
from pwn import xor

with open('output.txt', 'rb') as f:
    enc = f.read()

l = len(enc)
l0 = int(str(l)[0])
l1 = int(str(l)[1])
assert l % 3 == 0

a = enc[:l // 3]
b = enc[l // 3:l * 2 // 3]
c = enc[l * 2 // 3:]
c = xor(c, l0 * l1)
c = xor(b, c)
b = xor(a, b)
a = xor(c, a)
c = xor(b, c)
b = xor(a, b)
a = xor(a, l0 + l1)
flag = (a + b + c).decode()
print(flag)
SEE{s1mP4l_D3c0mp1l3r_XDXD}

BabyRC4 (Crypto)

RC4はkeyが同じ場合、平文と暗号文のXORは同じ。わかっている平文と暗号文のペアからXOR鍵を求め。その鍵でフラグの暗号文をXORして復号する。

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

p1 = b'a' * 36
c0 = bytes.fromhex('b99665ef4329b168cc1d672dd51081b719e640286e1b0fb124403cb59ddb3cc74bda4fd85dfc')
c1 = bytes.fromhex('a5c237b6102db668ce467579c702d5af4bec7e7d4c0831e3707438a6a3c818d019d555fc')

key = strxor(p1, c1)
assert len(c0) - len(key) == 2
flag = 'SE' + strxor(key, c0[:len(key)]).decode()[::-1]
print(flag)
SEE{n3vEr_reU53_rC4_k3y5ss5s:cafe2835}

Dumb Chall (Crypto)

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

・p: ランダム128ビット素数
・g: ランダム128ビット素数
・x: FLAGの数値化したもの
・y = pow(g, x, p)
・p, g, yを表示
・seen_c: 空集合
・30回以下繰り返し
 ・w, r: None
 ・choice: ランダムTrue or False
 ・choiceがFalseの場合
  ・w: 数値入力
  ・C: 数値入力
  ・Cがseen_cにあった場合、エラー
  ・seen_cにCを追加
  ・verify = first_verify
   ・(y * C) % p == pow(g, w, p)
 ・choinceがTrueの場合
  ・r: 数値入力
  ・C: 数値入力
  ・Cがseen_cにあった場合、エラー
  ・seen_cにCを追加
  ・verify = second_verify
   ・pow(g, r, p) == C
 ・verify(g, p, y, C, w, r)がFalseの場合、エラー
・フラグを表示

first_verifyの場合は、wを適当に選べば、Cを算出できる。
second_verifyの場合は、rを適当に選べば、Cを算出できる。

#!/usr/bin/env python3
import socket
import random

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(('win.the.seetf.sg', 3002))

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

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

for round in range(30):
    data = recvuntil(s, b': ')
    if 'w: ' in data:
        w = random.randrange(p)
        C = pow(g, w, p) * pow(y, -1, p) % p
        print(data + str(w))
        s.sendall(str(w).encode() + b'\n')
    else:
        r = random.randrange(p)
        C = pow(g, r, p)
        print(data + str(r))
        s.sendall(str(r).encode() + b'\n')
    data = recvuntil(s, b': ')
    print(data + str(C))
    s.sendall(str(C).encode() + b'\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)

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

実行結果は以下の通り。

p = 296501284807546731286222674011617639919
g = 95371665021421347944344267760751115144
y = 225243662779361830239958981796122960771
Something something zero-knowledge proofs blah blah...
Why not just issue the challenge and the verification at the same time? Saves TCP overhead!
Enter r: 133572976341856494726824263203248470720
Enter C: 179588428044400899838332318476058719280
You passed round 1.
Enter r: 202973207580200590528110454250281327631
Enter C: 235480426437196502413054566028726346305
You passed round 2.
Enter r: 7414755408304036614441511798254244165
Enter C: 54125112289563613715101342247374523629
You passed round 3.
Enter w: 260123886617174086643349635478769386459
Enter C: 17219449578745458937954879953842511031
You passed round 4.
Enter r: 91889379823064068530969714361335767267
Enter C: 131968360735560353118798543907743310427
You passed round 5.
Enter w: 137336816467403325168950719926067898227
Enter C: 60123469497270109417416956907291578135
You passed round 6.
Enter r: 145023642770488950169867193724427525785
Enter C: 144185721178483374498394339752661577914
You passed round 7.
Enter w: 164294497030901449452042347261545803281
Enter C: 236697805861904489943500865186383061603
You passed round 8.
Enter r: 269766404332636621028071094501291774109
Enter C: 203449902044462863689198391512397768065
You passed round 9.
Enter r: 83740389473424216611859576229610376232
Enter C: 5097305045013870186650024825216032349
You passed round 10.
Enter w: 3870506410528159151889283566532960802
Enter C: 175441168270801461041788133725955717704
You passed round 11.
Enter w: 266958709167233109481673204951008904973
Enter C: 233951923491560929521148314504251574134
You passed round 12.
Enter w: 103978737507927507127124098590270305201
Enter C: 82973205426509303560237009061738487148
You passed round 13.
Enter r: 272960543211065485146103697541658078626
Enter C: 188274484168571980400986889814133975815
You passed round 14.
Enter r: 131875022480537854319235450449672949118
Enter C: 212924441625621256291586645770170291573
You passed round 15.
Enter w: 224311984960205702179955563713205137203
Enter C: 260949222951303776585333476358645488540
You passed round 16.
Enter r: 34953140111251853371285999319997486857
Enter C: 290736585547277197568283160428093874000
You passed round 17.
Enter w: 207151185320386504972684171560450260610
Enter C: 203639970643156885061979274509687607624
You passed round 18.
Enter w: 36814522119674132218714126600848195317
Enter C: 149056176588752735110431047315769789213
You passed round 19.
Enter w: 249272653774376107204407990390443255206
Enter C: 142998004902767948862154654443648018808
You passed round 20.
Enter r: 147967477305859324932884197820440616342
Enter C: 291025086042792789992780774686934492305
You passed round 21.
Enter r: 228827713399248474102259804963877211978
Enter C: 57583549422604186310417640883288189694
You passed round 22.
Enter r: 175144007867686060403800121553758231661
Enter C: 67133237178855319905147393665118054014
You passed round 23.
Enter r: 84311493882052458862214241208805711939
Enter C: 290627072170301548507002119797988462323
You passed round 24.
Enter r: 154673847729816788029099691905080011589
Enter C: 248517482066109990184661715319967535598
You passed round 25.
Enter r: 287812620350746355699538586297835217189
Enter C: 77224864890694517704548610830262991286
You passed round 26.
Enter r: 112277248992406389673897555590419227653
Enter C: 268919531425183396107371045940075649237
You passed round 27.
Enter w: 16801219959875109241529074924012074495
Enter C: 179922130443790799793669184787258002277
You passed round 28.
Enter w: 252059376635180990332396670531906090801
Enter C: 24522676018904173495938438968496760461
You passed round 29.
Enter r: 240227875194019421705211875747502302959
Enter C: 89586121949639401073820496191564995588
You passed round 30.
You were more likely to get hit by lightning than proof correctly 30 times in a row, you must know the secret right?
A flag for your troubles - SEE{1_571ll_h4v3_n0_kn0wl3d63}
SEE{1_571ll_h4v3_n0_kn0wl3d63}

OpenEndedRSA (Crypto)

RSA暗号で、n、e、cの他にsも出力されている。sはpの2乗とppの2乗の和でppは素数、ppのバイト文字列の"?"の長さから16バイトと推測できる。
ppのブルートフォースで条件を満たすpを求める。
あとは通常通り復号する。

#!/usr/bin/env python3
from Crypto.Util.number import *
from gmpy2 import iroot
from sympy import nextprime

n = 102273879596517810990377282423472726027460443064683939304011542123196710774901060989067270532492298567093229128321692329740628450490799826352111218401958040398966213264648582167008910307308861267119229380385416523073063233676439205431787341959762456158735901628476769492808819670332459690695414384805355960329
e = 65537
c = 51295852362773645802164495088356504014656085673555383524516532497310520206771348899894261255951572784181072534252355368923583221684536838148556235818725495078521334113983852688551123368250626610738927980373728679163439512668552165205712876265795806444660262239275273091657848381708848495732343517789776957423
s = 128507372710876266809116441521071993373501360950301439928940005102517141449185048274058750442578112761334152960722557830781512085114879670147631965370048855192288440768620271468214898335819263102540763641617908275932788291551543955368740728922769245855304034817063220790250913667769787523374734049532482184053

pp = 1
while pp < 2 ** (8 * 16):
    pp = nextprime(pp)
    p, success = iroot(s - pp ** 2, 2)
    if success:
        if n % p == 0:
            break

q = n // p
phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(c, d, n)
flag = long_to_bytes(m).decode()
print(flag)
SEE{0dd_3vEN:deadbeef}