過密です

タイトルしょうもなさすぎたので変えました

セキュリティキャンプ2018応募課題

はじめに

この度セキュリティキャンプ2018のトラックAの脆弱性マルウェア解析トラックに応募し選考を通過することができたので、その際の応募用紙を掲載したいと思います。

自分は今まで特にこれといった開発経験がなく30日OS本とN予備校のオンラインテキスト等々を見てプログラミングの基礎を学んだくらいで、とても受かるとも思っていませんでした。おそらく参加者の中では底辺レベルだと思います(これはプロ特有の煽りではなく、ガチのやつ)。言い換えると自分の応募用紙はセキュリティキャンプのボーダーラインと言えると思うので、僕と同じような状況の人のために来年度以降の選考通過をお祈りして、応募用紙を掲載しようと思います。

注)原文を載せているのでかなり見づらいです。どうかご容赦ください。

注)Q6,Q7は答えが違う可能性があります。また他の部分についても間違いがありましたら見つけ次第指摘してもらえると嬉しいです。

応募用紙

Q1:

自分が今まで作ってきたものを自慢する課題です。上にも書いた通り、30日OS本とN予備校のテキストで作ったwebアプリのことを書きました。おそらくここは参考にならないと思うので省略させていただきます。

Q2:今までに解析したことのあるソフトウェアやハードウェアにはどのようなものがありますか?また、その解析目的や解析方法、工夫した点があればそれらも教えてください。

自分は半年前ほどからCTFのPwnを始めて、少しではありますが問題を解いてきました。どれも初心者向けの問題ではありますが、greeting (Tokyo Westerns/MMA CTF 2nd 2016),babyecho(DEF CON CTF Qualifier 2015), pwn200(EBCTF 2013),VillagerA(ksnctf)などです。解析する際のおおまかな流れは、まず静的解析でどの実行環境で動くバイナリか、checksec.shを実行してセキュリティ機構を確認しどの脆弱性の可能性があるかを確認した後、実際に実行して挙動をみたり、デバッガを使ってアセンブラレベルで動作を追って脆弱性を探します。そして脆弱性が見つかったらその脆弱性に対して攻撃を仕掛けて行くというのが流れです。もし解析してる途中に自力でもどうしようもなくなった場合はネットでwriteupを探しそこから少しヒントをもらい、また問題に取り組むようにしています。 CTFでバイナリを解析していると、脆弱性などのセキュリティ面の知識以外にも発見があります。例えば、脆弱性を見つけることができて、バイナリの挙動が分かっても攻撃の標的がわからないことが多々あります。先ほど挙げた中で言いますと、greetingという問題ではGOT Overwrite攻撃ができる脆弱性があるのですが、僕はその攻撃を使ってどこを攻撃してよいかわかりませんでした。そこで詰んでしまいwriteupを見るとプログラムはmain関数が始まる前と終わった後でコンストラクタ、デストラクタという領域にジャンプしているということを知りました。当時の僕は恥ずかしながらコンストラクタ、デストラクタの存在すら知らなかったので非常に新鮮な気持ちになったのを覚えています。このようにPwnをしているとセキュリティの知識だけでなくプログラマとして役立つ周辺知識も身につくのでとても楽しいです。 僕の通っている大学の同じ学科の周りの人にもとても技術力の高い人はいますが、低レイヤーに興味のある人がなかなかいません。僕のセキュリティキャンプへの参加を志望する理由の一つもそこにあり、低レイヤ―に興味がある仲間を作ってCTFプレイヤーとして、エンジニアとして切磋琢磨できる仲間が欲しいと考えています。

Q3:

自分のSNSアカウントを載せます。

Q4:あなたが今年のセキュリティ・キャンプで受講したいと思っている講義は何ですか?(複数可)

またそれらを受講したい理由を教えてください。

①E4 Linux故障解析入門 Q1でも述べたのですが、自分は自作OSを作ったことがあります。しかしそれは本当にOSの基礎部分であり、実際に世に出ているOSとは比べ物にならないと思います。僕はこれから、組み込みOSなどの違うアーキテクチャのOSや、何かの動きに特化したOSなども開発していきたいと思っているので、実用的なOSはどのようなメモリ管理、システムコールなどなどを実装しているのかはとても興味があるのでLinuxシステムの動きの理解を深められそうなこの講義はとても惹かれるものがあります。

②E5 Linuxカーネル脆弱性入門  大方①のE4と同じ理由です。講義名がもう面白いです。個人的にはyoutubeに上がっている講師の小崎さんのmallocの解説動画がとてもわかりやすくおもしろかったので、小崎さんの話を直接聞きたいなぁという願望もあります。

③A7 本当にわかる SpectreとMeltdown  自分はQ5の課題のSpectreを選んで調べました。しかし英語のテキストを読んだりしたので、恥ずかしながら理解ができてない部分も多くあると思います。例えば攻撃の例がいろいろ挙げられているのですが、実際のクラウドサービスなどで攻撃ができるとしたら、そのサービスにはどういう条件が必要なのかなどの理解が追い付いていないです。実際の攻撃例は動画で見たことがあるのですが、自分の手元で再現はしたことがなく(再現のプロセスがわからない)、この講義では自分の手を動かしながら動作を追うことができるということで、よりこの脆弱性に対する理解、カーネルの動きに対する理解が深まると思い興味を持ちました。

Q5:

 僕の調べた脆弱性は最近話題になっていたMeltdownとSpectreのうち、Spectreです。  SpectreはCPUのキャッシュを用いたサイドチャネル攻撃と、投機的実行による脆弱性です。キャッシュを用いたサイドチャネル攻撃とはCPUがデータにアクセスする際にキャッシュに乗っているデータとRAMに乗っているデータではキャッシュに乗っているデータのほうが速くアクセスできることを用いた攻撃です。一方で投機的実行は、CPUの命令は命令によって実行時間が違うので、実行時間が遅い命令を待たないで先に次の命令を実行してCPUの動作を高速化する技術です。これがどういう脆弱性を生むのか、この課題の解答の最後にのせた参考資料①の資料からサンプルコードを引用します。

struct array {

unsigned long length;

unsigned char data[];

};

struct array arr1 = ...; / small array */

struct array arr2 = ...; / array of size 0x400 */

/ >0x400 (OUT OF BOUNDS!) /

unsigned long untrusted_offset_from_caller = ...;

if (untrusted_offset_from_caller < arr1->length) {

unsigned char value = arr1->data[untrusted_offset_from_caller];

unsigned long index2 = ((value&1)*0x100)+0x200;

if (index2 < arr2->length) {

unsigned char value2 = 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ビットずつなのかということです。参考資料②のスライドにもあるように配列(上のコードではarr2)の大きさを0xff(キャッシュのページサイズ)にしてしまえば、1度上記のサンプルコードのif文のブロックを実行すれば、arr2->data[x0x100]のxを0から0xffまで繰り返してアクセスの実行時間を計測すれば済みますし、何回もキャッシュをフラッシュする必要もないのではないかと思いました。しかし少し定量的に考えてみると理由が分かってきました。提案した方法だと1バイト読みだすのに最悪の場合で0xff+1回、すなわち256回ループを回すことになります。一方参考資料①のサンプルコードでは1ビットずつ読み出すので、キャッシュフラッシュとif文のブロックを繰り返すことになりますが、1バイト読みだすのに必ず8回のループで済みます。前者の方法ではvalueの値が8以下の時に後者の方法より速くなりますが、0xffまであるうちの8以下なので実行の合計時間が短くなるとは考えにくいです。ただ大きなメモリ空間を攻撃するとなったときには、0x00が続く空間がたくさんある可能性も多くなってくるので、その時にはもしかしたら有効かもしれません。それでも1ビットずつのほうが安定したスピードでリークできますし、実行時間のおおよその計算もしやすいので実用的かと思いました。 気になるSpectreの攻撃例と対策ですが、Spectreは現実的な攻撃ではないようです。攻撃ができる条件として、攻撃対象となるマシン上にもともと存在するコードか動的生成したコードをもとに命令ポインタを操作する必要があるので、それは厳しいとされています。 もし攻撃が可能であった場合はそのコードが動いているプロセスのアクセス可能なメモリ空間を理論上すべてリークできるようで、カーネル内で実行すればカーネル空間もリークできるので理論上物理メモリすべてリークすることができると言われています。  それではなぜ脆弱性が見つかって早急に対策が急がれたMeltdownではなくSpectreを選んだかというと、これは完全に僕の主観なのですがSpectreのほうが技術的に面白いと思ったからです。キャッシュに乗っているか否かをアクセス時間で判断し、標的のメモリビットを間接的にリークするアイデアにとても感心しました。電磁波解析や電磁波解析など、専門的な知識がないと根本的な理解ができないロマンチックなサイドチャネル攻撃があるなか、このサイドチャネル攻撃はキャッシュの存在を理解していれば理解できるのもとても魅力的でした。

参考資料 ① https://googleprojectzero.blogspot.jp/2018/01/reading-privileged-memory-with-side.html

https://speakerdeck.com/sat/tu-jie-dewakaruspectretomeltdown

Q6:

まずmain関数が始まる前にコマンドライン引数の個数(C言語でいうmain関数の引数のint argc)がrdi(edi)レジスタに、コマンドライン引数の文字列が格納されている番地の配列のアドレス(先述のchar **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などのジャンプ命令のオペランドを命令番地でなくラベルにしたことに加え、コンパイルが通らなかった箇所を何点か変更したので以下に列挙します。

0x4003e6: →jne j3

0x4003f3: →cmp byte[rdx-0x1+rax], 0x0(cmpbだとコンパイルエラーだったため)

0x4003f8: →jne j1

0x400402: →jne j3

0x400414: →movzx edx, bytersp-0x10+rdx

0x400425: →jne j2

0x400427: →xor rax, main

0x40043e: →movzx eax, al(movzblだとコンパイルエラーだったため)

尚cmpbやmovzblのオペコードの変更はコンパイル後、機械語で同じになっていることを確認しています。  ファイル名をsec.asmとしコマンドライン

$nasm -f elf64 sec.asm

$gcc -o sec sec.o

$radare2 -d ./sec r3En1gNe

を入力しradare2でステップ実行していくとmain関数の最後のret命令のときのraxレジスタの中身が0になっていることが確認できました。念のためコマンドライン引数を1文字変えてみるとraxレジスタは1になっていたので、”r3En1gNe”が特定の文字列です。  CTFを少しかじっていたこともあり、アセンブラはすんなり解読でき、答えの文字列も早い段階で導きだせました。わからない命令はsetneぐらいだったのでそこは検索して調べました。最初のほうのediとrsiのレジスタの用途を知らなかったのですが、読んだ感じおそらくコマンドライン引数の個数とアドレス番地が入っていると予測でき、検索してみると予想通りでした。一番時間がかかったのがアセンブリプログラミングで、自作OSの際に少しやっていた経験はあったのですが、なかなかコンパイルが通らず、上に書いたように何度か試行錯誤し命令を少し変えたりしてコンパイルを通しました。答えの文字列まで一回でたどり着けたのもとても嬉しかったです。答えがreengineをいじったものかなと思ったのですが、iとgが入れ替わっているのでそこを指摘するまでの課題なのでしょうか。

(追記)engineerを逆から読んだものでしたね、、、クソイキってしまいました。

実行環境 Ubuntu16.04 ,gcc5.4.0,nasm version2.11.08, radare2 2.4.0

参考にした資料

https://www.simple-teq.net/assembly/assm-14.html

https://qiita.com/MoriokaReimen/items/b316a68d76c1eafa18f8

Q7:

1. USBに使用されるファイルシステムの種類を調べるとFATかNTFSがメジャーであることが分かりました。そこでBzというバイナリエディタで中身をみてみると最初のバイトが0xEB 0x52 0x90の形式をとっていて、これはNTFSファイルシステムのヘッダであることが分かりました。ちなみにFAT16だと0xEB,0x3C,0x90、FAT32だと0xEB,0x58,0x90であることも分かりました。オフセット1が壊れていた場合FATの可能性もあるのですが以下に続く課題でNTFSであると仮定して進めていくと、マウントできたのでファイルシステムNTFSであると分かります。

2&3. まずはバイナリエディタでイメージファイルの先頭付近にあるBoot sectorのバイト列を参考資料①と②を見ながら修正をしていきます。まずオフセット3からの4バイトは0x4e,0x54,0x46,0x53に修正しました。このオフセットは3~0xaまでがOEM IDというメンバに位置していて、修正するとASCIIコードで”NTFS “となります。ここはおそらくファイルシステムを判断するのに使用すると思われます。オフセット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が位置するオフセットは先ほど除いたBoot sectorから計算することができて、今回の場合だとBoot sectorのオフセット0x30からの8バイト分の$MFTの開始クラスタ番号であるLogical Cluster of $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を取り出すことができました。

4. 上記の過程で取り出した二つのpdfファイルを、実行環境をUbuntuに移し

$sha256sum  

とコマンドを打つと

iir_vol37.pdfのハッシュ値

5d62f82532d62645cd67b312546a599d90d15ea09c5ee5dbd61c5d90fc945ad3

iir_vol38.pdfのハッシュ値

86eb9f1fe34087f709a659b051d4d06930078c72d420683f2bf89455f31eb55f

が得られました。念のためpythonのhashlibライブラリでそれらのファイルのsha256のハッシュ値を計算したところ、結果が一致しました。

実は課題提出期限の8時間くらい前までNTFSではなくFAT16FAT32のどっちかだと仮定して進めてきていました。それはFATとNTFSのファイルの先頭(オフセット0~2)が同じようなジャンプ命令を表すバイト列であることを考慮せず、FATの資料を見て、これはFATだと決めつけていました。しかしどこのオフセットを見ても0xf8,0xffのようなFATテーブルの先頭のバイト列が見つかず、さらにFAT16か32かの区別さえつかず色々探る日が何日も続きました。なのでNTFSである可能性に気づき、調べてNTFSであることを知ったときは感動した感情が一瞬現れた後、これ提出間に合うのかという焦りが現れ、ヒヤヒヤした時間を過ごすことができました。あっているかはわかりませんが、あきらめずに調べ続けて自分なりの答えが出せて満足しています。

実行環境

Ubuntu16.04, sha256sum 8.25, python 2.7

Windows10, Bz version1.62, FTKImager 4.1.1.1

参考にした資料

https://qiita.com/kusano_k/items/45b0a86649aabb8040ff

http://www.writeblocked.org/resources/ntfs_cheat_sheets.pdf

http://free.pjc.co.jp/fat/index.html

http://elm-chan.org/docs/fat.html

まとめ

自分は冬休みにとある長期インターンに応募しまして、そこでは当時話題になっていたMeltdownとSpecterの英語の論文を読んで脆弱性を理解するという課題を与えられたのですが、それがQ5に役立ちました。その時は1日オフィスで7時間ぐらい英語の論文を読まされて、「何このクソインターン」「自分何してんだろ」と思い、3日でインターンを辞退したのですが、それが今になって役に立つとは思いませんでした。(ちなみにそれがトラウマでそれ以来インターンには参加してません。またスキルアップのためにインターン参加しようかな。。。) Q6は普段趣味でCTFのPwnを解いているのでアセンブリはすらすら読めました。多分正解していると思います。 Q7は課題締め切り当日まで残っていた課題で、用紙にも書きましたが、1週間くらいFATファイルシステムと思い込んで進めていて、締め切り日に気づいて急いで調べなおしました。あの時の心拍数は尋常じゃなかったです。諦めないって大事ですね。

去年も応募したのですが、その時は全く歯が立たず、文字通り、とりあえず応募したような人間だったのですが、振り返ってみると自分がセキュキャンに通る実感がまだ湧きません。よくこれで通ったなぁという気持ちです。プロの皆さんはセキュキャンがゴールではなくむしろスタートのようなことをよく仰っているのでこれからも精進したいと思います。

最後になりますが、僕にCTFを教えてくれたり、この界隈に引き込んでくれた、同学同学科の界隈のプロの同期(一応名前は伏せておきますが、カンファレンスで登壇するような超プロ)には感謝してもしきれません。本当にありがとうございます。

BeginnersCTF writeup

初投稿です。5/26~27の24時間開催されていたCTFに参加しました。友達と二人で参加したチームhiYは最終的に498point,193/844位 でした。チームとしては8問解けて、その中で自分の解いた問題のwriteupを書いていこうと思います。

[warmup]condition(pwn)

Host: pwn1.chall.beginners.seccon.jp

Port: 16268

が問題サーバで、実行バイナリも配られたので早速手元で実行すると

Please tell me your name...

と出てくるので適当に文字を打ち込むと

Permission denied

と返ってきます。radare2のデバッガモードで中を見ると0x40078fからgets関数の流れがあり、入力を格納する開始番地が[rbp-0x30]であることがわかります。

そのすぐ後ろでcmp命令でdword[rbp-4]と0xdeadbeefを比較して等しければflagを出力するputs関数にいくので[rbp-4]をその文字列にすればいいとわかります。リトルエンディアンに注意して

$python -c 'print("A"*44+"¥xef¥xbe¥xad¥xde")'| nc pwn1.chall.beginners.seccon.jp 16268

でflagが出ました。

[Warmup] Simple Auth(Rev)

配られたファイルを実行すると

Input Password:

と出力され適当に文字列を返すと

Umm...Auth failed...

と返ってくるのでまた中身を見ます。

main関数の流れはprintf後、scanfで入力したあとauth関数というところに飛んでいます。auth関数の返り値が1だとflagが出力されるようなので(というか入力した文字列番地を"Flag is %s"の引数としているので入力そのものがflag)早速auth関数の番地まで飛んで見ると、スタックに1文字ずつ律儀に何かを積んでいます。"ctf4b{"から始まることからこれがflagだと推測できます。radare2のデバッガモードで見ていたのでアセンブラの横にコメントで対応するASCIIを表示してくれました。

[Warmup] plain mail(Misc)

pcapファイルが渡されたのでwiresharkで中身を見ます。

$wireshark packet.pcap

パケット一覧のウインドウで右クリックからの追跡>TCPストリームを押すと通信の内容が見えます。右下のストリームというボタンでストリームを変更しながら中身を見ていきます。ストリーム0では、「秘密の情報を送るよ。まず暗号化したファイルを送って、次にパスワードを送るよ」的な内容が書かれています。ストリーム2を見て見るとbase64エンコードされた文字列が並んでいるのでこれをあとで復号するためにテキストファイルに落とします。ストリーム4ではパスワードが書かれています。

復号化を行うのにはpythonを使い、テキストファイルからzipファイルに復号しました。ここで解凍して見るとパスワードを求められるので先ほど得たパスワードを使うと無事解凍でき、中にはflag.txtが入ってました。

[Warmup] Welome(Misc)

今回のサービス問題。公式IRCに適当にログインするとflagが拾えます。

てけいさんえくすとりーむず(Misc)

$nc tekeisan-ekusutoriim.chall.beginners.seccon.jp 8690

上のコマンドを打つとサーバーに繋がり簡単な3桁以下同士の四則演算(割り算はなかった)を100問させられます。最初何問あるか知らなかったのでコード書くのだるいなぁと思い暗算してたのですが最初に100問って出てたのですぐやめました。 手計算してたら時間がないです。なのでpythonで文字列受け取って空白文字で分割して計算するコードを書きました。てけいさんとは。

-----------以下は手をつけたけど解けなかった問題です-----------------

Find the messages(Misc)

ディスクイメージが渡されたのでマウントしようと思ったのですが、あまり経験がないものでかなり手こずりました。結局開始セクタ番号などを調べてオプションをつけながら

$sudo mount -o loop,offset=1048576 disk.img /mnt/diskimg

でマウントでき/mnt/diskimgまで移動するとmessage1~3とlost+foundというディレクトリがありmessage1にはbase64エンコードされたテキスト、message2にはマジックナンバー8バイトが壊れたpngファイルがあり、それぞれがflagの断片となっていました。message3には何もなくおそらくファイルが壊れていて、その情報がlost+foundに入っているのだと予想して、fsckコマンドやら色々試したのですが最後までわかりませんでした。solve数が多かったので解きたかったです。自分のググり力の低さを感じました。

BBS (Pwn)

スタックオーバーフローがありripが取れたところまでいったのですが、それ以降分からず。要復習です。

(まとめ)

SANS Netwars2017ぶりにチームでの参加となりました。自分は半年以上前くらいから国内 CTFに数回参加したことがある程度で、毎回だいたいサービス問題含め4~5問解ける程度でしたが、今回は友達もいるおかげでチームで8問解けました。友達はCTF初参加らしく、前日に困ったらこれ読め的な感じで僕がハリネズミ本を渡してたくらいなのですが、僕の苦手なcryptoを2問、webを1問解いてくれて大活躍でした。しかもチームで一番得点高いのはその中のcrypto問だったので僕の人権は失われました。結局今回も自分は5問しか解けず進歩がなく辛かったですが、とても楽しかったです。また誘って参加したいです。少し落ち着いたらプロのwriteupを見て復習していこうと思います。

初投稿でソースコードとか書きづらい書式を選んだまま進めていったので次からはqiitaに投稿するなり、見やすく書いていこうと思うのでご容赦ください。