眠いので寝ますスマートフォン用の表示で見る眠いので寝ますお勉強のメモを置いていこうと思いまふ2018-06-14セキュリティキャンプ2018応募課題イベントはじめにこの度セキュリティキャンプ2018のトラックAの脆弱性・マルウェア解析トラックに応募し選考を通過することができたので、その際の応募用紙を掲載したいと思います。自分は今まで特にこれといった開発経験がなく30日OS本とN予備校のオンラインテキスト等々を見てプログラミングの基礎を学んだくらいで、とても受かるとも思っていませんでした。参加者の中では底辺レベルだと思います(これはプロ特有の煽りではなく、ガチのやつ)。言い換えると自分の応募用紙はセキュリティキャンプのボーダーラインと言えると思うので、僕と同じような状況の人のために来年度以降の選考通過をお祈りして、応募用紙を掲載しようと思います。注)原文を載せているのでかなり見づらいです。注)Q6,Q7は答えが違う可能性があります。他の部分についても間違いがありましたら見つけ次第指摘してもらえると嬉しいです。応募用紙Q1:自分が今まで作ってきたものを自慢する課題です。上にも書いた通り、30日OS本とN予備校のテキストで作ったwebアプリのことを書きました。ここは参考にならないと思うので省略させていただきます。Q2:今までに解析したことのあるソフトウェアやハードウェアにはどのようなものがありますか?また、その解析目的や解析方法、工夫した点があればそれらも教えてください。自分は半年前ほどからCTFのPwnを始めて、少しではありますが問題を解いてきました。2013),VillagerA(ksnctf)などです。解析する際のおおまかな流れは、まず静的解析でどの実行環境で動くバイナリか、checksec.shを実行してセキュリティ機構を確認しどの脆弱性の可能性があるかを確認した後、実際に実行して挙動をみたり、デバッガを使ってアセンブラレベルで動作を追って脆弱性を探します。脆弱性が見つかったらその脆弱性に対して攻撃を仕掛けて行くというのが流れです。解析してる途中に自力でもどうしようもなくなった場合はネットでwriteupを探しそこから少しヒントをもらい、また問題に取り組むようにしています。CTFでバイナリを解析していると、脆弱性などのセキュリティ面の知識以外にも発見があります。脆弱性を見つけることができて、バイナリの挙動が分かっても攻撃の標的がわからないことが多々あります。Overwrite攻撃ができる脆弱性があるのですが、僕はその攻撃を使ってどこを攻撃してよいかわかりませんでした。そこで詰んでしまいwriteupを見るとプログラムはmain関数が始まる前と終わった後でコンストラクタ、デストラクタという領域にジャンプしているということを知りました。当時の僕は恥ずかしながらコンストラクタ、デストラクタの存在すら知らなかったので非常に新鮮な気持ちになったのを覚えています。このようにPwnをしているとセキュリティの知識だけでなくプログラマとして役立つ周辺知識も身につくのでとても楽しいです。僕の通っている大学の同じ学科の周りの人にもとても技術力の高い人はいますが、低レイヤーに興味のある人がなかなかいません。僕のセキュリティキャンプへの参加を志望する理由の一つもそこにあり、低レイヤ―に興味がある仲間を作ってCTFプレイヤーとして、エンジニアとして切磋琢磨できる仲間が欲しいと考えています。Q3:自分のSNSアカウントを載せます。Q4:あなたが今年のセキュリティ・キャンプで受講したいと思っている講義は何ですか?(複数可)またそれらを受講したい理由を教えてください。Q1でも述べたのですが、自分は自作OSを作ったことがあります。それは本当にOSの基礎部分であり、実際に世に出ているOSとは比べ物にならないと思います。僕はこれから、組み込みOSなどの違うアーキテクチャのOSや、何かの動きに特化したOSなども開発していきたいと思っているので、実用的なOSはどのようなメモリ管理、システムコールなどなどを実装しているのかはとても興味があるのでLinuxシステムの動きの理解を深められそうなこの講義はとても惹かれるものがあります。1のE4と同じ理由です。講義名がもう面白いです。個人的にはyoutubeに上がっている講師の小崎さんのmallocの解説動画がとてもわかりやすくおもしろかったので、小崎さんの話を直接聞きたいなぁという願望もあります。自分はQ5の課題のSpectreを選んで調べました。英語のテキストを読んだりしたので、恥ずかしながら理解ができてない部分も多くあると思います。攻撃の例がいろいろ挙げられているのですが、実際のクラウドサービスなどで攻撃ができるとしたら、そのサービスにはどういう条件が必要なのかなどの理解が追い付いていないです。実際の攻撃例は動画で見たことがあるのですが、自分の手元で再現はしたことがなく(再現のプロセスがわからない)、この講義では自分の手を動かしながら動作を追うことができるということで、よりこの脆弱性に対する理解、カーネルの動きに対する理解が深まると思い興味を持ちました。Q5:僕の調べた脆弱性は最近話題になっていたMeltdownとSpectreのうち、Spectreです。SpectreはCPUのキャッシュを用いたサイドチャネル攻撃と、投機的実行による脆弱性です。キャッシュを用いたサイドチャネル攻撃とはCPUがデータにアクセスする際にキャッシュに乗っているデータとRAMに乗っているデータではキャッシュに乗っているデータのほうが速くアクセスできることを用いた攻撃です。一方で投機的実行は、CPUの命令は命令によって実行時間が違うので、実行時間が遅い命令を待たないで先に次の命令を実行してCPUの動作を高速化する技術です。これがどういう脆弱性を生むのか、この課題の解答の最後にのせた参考資料1の資料からサンプルコードを引用します。arr2->data[index2];}}サンプルコードにはないのですが13行目より前にCPUのキャッシュをフラッシュすることが必要になります。投機的実行が問題となるところが9行目のif分です。これは投機的実行のなかでも分岐予測実行とよばれるものが問題となっていて、if文の中身をtrueと予測して10行目からのコードをif文の結果を待たずに実行することができます。これもSpectreの脆弱性として言われていて、分岐予測は過去のif文の結果に基づいて行われているので、その過去の結果からの予測の仕方も操作できることが分かっています。if文の中身のuntrusted_offset_from_callerがarr1->lengthより大きかった場合は10行目で配列dataの長さを超えて、本来アクセスできないメモリを読みだして変数valueに格納します。11行目はvalueの最下位ビットが0か1かでそれぞれ0x200か0x300になります。12行目、13行目が実行されます。ここでif文の結果がfalseだとしても10行目から13行目で実行された結果は破棄されますが、13行目でアクセスしたarr2->data[index]はキャッシュには残ります。このコードのあとにarr2->data[0x200]にアクセスするようなコードを書き、その実行時間が閾値より短ければarr2->data[0x200]はキャッシュに乗っているのでvalueの最下位ビットは0、遅ければ1という判断ができます(このコードでは前提としてキャッシュのページサイズが0x100以下とします)。ここからは自分が考えたことなのですが、(説明がすでに十分であったから書いてないだけで自明なのかもしれないです)これを同様にして11行目のvalue&1の1を2,4,8,16,...,128というように順に実行していけば(毎回キャッシュはフラッシュする)valueの値が下から1ビットずつ、最終的には1バイトまるまる分かってしまいます。valueの値は本来アクセスできないメモリのデータであるのでこれは大変な脆弱性となります。ここで一つ疑問に思ったことがあり、valueの値をリークする時なぜ1ビットずつなのかということです。参考資料2のスライドにもあるように配列(上のコードではarr2)の大きさを0xff(キャッシュのページサイズ)にしてしまえば、1度上記のサンプルコードのif文のブロックを実行すれば、arr2->data[x0x100]のxを0から0xffまで繰り返してアクセスの実行時間を計測すれば済みますし、何回もキャッシュをフラッシュする必要もないのではないかと思いました。定量的に考えてみると理由が分かってきました。提案した方法だと1バイト読みだすのに最悪の場合で0xff+1回、すなわち256回ループを回すことになります。参考資料1のサンプルコードでは1ビットずつ読み出すので、キャッシュフラッシュとif文のブロックを繰り返すことになりますが、1バイト読みだすのに必ず8回のループで済みます。前者の方法ではvalueの値が8以下の時に後者の方法より速くなりますが、0xffまであるうちの8以下なので実行の合計時間が短くなるとは考えにくいです。大きなメモリ空間を攻撃するとなったときには、0x00が続く空間がたくさんある可能性も多くなってくるので、その時にはもしかしたら有効かもしれません。それでも1ビットずつのほうが安定したスピードでリークできますし、実行時間のおおよその計算もしやすいので実用的かと思いました。気になるSpectreの攻撃例と対策ですが、Spectreは現実的な攻撃ではないようです。攻撃ができる条件として、攻撃対象となるマシン上にもともと存在するコードか動的生成したコードをもとに命令ポインタを操作する必要があるので、それは厳しいとされています。攻撃が可能であった場合はそのコードが動いているプロセスのアクセス可能なメモリ空間を理論上すべてリークできるようで、カーネル内で実行すればカーネル空間もリークできるので理論上物理メモリすべてリークすることができると言われています。それではなぜ脆弱性が見つかって早急に対策が急がれたMeltdownではなくSpectreを選んだかというと、これは完全に僕の主観なのですがSpectreのほうが技術的に面白いと思ったからです。キャッシュに乗っているか否かをアクセス時間で判断し、標的のメモリビットを間接的にリークするアイデアにとても感心しました。電磁波解析や電磁波解析など、専門的な知識がないと根本的な理解ができないロマンチックなサイドチャネル攻撃があるなか、このサイドチャネル攻撃はキャッシュの存在を理解していれば理解できるのもとても魅力的でした。**argv)がrsiレジスタに格納されます。命令番地0x4003c0から0x4003e6までの動きとしては、rsp-0x8の中身が指す番地([rsp-0x8]と表記し、以下も同じように表記します)と[rsp-0x10]に定数を格納することと、ediと0x2を比較し等しくなければmain関数の最後にジャンプするという動きをしています。後者の動きは言い換えればコマンドライン引数の個数が2でなければ1を返り値として(eaxに1を格納して)main関数を終了していることになるので、実行結果を0にするにはコマンドライン引数の個数が2であることが必要条件になります。注意しておくポイントは、amd64のアーキテクチャはリトルエンディアンなので、命令番地0x4003c0,0x4003cdのmovabs命令の結果は[rsp-0x10]には0x0f、[rsp-0xf]には0x0e...というように格納されることです。次に命令番地0x4003e8から0x400402までの動きを見てみます。[rsi+0x8]には2個目のコマンドライン引数(以下ではコマンドライン引数と表記し2個目であることを表記しません)の文字列の開始番地が格納されています(argv[1])。これは[rsi]には1個目の開始番地が書かれていて、ポインタの配列の要素のサイズが8バイトであることから分かります。[rsi+0x8]をrdxレジスタに格納し、eaxをxor命令を使って0にします。0x4003eeからループする命令で、ecxにeaxの値をコピーし、rax(eax)をインクリメントします。その後[rdx-0x1+rax]の値と0x0を比較し等しくなければまた0x4003eeに戻ります。ループを抜けたらecxと0x8を比較し等しくなければeaxに1を格納してmain関数の最後にジャンプしています。ループを抜けたときraxはコマンドライン引数の文字数より1多くecxはその文字数に等しいです。ここでの動作はコマンドライン引数の文字数が8でなければ返り値を1としてmain関数を終了しています。コマンドライン引数の文字列が8文字であることが必要条件になります。命令番地0x400404では[rdx]をrsiレジスタにコピーします。先ほどrdxには[rsi+0x8]を格納しているので、[rdx]はコマンドライン引数の1文字目であり、[rdx+0x1]は2文字目です。0x400402まででコマンドライン引数の文字数は8文字であり、これは8バイトであるので、0x400404の命令でrsiにぴったりコマンドライン引数の文字が格納されます。注意点としてレジスタrsiの中身の詳細は最下位バイトが1文字目で、最上位バイトが8文字目です。0x400407,0x400409でxor命令を使ってeax(rax)とcl(ecx)レジスタを0に初期化します。ここからループで、0x40040bでrdxにrsiをコピーし、rdxをcl(ecx)ビット右シフトしand命令を使ってedx(rdx)の下位4ビット以外を0にし、[rsp-0x10+rdx]の値(1バイト)をedx(rdx)にゼロ拡張し格納します。その後rdxをcl(ecx)ビット左シフトし、ecxに0x4を足した後raxにraxとrdxの論理和をとります。ecxが0x40と等しくなければまた0x40040bにループします。このループで何をしているかというと、コマンドライン引数を最下位ビットから4ビットずつ取り出し、取り出した4ビットの値によって0x4003c0から0x4003dcまでで[rsp-0x10]から[rsp-0x01]までの連続番地に格納した定数8ビットを取り出し(8ビット単位で区切るとすべて上位4ビットが0になっているため実質4ビット)、これをまたraxに最下位ビットから4ビットずつ格納しています。ここで[rsp-0x10]から[rsp-0x01]までの連続番地はテーブルのような役割を果たしています。ecxと0x40を比べているのは0x40を0x4で割ると16で、4ビットずつ取り出して、4ビットずつ格納していくのを16回行うと64ビット、すなわちコマンドライン引数の文字列かつレジスタのビット数になるからです。このループを抜けた先の0x400427では上の命令で4ビットずつ格納していったraxと[rip-0x6e]の排他的論理和をとっています。[rip-0x6e]とはripの値が一つ先の命令番地0x40042eなので[0x4003c0]のことで、これはmain関数の先頭のアドレスから始まる機械語のビット列のことです。リトルエンディアンであることに注意すると[rip-0x6e]の値は0x1000b0d0e0fb848となります。0x40042eから0x40043eではraxと0x600967b3670e0385を比較し等しくなければalに1を、等しければalに0を格納し、alをeaxにゼロ拡張したあと関数を終了しています。実行結果を0にするためには0x400427のxor命令に入る直前にはraxは0x1000b0d0e0fb848と0x600967b3670e0385の排他的論理和、0x61096cbe6901bbcdである必要があります。そのために動作から逆算してうまく0x40040bから0x400422の間のループでraxに格納してあげます。最下位の1バイトは0xcdにする必要があることが分かり、最初に格納したテーブル的役割をもつ[rsp-0x10]から[rsp-0x1]までを見てみると、0xcは[rsp-0x9]に、0xdは[rsp-0xe]に位置します。4ビットを取り出す動作の時にここを参照すればうまくraxに0xcdを格納できそうです。そのために前の命令番地に戻ってみると、0x400414に入る直前にrdxをそれぞれ0xcを参照するには0x7、0xdでは0x2を格納しておけばよいので、0x400404で格納するrsiの最下位1バイトは0x72であることがわかります。これはコマンドライン引数の1文字目であるので、ASCIIコードを直すと1文字目は”r”であることがわかります。残りも同様に計算すると実行結果を0にする文字列は”r3En1gNe”となります。ここまで静的解析しかしてないので実際にコードを書いて動かしてみました。ファイル名の拡張子は.asmで、コンパイルにはnasmとgccを使用しました。プログラム中に実行結果が出力されることはなくraxのレジスタで判断するしかなかったので、radare2というレジスタの中身まで出力してくれるデバッガツールで動作を追って、main関数の終了の際のraxレジスタの状態を確認しました。問題文と変更した点はintel記法で書いたことと、0x4003c0、0x4003ee、0x40040b、0x400441の命令の前にそれぞれ、main、j1、j2、j3とラベルをつけてjneなどのジャンプ命令のオペランドを命令番地でなくラベルにしたことに加え、コンパイルが通らなかった箇所を何点か変更したので以下に列挙します。al(movzblだとコンパイルエラーだったため)尚cmpbやmovzblのオペコードの変更はコンパイル後、機械語で同じになっていることを確認しています。r3En1gNeを入力しradare2でステップ実行していくとmain関数の最後のret命令のときのraxレジスタの中身が0になっていることが確認できました。念のためコマンドライン引数を1文字変えてみるとraxレジスタは1になっていたので、”r3En1gNe”が特定の文字列です。CTFを少しかじっていたこともあり、アセンブラはすんなり解読でき、答えの文字列も早い段階で導きだせました。わからない命令はsetneぐらいだったのでそこは検索して調べました。最初のほうのediとrsiのレジスタの用途を知らなかったのですが、読んだ感じおそらくコマンドライン引数の個数とアドレス番地が入っていると予測でき、検索してみると予想通りでした。時間がかかったのがアセンブリプログラミングで、自作OSの際に少しやっていた経験はあったのですが、なかなかコンパイルが通らず、上に書いたように何度か試行錯誤し命令を少し変えたりしてコンパイルを通しました。答えの文字列まで一回でたどり着けたのもとても嬉しかったです。答えがreengineをいじったものかなと思ったのですが、iとgが入れ替わっているのでそこを指摘するまでの課題なのでしょうか。(追記)engineerを逆から読んだものでしたね、、、クソイキってしまいました。USBに使用されるファイルシステムの種類を調べるとFATかNTFSがメジャーであることが分かりました。0x90の形式をとっていて、これはNTFSファイルシステムのヘッダであることが分かりました。ちなみにFAT16だと0xEB,0x3C,0x90、FAT32だと0xEB,0x58,0x90であることも分かりました。オフセット1が壊れていた場合FATの可能性もあるのですが以下に続く課題でNTFSであると仮定して進めていくと、マウントできたのでファイルシステムはNTFSであると分かります。sectorのバイト列を参考資料1と2を見ながら修正をしていきます。オフセット3からの4バイトは0x4e,0x54,0x46,0x53に修正しました。ここはおそらくファイルシステムを判断するのに使用すると思われます。オフセット0xbからの2バイトは0x00,0x02に修正しました。これは1セクタが何バイトかを表すByte/Sectorというメンバで、リトルエンディアンで512を16進数に直した値をいれています。1セクタのバイト数は512が一般的なようなので、この値としました。オフセット0xdは0x08に修正しました。これは1クラスタが何セクタかを表すSector/clustというメンバで、これも一般的と言われている8を入れました。何かしらのエラーがでたら、これら二つのとりあえず一般的な値を入れた箇所を見直す必要があるかもしれません。上記の3か所を修正した後FTKImagerというマウントツールを使ってマウントしてみると、目的のpdfファイルiir_vol37.pdfとiir_vol38.pdfの2つが見つかりました。開いてみるとiir_vol37.pdfは開けるのですが、iir_vol38.pdfが壊れているようです。次に$MFTと呼ばれている部分を見ていきます。$MFTとはファイルシステム中のすべてのファイルの情報が書かれている特殊なファイルです。$MFTと呼ばれるメンバ(0x2155)と、上で述べたByte/Sector(0x200)とSector/clust(0x8)をすべて掛け合わせた0x2155000から始まることがわかります。$MFTの領域0x2155000に飛んでそこから下をずっと見ていくとASCII変換すると“FILE0”から始まるのバイト列のセットが繰り返されているのが分かります。$MFTの最初の4バイトはASCII変換で”FILE”を表す0x46,0x49,0x4c,0x45が固定なようです。0x2155000からはファイルシステムの中のすべてのファイルの$MFTが繰り返されているのだと予想できます。そのまま少し下に眺めていくとオフセット0x2165000に”FILE”ではなく”BAAD”で始まる$MFTがあります。その$MFT内の0x21650daから1バイトおきにiir_vol38.pdfと書かれていているのでこれがiir_vol38.pdfの$MFTだと推測できます。下に行くと0x2165400からは同様の推測方法でiir_vol37.pdfのものと思われる$MFTがあります。iir_vol37.pdfの$MFTの先頭4バイトは”FILE”だったのでiir_vol37.pdfが取り出せて、iir_vol38.pdfが取り出せなかった理由は先頭の4バイトにありそうです。0x2165000からの4バイトを0x46,0x49,0x4c,0x45に直してマウントしなおすと無事にiir_vol38.pdfを取り出すことができました。コマンドを打つとiir_vol37.pdfのハッシュ値:5d62f82532d62645cd67b312546a599d90d15ea09c5ee5dbd61c5d90fc945ad3iir_vol38.pdfのハッシュ値:86eb9f1fe34087f709a659b051d4d06930078c72d420683f2bf89455f31eb55fが得られました。念のためpythonのhashlibライブラリでそれらのファイルのsha256のハッシュ値を計算したところ、結果が一致しました。実は課題提出期限の8時間くらい前までNTFSではなくFAT16かFAT32のどっちかだと仮定して進めてきていました。それはFATとNTFSのファイルの先頭(オフセット0~2)が同じようなジャンプ命令を表すバイト列であることを考慮せず、FATの資料を見て、これはFATだと決めつけていました。どこのオフセットを見ても0xf8,0xffのようなFATテーブルの先頭のバイト列が見つかず、さらにFAT16か32かの区別さえつかず色々探る日が何日も続きました。NTFSである可能性に気づき、調べてNTFSであることを知ったときは感動した感情が一瞬現れた後、これ提出間に合うのかという焦りが現れ、ヒヤヒヤした時間を過ごすことができました。あっているかはわかりませんが、あきらめずに調べ続けて自分なりの答えが出せて満足しています。4.1.1.1参考にした資料1https://qiita.com/kusano_k/items/45b0a86649aabb8040ff2http://www.writeblocked.org/resources/ntfs_cheat_sheets.pdf3http://free.pjc.co.jp/fat/index.html4http://elm-chan.org/docs/fat.htmlまとめ自分は冬休みにとある長期インターンに応募しまして、そこでは当時話題になっていたMeltdownとSpecterの英語の論文を読んで脆弱性を理解するという課題を与えられたのですが、それがQ5に役立ちました。その時は1日オフィスで7時間ぐらい英語の論文を読まされて、「何このクソインターン」「自分何してんだろ」と思い、3日でインターンを辞退したのですが、それが今になって役に立つとは思いませんでした。(ちなみにそれがトラウマでそれ以来インターンには参加してません。スキルアップのためにインターン参加しようかな。Q6は普段趣味でCTFのPwnを解いているのでアセンブリはすらすら読めました。正解していると思います。Q7は課題締め切り当日まで残っていた課題で、用紙にも書きましたが、1週間くらいFATファイルシステムと思い込んで進めていて、締め切り日に気づいて急いで調べなおしました。あの時の心拍数は尋常じゃなかったです。諦めないって大事ですね。去年も応募したのですが、その時は全く歯が立たず、文字通り、とりあえず応募したような人間だったのですが、振り返ってみると自分がセキュキャンに通る実感がまだ湧きません。これで通ったなぁという気持ちです。プロの皆さんはセキュキャンがゴールではなくむしろスタートのようなことをよく仰っているのでこれからも精進したいと思います。最後になりますが、僕にCTFを教えてくれたり、この界隈に引き込んでくれた、同学同学科の界隈のプロの同期(一応名前は伏せておきますが、カンファレンスで登壇するような超プロ)には感謝してもしきれません。本当にありがとうございます。writeupHarekazeCTF2019のwriteupを少し。writeup久しぶりの更新、生きています。以前...2018-10-17picoCTF2018振り返り+ちょっとwriteuppicoCTF2018に参加しました。(1)はてなブログをはじめよう!kam1tsur3さんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。