過密です

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

CyberRebeatCTF crackme writeup

はじめに

CyberRebeatCTFに参加したので、その中で僕の解いたBinaryジャンルの問題のcrackmeのwriteupを書いていこうと思います。すでに全完してるような方がwriteupを出しているのであまり需要がないかと思ったのですが、ここでは手順を事細かに書いていこうと思います。

使ったツールはradare2です。ダウンロード方法や設定などはハリネズミ本に、使い方等はググれば先人の良ブログがたくさんあるのでそちらを参照してください。僕もまだ全然使いこなせていませんが、基礎操作さえできれば問題は解けるようになっています。

crackme(難易度100)

fileコマンド
f:id:kam1tsur3:20180909173115p:plain
動的リンク、not stripped、やった!と思ったのですが、アーキテクチャがARMでした。

x86系じゃないアセンブリはあまり読んだことがないのですが、、、とりあえずradare2に通してみます。
f:id:kam1tsur3:20180909173124p:plain
not strippedなのでmain関数に飛び、visualモードに変更しpを入力しアセンブリを表示(visualモードから戻るにはqを入力)。ちなみにvisualモードでは画面のスクロールはvimと同様jとkのキーで行います。main関数はこんな感じでした。
f:id:kam1tsur3:20180909174259p:plain

うーん何となくしかわからんなぁ。ということでARMアーキテクチャでググり、各種命令の意味、各レジスタの名称、引数の渡し方などを軽く理解します。ARMでは第1引数から順にr0,r1,r2...と格納しているみたいです。またpcレジスタx86系でいうeip)が現在の命令の2個先を指していることがわかりました。それを踏まえてもう一度逆アセンブルを眺めましょう。

気になるところは0x10574から比較分岐を行なっているところですね。r3 > 1ならジャンプをしています。前の命令も見てみると[fp-0x8]にargcを、[fp-0xc]にargvを格納していてるので0x10574ではコマンドライン引数によって分岐しています。コマンドライン引数が1以下だと0x1057cから続くエラーメッセージを出力してmain関数の最後に飛んでいます。コマンドライン引数が2以上だと0x105a0に飛んでいますね。

0x105a0からの流れではcheck(argv[1])を行い、その返り値(r0に格納されている)が0だとputs("Correct")、非0だとputs("Wrong...")を行いmain関数を終了しています。main関数はこんなところでしょうか。大事なところはコマンドライン引数の値をcheck関数に渡して返り値を0になるようにすればいいということですね。

visualモードでqを入力してcheck関数に飛びまたvisualモードに戻ります。
f:id:kam1tsur3:20180909181918p:plain

check関数はこんな感じです。

f:id:kam1tsur3:20180909182002p:plain

main関数が読めればcheck関数も読めると思います。0x1050cからの流れからもわかりますが、[fp-0x8]をカウンターとして使っていて、比較するobj.flag_encrypted_lenがさす中身は0x17であるので0x17回ループを回しています。0x104dcから始まるループの中ではloc._d_15というポインタ型の変数のさすアドレスから1文字ずつ取ってきて0x104ec〜0x104f4の処理を行い、また同じ場所に戻しています。ループが終わったら操作したloc._d_15の中身と引数である、main関数のargv[1]をstrncmpで比較しその返り値をそのままcheck関数の返り値としています。

obj.flag_encrypted_lenとかloc._d_15の内容ってどこ見たらわかるの?という人はradare2の赤いコメントを見て見ましょう。0x104dcや0x1051cの横のコメントをみるとloc._d_15の中身は0x21030、obj.flag_encrypted_lenの中身は0x21048であることがわかります。

えーでもこれだとツールのコメントなかったら解けないじゃんかという方向けにもう少し補足を加えます。loc._d_15で考えると、0x104dcと0x104f8の命令を見てください。同じ命令なのにも関わらず機械語だと0x70209fe5と0x54209fe5と1バイト目が違うことがわかります。ここがポイントで、最初の1バイトは現在のpcレジスタの値に加えるオフセットのことです。なので0x104dcの時のpcレジスタの値は先述の通り2命令先なので0x104e4、これに0x70を加えると0x10554
であります。同様に0x104f8の命令の時も計算すると0x10554となります。同じ考え方をobj.flag_encrypted_lenに適用するとloc._d_15は0x10554に、obj.flag_encrypted_lenは0x10558にあるということがわかります。そのアドレスを見てみると
f:id:kam1tsur3:20180909191419p:plain
はい!それぞれradare2のコメント通りの値が入っていますね!(リトルエンディアンを忘れずに)
どちらもポインタ型として使われているのでそれらのさすアドレスに飛んで見ましょう(戻ってs 0x****0で飛べます)
f:id:kam1tsur3:20180909191736p:plain
ありましたぁ。ここはデータが格納されているだけなので、横の逆アセンブルは気にしないでくださいね。loc._15_dの指す文字列とループの処理がわかったのでCでコードを書いて見ます。

/*crackme.c*/
#include <stdio.h>

int main()
{
    char en_flag[23] = {0xb0, 0xa1, 0xb0, 0xa7, 0xb5, 0x88, 0x9b, 0x96,
                        0x9f, 0x9f, 0x9c, 0xac, 0xc7, 0x81, 0x9e, 0xac,
                        0x84, 0x9c, 0x81, 0x9f, 0x97, 0xd2, 0x8e};

    int i;
    char tmp;
    for(i = 0; i < 23; i++){
        tmp = en_flag[i];
        tmp ^= 0xc;
        tmp ^= 0xff;
        en_flag[i] = tmp;
    }
    printf("%s¥n", en_flag);
}

f:id:kam1tsur3:20180909193209p:plain

わーい

まとめ

  • 本当はBinary問題3問全部writeup書こうと思ってたんですが、個人的に書きたかったcrackmeから書き始めたら疲れたのでこれだけにします。もしかしたら追記するかもです。
  • 同学科の同期と3人で参加しました。チームとしては27位でBinary以外はほとんど触ってないので2人がよく頑張ってくれました。
  • webわからんすぎるのはどうにかしたいですが、何か得意なジャンルを作っておきたいのでもう少しrev,pwnを解き続けたいと思います。このレベルなら解けるようになってきましたが、大きい大会となるとwarmupすら解けないのはしんどいです。
  • キャン18の参加者が何人か全完していて、はえ〜という気持ちです。
  • 問題のレベルが初心者向けが多く楽しめました。運営の方ありがとうございました。

セキュリティキャンプ 全国大会2018に参加しました

キャンプ中にも情報発信が大事だと学んだので早速ブログに残します。来年以降の参加を検討してる方や、キャンプの関係者に向けて、一参加者の感想として参考になれば幸いです。

僕が参加したのはAトラックの脆弱性マルウェア解析トラックです。

応募用紙

kam1tsur3.hatenablog.com 応募用紙が見たい方はどうぞ。原文を載せているので非常に見づらいですが勘弁してください。黒歴史なのは重々承知ですが、これも成長の過程だと割り切って後悔公開してます。

事前準備

講義によっては事前課題がありました。テスト期間とかぶったりとやっているときは大変だと感じたのですが、いざ講義を受けてみると事前課題があった方が理解が深まったので、課題はありがたいものだと思います。

あと名刺は必須ですよ。

1日目

到着



ぼっち参戦だったので、端っこにいたら早速名刺を持って話しかけてくれた方がいたので本当に安心しました。

f:id:kam1tsur3:20180821003331j:plain:w500

唯一撮り納めたご飯なので載せさせてください。

セキュリティ基礎

セキュリティだけでなく、いろんな場面で発生する「失敗」についてグループで討論しました。内容が抽象的であったこともあって最初はなかなか話が進まなかったのですが、具体例を交え話していくと議論が進んでいったように感じました。

特別講義(1)

OSCの方のOSCの紹介、情報発信していくことの大切さについてのお話を聞きました。

特別講義(2)

法律の観点から見たニュースでも取り上げられている仮想通貨の話や、未成年の犯罪などについてのお話を聞きました。唐突な野獣先輩ネタにツボってしまい僕は最前列で5分くらい笑いをこらえるのに必死だったので、それ以降覚えていません(は?)。挙手でどのラインからが刑法に触れるのかいうアンケートもしたのですが、僕を含め多くの参加者の人も理解が曖昧であったことが印象的でした。

LT大会

本当に面白かったです。改めてここはすごい場所なんだと実感した瞬間でもありました。中でもWiiフィボナッチ数列計算させてた方やCodeGolfの方は内容も話し方も面白く、僕もいつかあのようなユニークなLTができるといいなと思いました。

2日目

ビュッフェ形式の朝食のフレンチトーストが絶妙すぎて2斤くらい食べました。

AトラックではA1~A3までが一つの講義でした。

A1~3 インシデントレスポンスで攻撃者を追いかけろ

事前課題が7週間に渡って出された講義です。内容は、攻撃にあった社内ネットワークのマシンのイベントログ、コマンド実行履歴、動的解析から攻撃者をあぶり出すというもので、事前課題ではWindowsのシステム、ツール群を学習し、講義ではその内容を確認するようなCTFを行いました。あまりいい成績は残せませんでしたが、とても面白かったです。マルウェアに感染したマシンのイメージファイルのログがちゃんと時系列に沿っていて、資料がとても作り込まれていたものでした。話を聞くと1ヶ月くらいかけて実際に攻撃を行いそのイメージファイルを取っていたそうです(逆に後からログを作るのは難しいんだとか)。

3日目

特大イベント発生

尊敬する方にエレベーター内で遭遇しステッカーをもらっちゃいました。自分は名刺を持ち歩いてなかったので渡せなかったのが後悔です。

3日目から選択講義が始まりました。

A4 IN-DEPTH STATIC MALWARE ANALYSIS

講義では主にIDAを使った静的解析を行いました。静的解析の際のIDAの便利機能などの紹介や、マルウェアの中で呼び出される関数が何のWindowsAPIを呼びどういった挙動をするのかなどを調べていきました。個人的にはエンコードされたファイルシステムの文字列をデコードする関数を、xorが使われる、呼び出される回数が多いといった特徴から特定し、実際にデコードする様子が見れたのが面白かったです。

E5 Linux カーネル脆弱性入門

とても有名なLinuxカーネル開発者の講師の方による講義でした。Linuxシステムコールの基礎、DirtyCow、slabを利用した特権昇格についてのお話を聞きました。正直この講義が一番難しかったです。それに加え、情けないことに環境設定の段階でミスってしまったので講義中には用意してもらった課題は終わりませんでした。講義ファイルを持ち帰って復習しましたが、他人に説明できるかと言われると怪しいレベルの理解なので、また見返す必要があります。自分の弱さを痛感したのと見学者がめちゃくちゃ多かったので、すごい人の講義を受けているのだなと肌で感じました。

4日目

朝がつらくなってきて、朝ごはんに遅れたらフレンチトーストが残り僅かでした。。😭😭😭

D6 組込みリアルタイムOSとIoTシステム演習 ~守って!攻めて!ロボット制御バトルで体験する組込みセキュリティ~

GR-PEACHというハードウェアをhttp通信によってブラウザから操作するという内容です。事前課題では基礎操作を一通り学習しました。当日は1チーム2、3人の計5グループに分かれて競技を行いました。競技はGR-PEACHを操作して箱を持ち帰る、搭載されたカメラで写真をとる、他人のロボットの撮った写真を盗むなどがポイントになるルールで、競技は2回に分けて行われました。僕のチームの他二人はロボットに精通した方、ハニーポットで遊ぶネットワークの方と強いお二人だったので、攻撃と防御は二人に任せて僕はパイロットになりました😊。パイロットをやっていると攻撃されてるときのブラウザの処理が激重になるのが実感できました。

僕たちのチームはまずhttpのポートがデフォルトの80番になっていたのを変更したことと、2回目の競技では同じアクセスポイントを使用するという縛りがなくなったので、チームのPCをアクセスポイントとし攻撃の対策をしました(後者は結局ロボットとの通信がうまくいかず、共通のアクセスポイントを使用しました)。競技中はarp-scanで相手チームのロボットのIPを割り出し、nmapでhttpポートを探しひたすらDoS攻撃をするというもので、実際正常に動いているロボットは僕のチームを含め2つくらいしかなかったのでうまく攻撃ができていたのだと思います。結果はちゃっかり優勝してしまいました。

f:id:kam1tsur3:20180821191932j:plain:w400

これは僕たちのロボット



A7 本当にわかるSpectreとMeltdown

お正月に世間を賑わせたCPUの脆弱性についての講義でした。はじめに脆弱性についての説明があり、そのあとは実際にSpectreのPoCの一部が穴埋めになっているので、そこを自分で考えて埋めて、手元で動かしてみるというものでした。講師の方もおっしゃっていたのですが、脆弱性2のBTBがとても難しく、何回かスライドを読んだのですが、やろうとしていることはわかるのですが、該当するPoCの部分は正直理解できていません。最終的には講師のかたに答えを教えてもらい動かしてみると、読めないはずの領域が、ところどころ抜けていたもののメモリリークができました。個人的にはこの講義が一番楽しかったです。サイドチャネル攻撃ってなんかヤバい雰囲気があって魅力的ですよね。講師の方にあとで話を聞くと、世に回っているPoCはもっと複雑で難しいらしく、受講生のためにわかりやすくなるように色々手を加えて頂いたそうです。僕たちは本当においしいとこだけ頂いたんですね。ありがとうございます。

5日目

卒業

集中コースはそれぞれのトラックから代表者が成果発表を行いました。ジュニアゼミの中学生も発表していてみなさんとても面白かったです。印象的だったのはやはりJSエンジンを作った方とセルフコンパイラを作った方ですかね。難しい言葉を使わず、図やデモで説明していたのであの場にいた誰もが楽しめた発表だったと思います(何様)。

プレゼント

いろんな協賛企業の方からたくさんの記念品をもらいました。全部載せるのはしんどいので代表して一つ載せておきますね。

f:id:kam1tsur3:20180821200335j:plain:w400

これは大変なモチベーションです。

まとめ

本当に楽しい5日間でした。参加前は技術的にも不安で、友達できるかな。。。とか考えていたのですが、いざ参加したらとてもいい雰囲気のおかげで不安はすぐに消えました。反省点としてはいい意味で講師を裏切れなかったことです。例えばA1~3の講義のCTFでは誰も解いていない問題が多く残っていて、講師陣も「どんどん解いて〜」と余裕だったことや、D6の講義では講師の方はアクセスポイント乗っ取ることも想定してhtmlファイルを置いていたそうなのですが、結局誰もそこまで到達しなかったことなど、講師陣を驚かすようなことができたら気持ちよかっただろうなという感じです。まあこれは完全に自分の技術力、アイデアが乏しいのが原因なので、これから圧倒的成長をしていきたいです。

セキュリティキャンプ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に投稿するなり、見やすく書いていこうと思うのでご容赦ください。