つけじょにーのすぱげていコード

主に、競技プログラミング、セキュリティのお勉強の際に書いたすぱげていコードを書き込みます

SECCON大阪参加記

10/2にSECCON大阪に行ってきました。 バイナリおいしいというチーム名で参加させていただいて、僕は何も貢献できていなかったのですが、優勝しました。

当日までの準備

SECCON大阪のコンセプト、どうもangrで自動化っぽいんだよなぁと思っていたので、angrでシンボリック実行をやってみる - ももいろテクノロジーの記事を拝見させていただいたり、angrのdocumentを見たり、某キャンプの資料を見たりとして、勉強しました。

当日

まず、競技説明を聞いてビビりまくります。

過去最多問題数 21,672 !!?

テーマ(easy, hardなどの難易度やBOF、Backdoor探しなどテーマで分かれています)ごとの大問が4つあって、大問それぞれでバイナリが一定時間ごと切り替わります。 中には1秒ごと切り替わるものまで・・・ さすがに、セッションが切れていなければその問題は有効となる(1秒ごと切り替わるというのは、1秒経過後、セッションが切られた状態からバイナリを取得しに行こうとすると新しいバイナリになってしまっているということ)。

こういった際、やはり重要だと思われるのはある大問で降ってきた複数のバイナリで異なる箇所はどこか?だと考えたので、そこを念頭に置きながらバイナリをもぐもぐしてました。

するとチームメイトのうさぎさんがスクリプトをぱぱっと作成(!?)し、動かしてみると時折正解するっぽい・・・?

僕のイメージなのですが(間違っている箇所が含まれるだろうということです)、angrではまずELFバイナリを指定してProjectを作成し、path_group(pathというのは、シンボリック解析を行う過程で実行するバイナリの実行状態だと思っています)を作成するところはおおよそ共通して行う部分です。 その後、Constraint(制約)を指定してexplore(探索)します。 ここで、exploreには到達したいゴール、到達したくないはずれのルートを指定することが多いのですが、当日は何も指定せず、全探索で実行していました。

すると当然たくさんの実行パターンがあるのでpath_groupには複数のpathが入ることになるのですが、「Hello World」という文字列が表示された場合は失敗だとわかっている(攻撃が成立しなかった場合の正常動作)ので、そのpathを取り除き、攻撃が成立したと考えられるpathのうち最初に見つかったものを成功と考え、出力からフラグと思しきものを取得して、スコアサーバにSubmitするという感じでした。

僕は3並列でこのスクリプトをガンガン回していた(というか僕ができることってこれくらいだったのでは・・・?泣)のですが、割と安定して競技終了まで動作しており、点数を稼ぐことができたようです。多分。

辛かったこと

難読化辛い・・・ やたらめったらjmpしまくっていたり、and,orなどの論理演算で複雑にしてたりするバイナリがあって、泣きそうになっていました。 バイナリが切り替わるというのは前述の通りなのですが、ロジックが結構切り替わるものもあったらしく、難しかった・・・

思ったこと

今回僕は全然貢献できていないのですが(バイナリおいしいチームが前に行って賞状もらう時も足がガクブル状態になるくらい何もできてないです)、バイナリをちゃんとたべれるようにしたいなと思いました。 katagaitai勉強会が低レイヤーに興味を持ったきっかけで、勉強会後はモチベーションが上がるものの、頭が弱くて解けず、結局あまり取り組めていませんでした。 ですが、セキュキャンをきっかけにまた興味を持ち出して、それが今回参加する動機になりました。

SECCON本選に向けて

貢献できるようにしたい。 そのためにもCTFに積極的に出て精進する! 今まではWeb, Networkが主だったけど、Pwnもできるようにしたい。

セキュリティキャンプ全国大会 2016 参加記

8/9 ~ 8/13に行われたセキュリティキャンプ2016に参加してきました。
思い出しながら、書いていこうと思います。

講義の内容は各々異なります。
僕が受講した講義は以下のとおり

        • -

1-A HTTPプロキシ発展
2-B 謎マシンでNetBSDのクロス開発体験
3-A Webアプリケーションの脆弱性の評価と発見
4-B ロボットに使用できるミドルウェアとハードウェア制御
5-C サーバに対する攻撃とその影響度を調べてディスカッションする講義
6・7-E インフラセキュリティブートキャンプ

        • -

・1日目

当日まで

セキュキャン前日まで、全然参加者の方々と絡めていなくて、とてもつらかったです。

「自分雑魚だし、皆さんと仲良くなれるかな・・・」
「雑魚過ぎてつかえないポーイになりそうで怖い・・・」
と考えてました。

ですが、その旨をツイットしたら、参加者の方々が励ましてくれました・・・

圧倒的感謝・・・!
f:id:tukejonny:20160815025850j:plain

当日

セキュリティ基礎

セキュキャン応募用紙提出フォームでの安全性について、各班で話し合う感じでした
ここで班のメンバーと話し合えたので、オリエンテーション的な意味も含めて良かったです!

ZENIGATAになりたくて

INTERPOLで活躍されている福森さんからお話を聞かせていただきました。
普段座っている時間が長いし、エコノミー症候群には気をつけねばと思う一方で、それほどまでに技術に対して情熱を注げる人でありたいなとも思いました。
そして、技術とは何か。人を支える技術を作っていくことが大切で、個人プレイでやっていてはダメという言葉も胸に響きました。

サイバー犯罪の実態とこれに対処するための取組

普段、情報セキュリティを勉強するとなると攻撃者視点、防御側視点に限定することが多かったのですが、警察から見たサイバー犯罪ということについて知れて、大変良かったです。
また、学んだ技術を正しく利用していくために、「どのようなことを遵守しなければならないか」ということについても知ることができました。
終始、過激な発言に笑いが絶えませんでしたw

グループワーク

未来、倫理、小規模企業におけるセキュリティ対策、回避の4テーマのうち、班ごとに1テーマ選択し、講義やヒアリングを通して学んだ内容をもとに最終日発表するという一連の流れの説明を受けました。
チームとして協力しながら取り組むの楽しい!
途中、チーム名を決めるのですが、なかなか良さげなのが思いつかず、wikipediaのrandom articleで色々見てみると、最初は有名な方のお名前みたいな面白くない(失礼)ものが出てきたのですが、唐突にミルモでポン!という中々攻めているチーム名をゲットできました。
正直私はその文字列を見てもピンとこなかったのですが、画像検索してみると、青い帽子かぶった妖精・・・・のような何かが出てきて、「あ!こいつ出てるアニメあったなぁ!」と思い出すことができました。

・2日目

講義

1-A: HTTPプロキシ発展

ローカルプロキシにはFiddlerやBurpなどがありますが、僕はBurpが好きです。
最初におさらいを行い、早速手を動かします。
みんな、Javaみを感じてオラクります。
JavaでBurp Extenderを書いてみよう!という内容で、書いた後、jarファイルにエクスポートし、それをBurpで指定してロード、使ってみるという流れです。
最後に、スマホにつなげて、スマホアプリの通信をうまく改変し、以下のようなことを行いました。
・高額商品を買えないのに買う
・関係がない人にメールを飛ばす

実際に手を動かしながら、同じテーブルの参加者の方々と協力し合いながら(ほぼ僕が聞きに行っていただけでしたが・・・)やっていくのが楽しかったです!

2-B: 謎マシンでNetBSDのクロス開発体験

事前に面白そうなマシンとかCFカードあったら持ってきてと連絡頂いていたのですが、特に何も持っていけなくて本当ごめんなさい・・・
講師の蛯原さんがたくさんマシンを持ってきてくださって、実際にデモしていただきました。
マシンって結構幅広く見ることができて、どれもコンピュータなんだな・・・と改めて実感することができました。
また、RPIにNetBSDを積み、mikutterを動かすということもしました。
ですが、別マシンでコンパイルしておいたカーネルを使おうとしたらうまく動かなくなってしまい、最後まで解決しませんでした・・・
講師、チューターの方にHelp me!しまくりながらやっていました・・・

CTF

障害復旧まで

CTF開始直後、アクセスできないなどの障害が発生していることが発覚し、序盤数十分は競技開始できませんでした。
ついこの間、比べ物にならないくらい小規模ではありますが、学内のCTF運営をした際も、一部不具合があって当日に修正したり、問題を当日追加したり・・・というようなことがありました。

開催

無事に開催されましたが、「Web問解けない・・・つらい」って感じになってました。
結果は5位。
結局チームに貢献することができず、申し訳ないばかりです・・・
より精進していくことを誓い、今後もより一層頑張るぞい!と思いました。

・3日目

この日は講義が3つあり、他の日に比べても講義終わりにはガッツリ疲れていたような気がします(笑)。ですが、その分だけ濃密で、より多くの参加者の方々と交流できたような気がします。

講義

3-A: Webアプリケーションの脆弱性の評価と発見

事前課題で実際に攻撃を行うようなことを行っていたのですが、その際に実際に攻撃ができたことがすごく嬉しくって、めちゃくちゃ楽しかったです(曖昧でごめんなさい、公開できないこともあるので・・・)。
講義は2パートに分かれます。
・攻撃
・評価

まずは攻撃から。
ペイロードをどこに注入すれば良いか探す際、ページのどこに着眼すれば良いかということについてスライドを見て学び、講義参加者で使用出来るタグ・属性・属性値、使用できないタグ・属性・属性値を洗い出していきました。エラーが起きるところまでは行っていて、少しとっかかりが見つかれば・・・!というところで時間が切れてしまいました・・・

次に評価。
CVSSという評価指標があり、これを計算するツールがインターネット上で公開されています。
幾つか脆弱性報告の例をあげて、実際にそのツールを用いて評価してみるということをやってみました。
僕はSlackのボットで脆弱性報告を毎日投稿するものを動かしているのですが、そのCVSSの指標の知見を深めることができたと同時に、脆弱性報告を自分が報告するとしたら、こんな感じに評価されるんだなということを知ることができました。

4-B: ロボットに使用出来るミドルウェアとハードウェア制御

この講義の事前課題が出ていなくて、始まるまで「僕、レベルすごい低いんだけど、講義についていけるのか・・・」とガクガクブルブルしていました。
ハードウェアの基礎のお話から始まったのですが、雑魚い自分は講師、チューターの方にHelp me!しまくってなんとか作れた・・・という感じでした。
電子回路のお話がメインでした。
普段触れていないこともあり、勉強になることがたくさんありました・・・

5-C: サーバに対する攻撃とその影響度を調べてディスカッションする講義

事前課題で攻撃されたサーバーのログ等を調査し、その結果を発表できるようにしておくということをしました。
当日は発表を行い、最後に答え合わせを行うというような形式でした。
他の方の発表を聞いて、自分のスライドの如何にまとまっていないことか・・・
「これじゃいかん」と危機感を持つことができました。
他の方の発表を聞いて、発表形式が異なっていたり、着眼点が異なっていたりして、かなり多くの知見を得ることができました。

ラックの川口さんからイチゴ大福をいただきました。
美味しかった!

f:id:tukejonny:20160815030016j:plain

グループワーク

この日は1時間しかなかったため、スライドのざっくりとしたアウトラインを仕上げるところまでで力つきました・・・

・4日目

講義

6・7-E インフラセキュリティブートキャンプ

大まかな流れとしては、AWSインスタンス立てて、SSHでアクセス(ここで詰まる)、docker-composeして動作確認、slack-echo-commandを使ってslackに投稿という感じでした。
AWSはほとんど利用したことがなく、詰まってしまい、また関係ないところで(Git)インシデントを起こしてしまい、のむけんさんに大変お世話になりました。本当ごめんなさい。二度とこんなインシデントは起こさないと強く思いました。
障害にいくつも遭遇したのですが、それによってトラブルシューティング力が少し向上したと思います
同じチームの方と協力(ここでも僕がメンバーに聞きまくってるだけですが・・・)してやれたことがすごく良かったです!

グループワーク

さて、最後のグループワークです。
それと同時に最後のヒアリングチャンスとなったため、急いで聞きに行きました。
スライドも書き進め、なんとか形になってきました・・・!

グループワーク(延長)

ちゃんと23:00には寝ましたよ?本・・・当です!!
メンバーの部屋に凸り、技術談義に花を咲かせながらもなんとかスライドを仕上げました!
マジで楽しかった!!

・5日目

グループワークの発表

CTFは全てを救う


pptxで編集したものをLibre Officeで開いたために、文字が崩れてしまっていました・・・(気合で読んでください)

技術書争奪戦

超豪華なイベント!
技術書を無料で頂けるという・・・超豪華イベントです!(2回目
ランダムに班の番号が呼ばれ、その順番で書籍を取っていきます。
書籍を提供していただいた皆様、本当にありがとうございます!
f:id:tukejonny:20160815122226j:plain

最後に

今回キャンプに参加させていただけたことで、今まであまり触れてこなかったレイヤのきっかけを得ることが出来たし、縦横のつながりを作ることもできたし、刺激をたくさん受けることが出来ました。

僕が重要だと考えていることの一つに「閉じた世界にいない」ということがあるのですが、こうしてキャンプに参加することで、自分がいかに未熟かを思い知ることができました。キャンプすごく濃密で、参加者に限らず、講師、チューターの方々からも熱気をもらうことができます。

僕は実力は全然ですが、コンピュータの仕組みを知ることが好きだし、作るのが好きだと思っています。情報セキュリティの勉強を始めてから、こう思えるようになったのですが、それも刺激あってこそのものでした。

セキュキャンのおかげでまちがいなく、最高の夏になった!

セキュリティキャンプは22歳までという制限があります。
かくいう私は22歳でギリギリで参加できた身です。
とりあえず、応募してみてはどうでしょうか。

僕は実力が無い中、気持ちでギリギリセキュキャンに参加できたと思います。
周りの参加者の方々は、自分よりも全然レベルの高い人たちばかり。だからこそ、たくさん刺激を受けられたし、今後圧倒的成長していける!と感じています。

得られる可能性のあるチャンスなら掴んでみようとトライしてみるべきです!
実力が全然ですが、来年はチューターに応募したいなと考えています。
圧倒的成長するために、がんばるぞい!

急ピッチで書いたので、もしかしたらまた変更するかも。

セキュキャンの応募用紙

この間、セキュリティキャンプに応募したのですが、なんとか選考を通過することができました!
私は今年22歳になったばかりで、これがラストチャンスだったのですが、なんとか拾ってもらえて本当に良かった・・・
今年のキャンプに参加される方々の中でも知識・技術力ともに一番無いので、必死に食らいついて、キャンプでしか得られない宝物持ち帰ってやる!!という意気込みで望みます。

参考になるのか、大分怪しいのですが、私が書いた応募用紙を晒そうと思います。
共通問題には軽く触れて、選択問題を全部晒そうかと思います。

念のため、先に言いますが、選択問題の解答が間違っている可能性があります。
その点だけ、お気をつけください
あと、温かい目で見ていただけると嬉しいです・・・

選択問題3について、UNIX V6カーネルの知識も少し使っているので、Linuxカーネルでの挙動とは異なるかと思います。

                          • -

共通問題1-1

私が作ったSlackのボットと、Jonny言語について書きました。
また、製作の際に工夫した点も書きました。
あと、私は作った作品が少ないため、普段読んでいる技術書の中でも、私のお気に入りについての熱い思いを書きました。

共通問題1-2

私が作った作品で用いられている言語やライブラリを書きました。

共通問題1-3

ブログ(ここ)やQiita、GithubなどのURLを書きました

共通問題2-1

競技プログラミングで苦悩していたお話を書きました。

共通問題2-2

自分が、情報セキュリティをきっかけに幅広い分野を学び、楽しく勉強できるようになったこと、それによってそれまで以上に成長できたことなどを書きました。

共通問題2-3

無理せず、好きなことをやっていくのが良いということ
広い分野に目を向けてみること
IT技術は結局、どれも、どこかで関連してくること
つらいのに、それでも諦めきれない自分の気持ちに気づくこと、そしてそこにはちゃんとした理由があること
などを書きました。

共通問題3-1

あとから見て気づいたのですが、講義単位で話さなくてはならないところを、トラック単位で話してしまっていました(何やってんだ俺・・・)
私は、アプリケーショントラック、IoTトラック、検知トラック、集中講義の講義を受けたいと考えていたため、これらのトラックについて書きました。
また、身に付けたいものについて書きました。

                                      • -

選択問題3

1.パソコンに電源を入れる。
2.CPUが、マザーボード上のROMに格納されているシステムBIOSを実行する。
3.システムBIOSは、サーバに搭載されたCPUやメモリ状態、外部デバイスの状態などを確認する。接続方法に問題があった際は、構成エラーとみなし、コンソールにエラー出力する。おそらく、各ハードウェアコンポーネントに組み込まれているファームウェアとやり取りが正常にできるかどうかをチェックし、構成エラーが生じているかどうかを判断しているものと思われる。
4.BIOSは、設定に従い、HDD、CDなどの先頭ブロックからブートストラップローダを実行する。ちなみに、設定ファイルの実態が/boot/grub/grub.confであり、grubの設定をいじる際には、
5.ブートストラップローダは、GRUBなどのブートローダを起動
6.ブートストラップは、初期ルートファイルシステムである初期RAMディスク(initrd, initramfs)とカーネル(vmlinuz)をメモリに読み込む。どうやら、Linux Kernelのバージョンが2.6.xxからinitrdではなくinitramfsが採用されるようになったらしい。initrdでは、ddコマンドを使って必要なサイズのファイルを/dev/zeroを入力に、initrdを出力に作成している。initrdでは、これによって出来たファイルをloopbackマウント(ddコマンドで作成された”ファイル”を、フォーマットし、あたかもブロックデバイスペシャルファイルかのように扱い、任意ディレクトリにマウントすること)する操作が必要だが、initramfsでは適当なディレクトリを作り、その中に必要なファイル群を詰め込んでcpioとgzipで圧縮するため、loopbackファイルを作る必要がなく、作成されたloopbackファイルに制限されることがないため、自由度がある。
7.ブートローダによってカーネルが起動される
8.カーネルは、初期RAMディスクを展開する

試しに、CentOS5でinitrd(Initial Ram Disk)を展開してみた。初めてまともにcpioコマンドを使ったので、「ファイルをアーカイブにコピーしたり、アーカイブからファイルをコピーしたりするコマンドだったんだ・・・」と驚きました。-iでアーカイブからファイルを抽出するようですが、いくつかのディレクトリが含まれるため、-dをつけて必要であればディレクトリを作成するようにしました。

$gzip -d initrd-2.6.18-406.el5.img
$cat initrd-2.6.18-406.el5 | cpio -id
$ tree .
.
|-- bin
|   |-- dmraid
|   |-- insmod
|   |-- kpartx
|   |-- modprobe -> /sbin/nash
|   `-- nash
|-- dev
|   |-- mapper
|   `-- ram -> ram1
|-- etc
|-- init
|-- initrd-2.6.18-406.el5
|-- lib
|   |-- ahci.ko
|   |-- ata_piix.ko
|   |-- crc16.ko
…
|   |-- sd_mod.ko
|   `-- uhci-hcd.ko
|-- proc
|-- sbin -> bin
|-- sys
`-- sysroot

10 directories, 25 files

bin/にはnashやmodprobe(カーネルに対し、モジュールの追加や削除ができるコマンド)、insmod(カーネルモジュールのロードができるコマンド)、kpartx(パーティション毎のブロックデバイスファイルを/dev/mapper/に生成できるコマンド)、dmraid(RAIDバイスの発見、設定、有効化ができるコマンド)がある。
lib/にカーネルモジュール群(デバイスドライバなど)があり、おそらく、modprobeあるいはinsmodを用いてこれらのモジュールをロードするのだろうなと予測できる。
initファイル(実行可能スクリプト)を覗いてみると、insmodコマンドを用いてモジュールをロードしている部分もあれば、procやsys、devなどのファイルシステムをマウントしている部分などが見られる。
ついでに、CentOS7でinitramfsも展開してみたが、何故かearly_cpioとGenuineIntel.binだけであり、これではとても起動できるように思えない。
binwalkによって解析すると、gzipが始まるオフセットがわかったため、以下のように進め、解凍を行った。

$ binwalk initramfs-3.10.0-327.4.5.el7.x86_64.img
DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
…
18432         0x4800          gzip compressed data, maximum compression, from Unix, last modified: 2016-02-08 19:18:12
$ dd if=initramfs-3.10.0-327.4.5.el7.x86_64.img of=initramfs-3.10.0-327.4.5.el7.x86_64.gz bs=18432 skip=1
$ gunzip initramfs-3.10.0-327.4.5.el7.x86_64.gz
$ cat initramfs-3.10.0-327.4.5.el7.x86_64 | cpio -id

モノシリックカーネルというだけあって、ものすごいファイルの量・・・。initrdとは比べ物にならない。CentOS5からCentOS7までで、これだけの量が増えるくらい変化があったんだなぁと感じる。binやsbinの中身もかなり豪華で、systemdが採用されているからsystemctlやjournalctlが入っていたりしており、initファイルはsystemdへのシンボリックリンクとなっていた。起動手順はおそらくそこまで変わっておらず、initrdにてinitが行っていたことは、systemdが行っているものと思われる。

9.カーネルが、initを起動する。これにより、前述の通りモジュールがロードされたり、procやsysファイルシステムがマウントされたりする。この時、ブロックデバイスのスーパーブロック(inodeのブロック数やフリーリストなどの情報が含まれている)の情報をもとに、ブロックデバイス上のinode領域からinode情報を取得、メモリ用のinodeに変換(ブロックデバイス上のinode情報の形式と異なる)、メモリに載せるということも行っている。また、init.dのデーモン群もランレベルに応じて実行される

・プログラムがメモリに載る流れ

1.ユーザがプログラム名を指定して実行やコンパイルなどの操作をしようとすると、そのファイルのinode情報のうち、デバイス番号(inode.i_dev)と使用しているストレージ領域のブロック(inode.i_addr[])を参照し、ファイルのコンテンツをブロックデバイスから取得、プロセスの仮想アドレス空間内のdataセグメントに一時的に載せる。
2.載せられたファイルコンテンツに対応するinode内の参照カウンタが0の場合、dataセグメントから一旦スワップアウトを行う。
3.schedなどのスケジューラによって、プロセスの仮想アドレス空間のtextセグメントにスワップインされる。これによってプログラムが実行できるようになった。

textセグメントの共有について、textセグメントに関する情報は、text構造体型の配列によって管理されており、プロセスのproc構造体内にこのtext構造体型へのポインタがある。このため、物理メモリを見てみると、単一のtextセグメントに対し、複数のプロセスのproc構造体からの参照がある。

ここまででコンピュータ起動の流れとファイルを参照しようとした時にプロセスのtextセグメントに載るまでの流れがわかった。
以上を踏まえて、プログラムが実行される流れを見ていく。

1.ソースコードファイルを作成

$ cat > hello.c << EOF
#include <stdio.h>

int main(void) {
   printf("Hello, World!\n");
   return 0;
}
EOF

一定時間が経ったりメモリの空きページが少なくなってくると、スレッドが起きて、ブロックデバイスへの反映を行う。
それまでは、メモリ上に今書いたプログラムが存在することとなる。メモリ上のinodeにはファイル名などメタ情報が、プログラム自体はおそらくtextセグメントに存在するものと思われる。

2.プリプロセス
マクロ展開、#includeや#ifdefなどの処理が行われる。

$ gcc -E hello.c | cat -s
…

__extension__ typedef unsigned long int __fsblkcnt_t;

プリプロセッサによって#includeであればその箇所に指定したヘッダのプログラムを置いたり、#defineマクロであれば、展開して、プログラム中で使用されているところに埋め込んだりする。

3.コンパイル
/usr/libexec/gcc/i386-redhat-linux/4.1.2/cc1がコンパイラの実体となる。
普段はgccがこれを呼び出して処理してくれているが、今回は何が行われているか知りたいので、実体を利用する。
これによって、Cプログラムからアセンブラが出力される。

$ /usr/libexec/gcc/i386-redhat-linux/4.1.2/cc1 -quiet -v hello.c -quiet -dumpbase hello.c -mtune=generic -march=x86-64 -auxbase hello -Wall -version -o hello.s
...
/usr/local/include
/usr/lib/gcc/i386-redhat-linux/4.1.2/include
/usr/include
探索リストの終わり
GNU C version 4.1.2 20080704 (Red Hat 4.1.2-55) (i386-redhat-linux)
compiled by GNU C version 4.1.2 20080704 (Red Hat 4.1.2-55).
GGC heuristics: --param ggc-min-expand=64 --param ggc-min-heapsize=64358
Compiler executable checksum: 391ea76ef17128f7cf6721bdef1a3431

4.アセンブル
これによって、リンクされる前のオブジェクトファイルが出来上がる。

$ as -v --32 -o hello.o hello.s
GNU assembler version 2.17.50.0.6-26.el5 (i386-redhat-linux) using BFD version 2.17.50.0.6-26.el5 20061020

5.リンク
この例では一つしかファイルがないが、本来なら複数のオブジェクトファイルをリンクすることが想定される

$ /usr/libexec/gcc/i386-redhat-linux/4.1.2/collect2 --eh-frame-hdr -m elf_i386 --hash-style=gnu -dynamic-linker /lib/ld-linux.so.2 /usr/lib/gcc/i386-redhat-linux/4.1.2/../../../crt1.o /usr/lib/gcc/i386-redhat-linux/4.1.2/../../../crti.o /usr/lib/gcc/i386-redhat-linux/4.1.2/crtbegin.o -L/usr/lib/gcc/i386-redhat-linux/4.1.2 -L/usr/lib/gcc/i386-redhat-linux/4.1.2 -L/usr/lib/gcc/i386-redhat-linux/4.1.2/../../.. hello.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/i386-redhat-linux/4.1.2/crtend.o /usr/lib/gcc/i386-redhat-linux/4.1.2/../../../crtn.o

これで実行ファイルを作成できた。
上の手順で作成した実行ファイルを、straceシステムコールの引数に渡して実行してみた
アプリケーションから実行したり、シェルから実行したりと様々な場合があるとおもうが、ここではシェルでの実行を考える。

$ strace ./hello
execve("./hello", ["./hello"], [/* 22 vars */]) = 0 //hello実行ファイルのパスを引数に、execveを呼び出し
brk(0)                                  = 0x87f4000 //データセグメントのサイズを変更する
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory) //ld.so.preloadにアクセス可能か調べる
open("/etc/ld.so.cache", O_RDONLY)      = 3  //ld.so.cacheのファイルディスクリプタが3だった
fstat64(3, {st_mode=S_IFREG|0644, st_size=18680, ...}) = 0 //ld.so.cacheの情報(inode番号やデバイス番号含む)が詰め込まれたstat構造体を取得
mmap2(NULL, 18680, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7fbb000 //ファイルディスクリプタ3番、つまりld.so.cacheのオフセット0から、18680バイトだけマッピングする。その際、読み込み権限のみ付加する
close(3)                                = 0 //ld.so.cacheを閉じる
open("/lib/libc.so.6", O_RDONLY)        = 3 // /lib/libc.so.6を開く。ファイルディスクリプタは3番。
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0  W\0004\0\0\0"..., 512) = 512 //ファイルディスクリプタ3番、つまりlibc.so.6のELFヘッダを読み込む。
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7fba000 //ファイルディスクリプタ-1番(どうも、ゼロクリアを意味するらしい)4096バイトだけマッピングする。その際、読み書きの権限をつけてあげる。また、マッピングに対する変更が他のプロセスから見えず、マッピングはどのファイルとも関連付けされないオプションをつけている。
fstat64(3, {st_mode=S_IFREG|0755, st_size=1706204, ...}) = 0 //libc.so.6のstat構造体を得る。
mmap2(0x55c000, 1422788, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x55c000 //libc.so.6のオフセット0からを、なるべくメモリ番地0x55c000から、1422788バイトだけマッピングする。読み込み、実行の権限が付いている。また、マッピングに対する変更が他のプロセスには見えない上、このマッピング領域に対する書き込みを拒否する。
mmap2(0x6b2000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x156) = 0x6b2000 //libc.so.6のオフセット0x156からを、なるべくメモリ番地0x6b2000から12288バイトだけマッピングする。読み込み、実行の権限が付いている。また、マッピングに対する変更が他のプロセスから見えず、指定アドレス以外のアドレスにマッピングを行わない。これは非常に強力で、既存のマッピングと重なる場合は既存のマッピングを破棄する。それに加え、このマッピング領域に対する書き込みを拒否する。
mmap2(0x6b5000, 9668, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x6b5000 //ファイルディスクリプタ-1(ゼロクリア)のオフセット0からを、なるべくメモリ番地0x6b5000から9668バイトだけマッピングする。読み込み、実行の権限が付いている。また、マッピングに対する変更が他のプロセスから見えず、指定アドレス以外のアドレスにマッピングを行わない。それに加え、このマッピングはどのファイルとも関連付けられない。
close(3)                                = 0 //libc.so.6を閉じる
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7fb9000 //ファイルディスクリプタ-1(ゼロクリア)のオフセット0からを、4096バイトだけマッピングする。読み書き権限を付け、マッピング変更が他のプロセスから見えず、マッピングが他のプロセスに関連付けられない。
set_thread_area({entry_number:-1 -> 6, base_addr:0xb7fb96c0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0 //entry_numberが-1なので、未使用のスレッド局所記憶、TLS(Thread-Local Storage)エントリを探し、適切なentry_number(6)を得る。
mprotect(0x6b2000, 8192, PROT_READ)     = 0 //0x6b2000から8192バイトを読み込み専用でメモリ保護
mprotect(0x558000, 4096, PROT_READ)     = 0 //0x558000から4096バイトを読み込み専用でメモリ保護
munmap(0xb7fbb000, 18680)               = 0 //0xb7fbb000から18680バイトだけのマッピングを消去
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0 //標準出力のstat構造体を得る。
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7fbf000 //4096バイトだけ、ゼロクリア。そのマッピング領域は、読み書き権限が付いており、マッピングの変更は他のプロセスからはわからず、このマッピングが他のプロセスに関連付けられることはない。
write(1, "Hello, World!\n", 14Hello, World! //Hello, Worldを出力
)         = 14
exit_group(0)                           = ? //プロセス中のすべてのスレッドをexitさせる。

ライブラリ等を、実行ファイルプロセスの仮想アドレス空間マッピングしたり、権限を調整したりしているのが分かる。
まず、実行ファイルを実行するようにシェルに命令すると、シェルはfork()システムコールを呼び出し、子プロセスを生成する。
試しに、while文をぶん回すだけのCプログラムを実行し、Ctrl-Zでサスペンドし、pstreeで確認してみると

init
 …
 ├─sshd
 │   └─sshd
 │       └─sshd
 │           └─bash
 │               ├─pstree -a
 │               └─while_prog

bash上でwhile_progを実行するように命令すると、bashは子プロセスを生成し、生成された子プロセスにてexecve()が実行される。この時、bashはwait()システムコールを発行して、子プロセスの実行終了を待つ。
execve()が、実行ファイルのパスが渡されて呼び出される。execveは、ファイルのパスにあるファイルを実行し、自身のプロセスとすり替える動作を行う。ソースコードを試しに読んでみた。リンカが、execveがどこにも定義されていない場合、__execveを参照するように、weak_alias関数を呼び出して別名をつけている。おそらく、この部分ですり替えることを行っているのだとおもう。
これにより、実行ファイルが参照され、補助記憶装置から主記憶装置への読み込みが行われ、実行が始まる。

選択問題4

以下のようなC言語のプログラムを書いた。
DEBUGマクロを定義すると、DEBUG情報が出力されるようになっている。
CPUサイクルはインラインアセンブラを用いて、メモリ使用量はpsコマンドを用いて得た。
filepathグローバル変数は、適宜pyonpyon.rhへのパスに書き換える必要がある。

valid_order_brand、invalid_order_brandはそのまま定義しようとしても、コンパイラの警告が出力されてしまった。
おそらく、char **というスカラー型に対して配列型で初期化しようとしたためだと思う。
そのままC言語のコードに埋め込めと言われているわけではないので、正しく解釈する必要があると考えた。
「厳密に」と書かれているため、文字列のどこにも含まれてはいけないと考えると、文字列の配列として定義し、それぞれの文字列が含まれているかどうかチェックすれば良いと考えた。
そのため、以下のソースコードでは、valid_order_brandとinvalid_order_brandのどちらも文字列へのポインタの配列として定義している。

ソースコード

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
//#define DEBUG
typedef unsigned long long ull;

const char *filepath = "/vagrant/pyonpyon.rh"; //ファイルへのパス
unsigned int pid = -1; //Process ID。psコマンドを実行するのに用いる

//Rabbit House Packet
struct rh_packet {
   char Magic[2];
   char Source[20];
   char Destination[20];
   uint32_t DataLength;
   char *Data;
};
typedef struct rh_packet rh_packet;


struct stat_info {
   ull cpu_clock;
   double mem;
};
typedef struct stat_info stat_info;

int read_stat = 1; //読み込み可能かどうか

const int valid_order_brand_num = 3;
char *valid_order_brand[] ={
   "BlueMountain",
   "Columbia",
   "OriginalBlend",
};
const int invalid_order_brand_num = 2;
char *invalid_order_brand[] ={
   "DandySoda",
   "FrozenEvergreen",
};

//CPUタイムスタンプ(最後にリセットされてからのCPUクロック数)を取得する関数
//Intel x86系のCPUではクロック毎加算される64bitタイムスタンプカウンタがある。
//これをrdtsc(Read Time Stamp Counter)命令によって呼び出すことが可能だ。
//ここでは、rdtscをインラインアセンブラで実装している。
ull rdtsc() { 
   ull ret;
   //インラインアセンブラ
   //volatileキーワードによって、コンパイラの最適化を抑制する
   //=Aは制約子であり、eax(32bit), edx(32bit)レジスタを表す制約子(64bit int向け)。ちなみに、出力先を指定する制約文字列には必ず制約修飾子文字「=」を書かなければならないというルールがある
   //Intel x86のrdtsc命令はCPUのタイムスタンプカウンタの現在値をedx:eaxにロードする(上位32bitがedx、下位32bitがeax)
   //インラインアセンブラの構文は、asm(アセンブラテンプレート : 出力オペランド : 入力オペランド : ワークレジスタ);となっている
   //ここでは、rdtscによってrdtsc命令を実行し、edx:eaxにタイムスタンプカウンタを格納した後、出力先変数として指定されているretに、edx:eaxに格納された値を出力する。
   __asm__ volatile ("rdtsc" : "=A" (ret));
   return(ret);
}

//現在のCPUサイクル及びメモリ使用量を取得する関数
stat_info get_stat(int pid) {
   stat_info ret;
   char command[256] = "";
   //このプロセスのRSS(このプロセスが使用しているスワップされていない物理メモリサイズ)を取得
   snprintf(command, sizeof(command), "ps l -p %d | awk '{print $8}'",pid);

   FILE *fp;
   char result[256];
   if((fp = popen(command, "r")) == NULL) {
       printf("cannot open");
       exit(EXIT_FAILURE);
   }
   while(fgets(result, sizeof(result), fp) != NULL) {
       int res = sscanf(result, "%lf", &ret.mem);
       if(res == EOF) {
           pclose(fp);
           return(ret);
       }
   }

   pclose(fp);
   ret.cpu_clock = rdtsc();
   return(ret);
}

//文字列の文字を全て小文字に変える
char *str2lower(char *s) {
   int length = (int)strlen(s);
   int i;
   for(i = 0; i < length; i++) {
       s[i] = tolower(s[i]);
   }
   return s;
}

//条件1~6のチェック
int check(rh_packet rhpkt) {
   int i;
   //Condition 1 Rabbit House?
   if(rhpkt.Magic[0] != 'R' || rhpkt.Magic[1] != 'H') {
       return(0);
   }
#ifdef DEBUG
   printf("[*] Condition 1 OK\n");
#endif
   //Condition 2
   char *src = str2lower(rhpkt.Source);
   if(!strncmp(src, "rise-san", 8) && !strncmp(src, "cocoa-san", 9)) {
       return(0);
   }
#ifdef DEBUG
   printf("[*] Condition 2 OK\n");
#endif
   //Condition 3
   char *dst = str2lower(rhpkt.Destination);
   if(!strncmp(dst, "chino-chan", 10) && !strncmp(dst, "chino", 5)) {
       return(0);
   }
#ifdef DEBUG
   printf("[*] Condition 3 OK\n");
#endif

   //Condition 4
   if(!strcmp(src, "cocoa-san") && !strcmp(dst, "chino")) {
       return(0);
   }
#ifdef DEBUG
   printf("[*] Condition 4 OK\n");
#endif

   //Condition 6
   for(i = 0; i < invalid_order_brand_num; i++) {
       if(strstr(rhpkt.Data, invalid_order_brand[i]) != NULL) {
#ifdef DEBUG
           printf("[-] %s found.\n", invalid_order_brand[i]);
#endif
           return(0);
       }
   }
#ifdef DEBUG
   printf("[*] Condition 6 OK\n");
#endif

   //Condition 5
   for(i = 0; i < valid_order_brand_num; i++) {
       if(strstr(rhpkt.Data, valid_order_brand[i]) == NULL) {
#ifdef DEBUG
           printf("[-] %s not found.\n", valid_order_brand[i]);
#endif
           return(0);
       }
   }
#ifdef DEBUG
   printf("[*] Condition 5 OK\n");
#endif

       return(1);
}

rh_packet read_packet(FILE *fp) {
   rh_packet ret;

   //Magic Byte
   int fsize = (int)fread(ret.Magic, sizeof(ret.Magic), 1, fp);
#ifdef DEBUG
   printf("[+] Magic Byte = %s\n", ret.Magic);
#endif
   if(fsize == 0) { //もうこれ以上読み込めない
       read_stat = 0;
#ifdef DEBUG
       printf("Finish.\n");
#endif
       return(ret);
   }

   //Source Addr
   fread(ret.Source, sizeof(ret.Source), 1, fp);
#ifdef DEBUG
   printf("[+] Source Addr = %s\n", ret.Source);
#endif

   //Destination Addr
   fread(ret.Destination, sizeof(ret.Destination), 1, fp);
#ifdef DEBUG
   printf("[+] Destination Addr = %s\n", ret.Destination);
#endif

   //Data Length
   fread(&ret.DataLength, sizeof(uint32_t), 1, fp);
   ret.DataLength = ntohl(ret.DataLength);
#ifdef DEBUG
   printf("[+] DataLength = %d\n", ret.DataLength);
#endif

   ret.Data = (char *)malloc(sizeof(char)*ret.DataLength);
   fread(ret.Data, sizeof(char)*ret.DataLength, 1, fp);
#ifdef DEBUG
   printf("[+] Data = %s\n", ret.Data);
#endif
   return(ret);
}

int main(void) {
   pid = (int)getpid();
   //pyonpyon.rhを開く
   FILE *fp;
   fp = fopen(filepath, "rb");
   if(fp == NULL) {
       printf("OPEN ERROR");
       exit(EXIT_FAILURE);
   }

   rh_packet pkt;
   while(1) {
       pkt = read_packet(fp);
       if(!read_stat) break;
       //CPUサイクルやメモリ使用量を取得(before)
       stat_info before = get_stat(pid);
#ifdef DEBUG
       printf("Before CPU Cycle=%llu, Memory=%lf(kB)\n", before.cpu_clock, before.mem);
#endif
       //チェックをかけて
       int result = check(pkt)
       //再度CPUサイクルやメモリ使用量を取得
       stat_info after = get_stat(pid);

       //最後にチェックに応じた出力を行い
       if(result) {
           printf("PASS\n");
       } else {
           printf("REJECT\n");
       }

#ifdef DEBUG
       printf("After CPU Cycle=%llu, Memory=%lf(kB)\n", after.cpu_clock, after.mem);
#endif
       //マッチングにかかったCPUサイクル及びメモリ使用量を出力する。
       printf("Matching used CPU Cycle %llu, Memory %lf(kB).\n", after.cpu_clock-before.cpu_clock, after.mem-before.mem);
       putchar('\n');
       free(pkt.Data); //Dataは動的に確保しているため、解放
   }

   fclose(fp);
   return 0;
}
・実行結果
$ ./rhpkt_match
REJECT
Matching used CPU Cycle 31619646, Memory 80.000000(kB).

REJECT
Matching used CPU Cycle 17624155, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 26430615, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 30070283, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 22568636, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 24127622, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 15864860, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 24185634, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 22638197, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 28097601, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 20553726, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 34922973, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 23925269, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 20784937, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 23470467, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 23961367, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 23308672, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 29050089, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 16536914, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 21359561, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 13795710, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 13543117, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 17725989, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 17355279, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 23561724, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 13990891, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 13264643, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 13755037, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 13287532, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 21081993, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 20273016, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 19157997, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 14535584, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 13464240, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 23214606, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 14579642, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 16930423, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 22910441, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 73592721, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 19578587, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 19594448, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 19079588, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 15535166, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 13411838, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 13420062, Memory 0.000000(kB).

選択問題6

実際に攻撃を行いながら調べたいと思い、isoが配布されているBadStoreというwebアプリケーションに対して攻撃を行った。
前提
Apache+Perl(CGI)+MySQLの構成を想定する。
ECサイトであり、プロトコルにhttpを用いている。
ページ内検索、ホーム画面、最新の商品、掲示板入力フォーム、注文履歴、カートの中身確認、About Us、パスワードリセット、アカウントログイン及び登録、関係者専用ページなどがある。


テスト
1.ID「a」、パスワード「a」のような非常に脆弱なパスワードで登録できるか試してみる
1桁が無理でも、7桁未満くらいなら、総当たり攻撃が可能であるため、最低何桁であれば登録できるか試す。
BadStoreではID「a」、パスワード「a」の組み合わせで登録できてしまった・・・。

2.ID「admin」、パスワード「’ OR 1=1; #」でログインを試みる
認証時、簡単な例だと以下のようなSQL文が実行されていることが予測できる

SELECT * FROM users WHERE id = ‘<param_id>’ AND password = ‘<param_password>’;

ここで、上記の通りにログインしようとすると

SELECT * FROM users WHERE id = ‘admin’ AND password = ‘’ OR 1=1; #

となる。
「#」以降はコメントアウトとして扱われ(1行コメントアウト)、無視される。
テーブル名などを知りたいという場合は

登録されていないID’ OR lower(substring((SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES LIMIT 1), 1, 1)) >= ‘u’ #

のように打ち込み、ログインが成功するようであれば、あるテーブル名の1文字目がアルファベット順で’u’より後ろにあることがわかる。
ちょっと長いため、Firebugなどでインプットタグのmaxlengthをいじる必要がある

3.ID「admin’ OR 1=1; #」、パスワード「xxx」でログインを試みる
パスワードでダメだったので、IDで試してみる。
こうすると、SQL文が以下のようになると思われる

SELECT * FROM users WHERE id = ‘admin’ OR 1=1; # AND password = ‘xxx’;

Bad Storeでは、Software errorが発生した。
エラー画面から、SQL文は以下のようなものが実行されているとわかった

SELECT * FROM users WHERE id = ‘admin’ AND password = ‘f56…’;

2で失敗していたのは、パスワードを用いてハッシュ値を算出するため、攻撃が意図通りにいかないためであると分かる。

4.IDに「’」でログインを試みる
シングルクオートのみにし、徐々にいじることで攻撃を図る。
3の時と似たようなエラー画面が表示された。

5.ID「admin’ #」、パスワード「xxx」でログインを試みる
OR 1=1がない場合、SQL文は以下のとおり

SELECT * FROM users WHERE id = ‘admin’ # AND password=‘f56…’;

これでも、idがadminでさえあれば一致するので試してみた。
通ってしまった・・・。見事にAdministratorになった。

6.ID「admin’ OR ‘x’=‘x」、パスワード「xxx」でログインを試みる
SQL文が以下のとおりになる

SELECT * FROM users WHERE id=‘admin’ OR ‘x’=‘x’ AND password=‘f56…’;

やっていることは5と同じ。結局、idがadminであるものが偶々見つかって認証が通る。

7.ページ内検索フォームに

「<s>hello」

と打ち込む
適切にエスケープされていないと、helloに打ち消し線が引かれる。
もし適切にエスケープされていないのであれば、他のHTMLタグを用いることが可能となる
Bad Storeでは打ち消し線が引かれた。


8.ページ内検索フォームに

「<script>alert(‘XSS’)</script>」

と打ち込む
単純なXSSを試す。
BadStoreではSQLのエラーが出た。おそらくシングルクオートで引っかかったものと思われる。

9.ページ内検索フォームに

「<script>alert(1)</script>」

と打ち込む
シングルクオートがあったことがエラーの原因だったため、数値で試してみる。
Bad Storeではalertが発火した。

10.ページ内検索フォームに

「<script>alert(“XSS”)</script>」

と打ち込む
では、ダブルクオートではどうだろうか。
Bad Storeではalertが発火した。

11.最新の商品一覧画面にて、通常はあまりないと思うが、チェックボックスなどでチェックしている場合、value値に適当な値を入れることで一覧に無い商品を参照できないか
例えば、Bad Storeでは以下のようになっていた

<input name=“cart item” value=“1000“ type=“checkbox”></input>

value値に該当するアイテムが無い旨のエラーメッセージが表示された。


12.ゲストブックにて、

name「<s>name」、email「<s>email」、comments「<s>comment」を打ち込む

適切にエスケープされてい無い箇所が無いか調べる。
Bad Storeでは全て打ち消し線が引かれた・・・。


13.ゲストブックにて、

name「<img src="/images/BadStore.jpg”/>」、email「<h1 onclick="alert(1)">click me!</h1>」、comments「<script>alert("XSS")</script>」

を打ち込んでみる。
適切にエスケープされていないことがわかったため、画像を名前に埋め込んでみたり、持続型XSSを埋め込んだりといったことを実験してみる。
期待通り、名前にはBad Storeの画像が埋め込まれ、alertが発火した。

14.パスワードリセットにて、メールアドレスフォームに「admin@example.com」と打ち込んでみる。
どのように動作するか確認する。
メールアドレスに送信するのではなく、直接webページに仮パスワードを記載するようだ。

15.パスワードリセットにて、メールアドレスフォームに

「<script>alert(1)</script>」

と打ち込んでみる。
POST後、メールアドレスがwebページに表示されていたため、適切にエスケープされていなければalertが発火する。
Bad Storeでは発火した。

16.パスワードリセットにて、CSRFトークンが無いか確認し、なさそうであれば試しにPOSTしてみる

$curl -H "Content-Type: application/x-www-form-urlencoded"  -X POST -d '{"email":"<script>alert(1)</script>", "pwdhint":"green", "DoMods": "Reset User Password"}' http://192.168.100.131/cgi-bin/badstore.cgi\?action\=moduser
<HR><P><BR><Center><FONT SIZE=2, FACE='Times'>BadStore v1.2.3s - Copyright &#169; 2004-2005</Center></BODY></HTML>

実際に攻撃を行う際は、以下のようなHTMLを用意する方法もある

<html>
<head></head>
<body onload="document.csrf.submit();">
<form name="csrf" method="POST" action="http://192.168.100.131/cgi-bin/badstore.cgi?action=moduser" enctype="application/x-www-form-urlencoded">
<input name="email" size="15" type="text"> 
<select name="pwdhint">
<option value="green">green</option>
<option value="blue">blue</option>
<option value="red">red</option>
<option value="orange">orange</option>
<option value="purple">purple</option>
<option value="yellow">yellow</option>
</select>
<input name="DoMods" value="Reset User Password" type="submit"></p></form>
</body>
</html>

SSOidが、「YTowY2MxNzViOWMwZjFiNmE4MzFjMzk5ZTI2OTc3MjY2MTphOlU==」というようなbase64で、解読すると「a:0cc175b9c0f1b6a831c399e269772661:a:U」となる。これは、ID「a」、パスワード「a」で試した結果で、a::a:Uという形式(md5はhashkillerのようなレインボーテーブルで調べた)になっており、トークンが生成しやすいものとなっている。
商品をカートに追加する際も、CSRFトークンが用いられていないため、以下のようにPOSTすることで、SSOidに指定したユーザのカートに任意の商品が追加可能であると考えられる。
curl -H "Content-Type: application/x-www-form-urlencoded" -H "Cookie: SSOid=<攻撃対象のSSOid>” -d '{"cartitem":1000, "Add Items to Card": "Add Items to Cart"}' http://192.168.100.131/cgi-bin/badstore.cgi\?action\=cartadd
このほかにも、商品注文画面やログイン画面にもトークンが用いられていない。

17.関係者用ログインページにて、email「’」、パスワード「xxx」を打ち込んでみる
SQLのエラー画面をみたいため、エラーが起きるように、余計なシングルクオートを追加する。
Bad Storeでは何も起きなかった。

18.関係者用ログインページにて、email「”」、パスワード「xxx」を打ち込んでみる。
シングルクオートでダメだったので、ダブルクオートを用いている可能性を疑う。
IDとパスワードに一致するアカウントが見つからなかった旨のページが表示される。

19.cgi-bin/test.cgiに強制アクセスしてみる
testといった名前は使われやすく、意外と重要な情報があったりすることもある。
Bad Storeでは見ることができた。

20.cgi-bin/に強制アクセスしてみることで、ディレクトリリスティングを試みる
test.cgi以外にも、名前の予測が難しいが重要な情報が含まれるファイルを閲覧したい。
Bad StoreではさすがにForbiddenが返された。

21.robots.txtに強制アクセスしてみる
robots.txtには、検索エンジンにインデクシングしたくないページの情報が含まれていることが多く、重要な情報を得られることが期待できる。
Bad Storeでは、見ることができ、アカウント情報を格納したページ(supplier/accounts)を閲覧できた
そのページにはbase64エンコーディングされた以下の文字列のような重要情報が記載されていた
joeuser/password/platnum/192.168.100.56
そのほかには、scanbotというディレクトリの下に、ウィンドウを開いてリダイレクトさせることを繰り返すような嫌がらせhtmlファイル(?)があった。

22.Bad Storeであれば「Doing Business」、「Procedures」などのディレクトリリスティングできそうなディレクトリに強制アクセスを仕掛けてみる
ほかにも有用な情報を得たいため。
Bad Storeでは、ディレクトリリスティングできるものの、有用なファイルが見当たらなかった。

23.getパラメータにlogin=Trueなどというものがあれば、logout=Trueなどとして試してみる。
これにより、ログアウトを強制することができる。
BadStoreではlogin=Trueというクエリパラメータを認証時に利用していたため、logout=Trueとしたらログアウトできた。

24.ファイルを開いていると推測できる箇所、メール送信を行っている箇所では、「;ls+-l」や、「|ls; #」などOSコマンドインジェクションを試みる
OSコマンドが実行できると、webアプリケーションを起動しているユーザの権限で任意コマンド実行ができるので、サーバー側に対してほとんどなんでもできてしまう場合もある(例えば、root権限でwebアプリケーションを立ち上げている場合)
Bad Storeでは、従業員としてログイン(通常のログインと同じ手法でいけた)したのち、ファイルアップロードができるようだ。
しかし、なぜかrobots.txtに書いてあったuploadsディレクトリを参照しても「そんなディレクトリはない」と怒られた。
試しに以下のようなPerlプログラムを書いて、名前を「../../cgi-bin/exec.pl」としてアップロードしてみた
Permission Deniedが返された
だが、Bad StoreのProceduresディレクトリもみれることがわかっているので、試しにそこにも置こうと試みる。
が、同様にPermission Deniedが返された。

25.Perl CGIでopen関数が使われていそうな場所が見つかれば、「|ls -la」などを打ち込んでみる
sysopen関数を使われていると難しくなるが、open関数は、「|」があるとOSコマンドを実行してしまう。
Bad Storeで試してみたが、どうやら「|ls -la」というファイル名でファイルが置かれただけらしい・・・。

26.メール問い合わせ部分があった際、メールヘッダインジェクションができないか試す。
メールの文章入力フォームに
sample
Bcc: example@example.com
などと入力すると、BCCが追加され、example@example.comにも送信されてしまう。


27.GETパラメータにurlリダイレクタがある場合など、urlに「http://example.com/%0D%0ASet-Cookie:+SSOid=abc」と打ち込んでみる
リダイレクタではLocationヘッダが用いられると推測できるため、
Location: http://example.com/
Set-Cookie: SSOid=abc
となることが期待される。
これによってセッション固定化攻撃が可能となる。


28.ユーザ認証画面にて、ID「xxx' OR 1=1; #」でログインを試みる
Bad Storeで任意のIDでログインしたかったので、諦めきれず試す。
やはりエラーがでる

SELECT * FROM users WHERE id = ‘xxx’ OR 1=1; # ‘ AND password=…

コメントアウトされた部分についてもsyntaxがチェックされるのだろうか?

選択問題11

CVE-2015-7547(glibc)

攻撃概要:
この脆弱性攻撃は、悪意のあるDNSサーバに対して、DNSクライアントがglibcのgetaddrinfo()関数を呼び出し、ホスト名からIPアドレスを解決しようとした際に、2048バイトを超えるようなレスポンスが悪意あるDNSサーバから返ってくると、getaddrinfo()関数呼び出しにより呼び出された_nss_dns_gethostbyname4_rにて、BOFが発生(2048の固定サイズでスタックに領域を確保している)してしまうことで成功する。
細かく説明すると、getaddrinfo()では、レスポンスを受け取ると、UDPのクエリ処理を受け持つsend_dg関数内にて2048バイトまではスタック上に確保するが、それ以上のサイズのレスポンスについて、確保しきれない分をヒープ上に、最大65536バイトまで確保する。ここでサイズが最大65536と認識されるわけだが、send_dg関数から戻った時、スタック上には2048バイトしか確保していないのに実際のデータサイズは65536バイトもある、ということで不整合が起きる。次に__libc_res_nsendがsend_dg(UDP)やsend_vc(TCP)を呼び出す際、ちゃんと不整合を修正(ポインタの更新)しているため、1回の__libc_res_nsend呼び出しで攻撃を完結させる必要がある。
攻撃を1回で完結させるため、公開されているPoCでは、
TC(TrunCated)フラグを立てて2048バイトを超えるUDPレスポンスを返す。
ここでクライアント側が、レスポンスサイズが最大で65536だと認識してしまい、バッファ(2048バイト分しか確保されてない)サイズが最大で35536バイトに更新される
TCビットが立っているため、TCPフォールバックが起きる(クライアントが、今度はTCPでリクエストを送る)。このTCPフォールバックの活用こそが、__libc_res_nsend関数の呼び出しが1回目のうちに攻撃を完了させるための工夫である。
クライアントは、バッファのサイズが最大で65536バイトだと勘違いしたまま、バッファにレスポンスを書き込もうとする。ここでは、バッファのサイズを超えるレスポンスにならないため、ヒープ領域が確保されず、そのままスタックに書き込まれる。
これによりバッファオーバーフロー攻撃が成功する。

攻撃を検知する方法:
1.信頼できるDNSキャッシュサーバを経由して名前解決を行う
UDPクエリでは、resolve.confのoptionsでedns0を指定しない場合は512バイトを越えるレスポンスが送出されず、TCビットの付いたレスポンスはデータを含まない(その後のTCP通信からデータが送られ始める)ことが、仕様にあり、DNSキャッシュサーバがこれに従うため、クライアント側が2048バイトを越えるレスポンスを受けることがないと分かる。よって、攻撃の防御が容易。キャッシュサーバで2048バイトを超えるようなレスポンスを受け取っていることをチェックすれば、検知することが可能である。具体的には、iptablesのlengthで2048を指定し、--log-level=[info|debug]などでログに記録しておくような方法がある。また、logwatchなど他のツールを用いることで、システム管理者に通知することも可能と考えられる。なお、iptablesコマンド実行時に、状態がESTABLISHEDの時にチェックをかけるように指定しないとダメである。なぜならば、TCPならフラグメントが生じて、パケットを再構築した際に2048以上になりうるからだ。

$ sudo iptables -A INPUT -p udp - -sport 53 -m state - -state ESTABLISHED -m length - -length 2048: -j LOG - -log-level=info - -log-prefix “ipv4 udp glibc detect”
$ sudo iptables -A INPUT -p tcp - -sport 53 -m state - -state ESTABLISHED -m length - -length 2048: -j LOG - -log-level=info - -log-prefix “ipv4 tcp glibc detect”
$ sudo ip6tables -A INPUT -p up - -sport 53 -m state - -state ESTABLISHED -m length - -length 2048: -j LOG - -log-level=info - -log-prefix “ipv6 udp glibc detect”
$ sudo ip6tables -A INPUT -p tcp - -sport 53 -m state - -state ESTABLISHED -m length - -length 2048: -j LOG - -log-level=info - -log-prefix “ipv6 tcp glibc detect”

2.SSPを用いる
getaddrinfo()を呼び出すプログラムのコンパイル時に、Stack Smashing Protectorを指定しておくと、stack canaryを用いてBOFが検知される。例えば以下のように実行すれば、SSP有効となる。

$ cat > propolice.c << EOF
#include <string.h>

void check() {
  char buf[2];
  memset(buf, 114514, 32);
  return;
}

int main(void) {
  check();
  return 0;
}
$ gcc -fstack-protector-all -o propolice propolice.c #-fstack-protectorでもSSP有効となるが、例えばBOFされるバッファが8バイト未満だとスタック破壊検出コードを生成しないため、必ずスタック破壊検出コードを生成する-fstack-protector-allを使用したほうがいい。
$ ./propolice
*** stack smashing detected ***: ./propolice terminated
アボートしました

内部的にはmain()の始めで、スタックにランダムな値を仕込んでおく(canary)。最後に、最初のcanaryと同じ値(ハードコードされているので、この値はテキストセグメントにある)と、スタック上のcanaryがあるであろう位置にある値とを比較し、一致しなければプログラムを強制終了する。
これにより、バッファオーバーフローが起きてしまい、DoS攻撃が成功してしまうものの、その後のROPなどの攻撃は防げるため、任意コード実行ほど深刻にはならない。

  • fstack-protector-allがきちんと動作することが確認できた。では、-fstack-protectorでは本当にスタック破壊検出コードが生成されないのだろうか。試してみた。
$ cat > sample_nogen_stackdestroycode.c << EOF
#include <string.h>

int main(void) {
   char buf[1];
   const char *sample = "AAAA";
   strcpy(buf, sample);
   return 0;
}
EOF
$ gcc -fstack-protector -o sample_nogen_stackdestroycode sample_nogen_stackdestroycode.c
$ ./sample_nogen_stackdestroycode
セグメンテーション違反です

Segmentation Faultが起きたことはわかるがスタック破壊検出されていないように見える。
しかし、ももいろテクノロジーにてここで紹介したSSPのBypass手法のエントリを見つけた。
どうやら、ROP前になんとかcanaryの値を得て、ROP実行し、プログラム終了前にスタック上の破壊されたcanaryの位置に復元してやるというものだった。
そのため、この手法で検知しようとしても、うまくBypassされる可能性がある。
後述するmudflapでも、似たような手法で検知を行っているため、同様にBypassされる可能性が否定できない。

3.mudflapを用いる
2で取り上げたSSPと同様、バッファオーバーフローを検知してくれる。他にも、メモリリーク、ポインタ誤使用なども検出してくれる。

$ cp propolice.c mudflap.c
$ gcc -fmudflap -lmudflap -o mudflap mudflap.c
$ ./mudflap
*******
mudflap violation 1 (check/write): time=1464535861.664713 ptr=0xbfe333fa size=23
pc=0xdc5ecd location=`(strcpy dest)'
     /usr/lib/libmudflap.so.0(__mf_check+0x3d) [0xdc5ecd]
     /usr/lib/libmudflap.so.0(__mfwrap_strcpy+0x158) [0xdd2298]
     ./mudflap(main+0x4e) [0x8048672]
Nearby object 1: checked region begins 0B into and ends 21B after
mudflap object 0x83d7e38: name=`mudflap.c:5 (main) buf'
bounds=[0xbfe333fa,0xbfe333fb] size=2 area=stack check=0r/3w liveness=3
alloc time=1464535861.664706 pc=0xdc590d
number of nearby objects: 1
セグメンテーション違反です

ついでにメモリリークやポインタ誤使用もやってみた

$ cat >  memory_leak.c << EOF
#include <stdlib.h>

int main(void) {
   char *ptr = (char *)malloc(sizeof(char *)*10);
   for(i=0;i<=3;i++){
       printf("Hello\n");
   }
   return 0;
}
EOF
$ export MUDFLAP_OPTIONS='-print-leaks'
$ gcc -fmudflap -lmudflap -o memory_leak memory_leak.c
$ ./memory_leak
Hello
Hello
Hello
Hello
Leaked object 1:
mudflap object 0x8910f10: name=`malloc region'
bounds=[0x8910ea0,0x8910ec7] size=40 area=heap check=0r/0w liveness=0
alloc time=1464537305.697616 pc=0x1ca90d
     /usr/lib/libmudflap.so.0(__mf_register+0x3d) [0x1ca90d]
     /usr/lib/libmudflap.so.0(__wrap_malloc+0xcf) [0x1cbe6f]
     ./memory_leak(main+0x1d) [0x8048631]
     /usr/lib/libmudflap.so.0(__wrap_main+0x49) [0x1ca969]
number of leaked objects: 1

間違ったポインタの扱いというのが、いまいちピンとこなかったが、「これはダメでしょ」というものを試してみた

$ cat > wrong_pointer.c << EOF
#include <stdio.h>

int main(void) [
   int *ptr; //初期化されていない
   *ptr = 5;
   printf("%d\n", *ptr);
   return 0;
}
EOF
$ gcc -fmudflap -lmudflap -o wrong_pointer wrong_pointer.c
$ ./wrong_pointer
number of leaked objects: 0

初期化されていない状態のポインタを使って、参照外しで値を入れ込んでいる。これも、メモリリークにつながる。
無事検知されたようで良かった。

興味を持った理由:
この脆弱性を知るまで、時折Twitterで脆弱性情報を探していたが、とびきり目立つ脆弱性しか目につかず、おそらく、同年代で自分と同じく情報セキュリティに興味がある方々よりも脆弱性に関する知識が少なかった。
この脆弱性はTwitterでもおおきく話題として取り上げられ、時々しか確認しない私でも知ることができた。
DNSに関して知識が浅かったため、勉強も兼ねて・・・と最新情報(英語)をアバウトに翻訳し、友人に伝えた。
これは、私にとって初めてアグレッシブに脆弱性情報と向き合った瞬間かもしれない。
これがきっかけで、脆弱性情報を効率的に収集するにはどうしたらいいか考えるようになり、現在では、研究室のSlackで、毎朝7:00にJVNiPediaから脆弱性情報を取得し、知らせてくれるボットを動かしている。
こうした経緯があり、この脆弱性にはおおきく関心があった。
だが、脆弱性が発覚した当日に英語の翻訳をした程度で、日本語の記事をチェックしていなかったため、これを機会に調べてみようと思い、この問題で提示した。

                                              • -

以上になります。
プロの方々がアップしていくなか、こうしてビクビクしながらアップしております・・・´д` ;

mixi のgit-challengeに行ってきたよ

本日、mixiさんの第2回 git-challengeに参加してきました。
プロの方々がたくさん来るんだろうなぁとブルブル震えながら行きました。
f:id:tukejonny:20160305211228j:plain

まず、gitについての説明をいただきました。
もう、ビュンビュン進んでいきます
f:id:tukejonny:20160305211511j:plain
gitは知ってる前提ということで、基本操作(ステージング、コミット、プッシュ、etc...)は飛ばしました

gitの基本知識を詰め込んだところで、お昼休憩に入ります。
肉系の弁当か、ちらし寿司のどちらかを選ぶことができます(写真撮ってませんでした・・・ごめんなさい)
mixiさんありがとうございます。

さて、2人1組でチームをいくつか組み、競い合っていきます。
後学のために、チューターさんに積極的に話しかけました(´・Д・)」
f:id:tukejonny:20160305214034j:plain

競技途中に10分くらい休憩を取り、その間に桜餅をいただきました
f:id:tukejonny:20160305214659j:plain
mixiさんありがとうございます : )

今までmasterブランチだけで開発してきた自分にとって、基本的な操作の根本理解につながったことが大きかったと思っています。
メンバーの方と協力し、問題を解いていくのは非常に楽しかったです!

octcatのステッカーです!
Github Japanさん、ありがとうございます : )
f:id:tukejonny:20160306110936j:plain

残念ながら入賞に至りませんでしたが、知見を深めることができたため、非常に良かったです。
競技後は、問題の解説等いただきました
f:id:tukejonny:20160305214842j:plain

懇親会では、gitの裏舞台に関するお話+交流をすることができました

今日は本当に知見の深まる1日でした!
mixiさん、プライベートリポジトリを提供してくださったGithub Japanさん、ありがとうございました!

SECCON2015予選に参加してみた話

今回、チームsosuncer(そすんさー)で参加してきました。
f:id:tukejonny:20151207001727j:plain
チーム全体では900Pを稼ぎ、213位でした。私はほとんど役に立てませんでした。

じゃあ、お前は何をやったんだということなんですが、
解けたのは2問

1. Connect the server
2. Last challenge(Thank you for your playing)

です。
Web/Networkを解きたくて、画面とにらめっこしていたのですが、解けず終い

①Connect the server
netcatで指定されたFQDN&ポート番号にアクセスします。
すると、向こうから1文字ずつ(?)文字が送られてきていて、タイプしてるみたいに表示されていきます。
それでコネクション切れます

とりあえず、何が起こっているかWiresharkでキャプチャしながら覗こうと思い、キャプチャフィルタを設定してキャプチャを開始してから
netcatで接続します。
一通り終わって、WiresharkTCPストリームを追跡します。
すると、フラグが送られているのがわかります。


②Last challenge
単一換字暗号ですね。
この問題、どうやら1問目として出てきた問題と同じやつらしいです。
Pythonでmake_transして表を作っていけます。

#-*- coding: utf-8 -*-
from string import*

t = maketrans("ABCDEFGHIJKLMNOPQRSTUVWXYZ{}", "SVMO{CTYGUPXJN}AFDKIQHRBZLWE")
text = raw_input("input: ")
print text.translate(t)
(*'-') < python encoder.py
input: A}FFDNEA}}HDJN}LGH}PWO
SECCON{SEEYOUNEXTYEAR}





解けなかったけど、取り掛かった問題についても書こうと思います。

1. Fragment2
2. Command-Line Quiz
3. Entry Form
4. Bonsai XSS
5. Find the prime numbers

①Fragment2
pcapが配られます。
Wiresharkでみます
パケット一つ。
へ・・・?

ASCIIみてみると、Flag is in headerと書かれています。
ヘッダと聞いて思いつくのは、Wiresharkで見れるEtherとかIPとかのヘッダと、バイナリエディタなどで見れるpcapファイル自体のヘッダでした。
ごにょごにょ探してみるものの、見つからず。



②Command-Line Quiz
友人が先にとき進めていて、それに合流する形でした。
どうやら、6つのテキストファイルがサーバーに置いてあって、1つはフラグの書かれたテキストファイル(当然権限がないと怒られて読めません)、ほか5つは、1, 2, 3, 4, 5とステージ番号が振られていて、ステージ1をクリアしたらステージ2のテキストが読めるって感じになってるらしい。
回答は、環境変数にセットして、shコマンドを打つ事でできる仕組みになっていました。

ステージ1:テキストの先頭読むときどうやる? Ans. head
ステージ2:テキストの末尾読むときどうやる? Ans. tail
ステージ3:テキストの特定の1行みるときどうやる? Ans. grep
ステージ4:テキストを処理するときどうやる? Ans. awk
ステージ5:flags.txtはただ一つのプログラムでのみ読む事ができます。
      頑張って探してね👍

ええ〜、bin/ sbin/ usr/bin usr/sbinあるんですけど・・・
どんだけ探せばいいんすか・・・(泣)

結局解けませんでした。


③Entry Form
メールアドレスと名前の2つのフォームが用意されており、メールアドレスについては、形式が決まっているため、バリデーションが施されていました。
submitすると、送ったよ 戻る みたいのが表示されます。
チームメンバーから助言をいただき
、ディレクトリ階層を遡ってみると、register.cgi_bakというファイルが見つかります。
こいつにアクセスすると、cgiソースコードがみれます。
どうやらPerlで書かれているらしい・・・
お、open関数使ってる
open関数使ってsendmailコマンド実行しとる・・・
後ろにコマンド繋げてやればいけるかな?
あぁ、結果どうやって出力すればいいんだこれ
って感じになりました。

いろんな記事みながら、いろいろ試しました。
admin@example.com | cat log | nc
admin@example.com;cat log | nc
みたいのとか・・・
出力しようがないということで、グローバルIPを持つホストでnetcatでリッスンさせて結果みれないかなーってやってました。

ほかの方のWriteUpみて恥ずかしくなりました。ポッチャマァ・・・



④Bonsai XSS
実行 -> なんか勝手に動いてく -> 止まる -> クリック -> クリックできねーよって言われる -> なんかいろいろ動かす -> どうやらTab 右方向キーでURL入力欄に打ち込めるようになる -> 何しても動かない・・・

で解けず終い。



⑤Find the prime number
渡されたソースみて、cgiの動きを調べて、わかったことを投げて、それ以降進捗ありませんでした。

read_json()を使って、jsonファイルを読み込んでいる(jsonファイルには、素数p, qなどの数値があるらしい?)
write_json()を使って、jsonファイルに書き込みを行っている

単純なRSAではない?なんか独自の計算行ってる?

Webページを見たときに表示される文字は
q = "%019d + %019d = %019d" % (c, o, h)
で出力している。
また、出力した値はログファイルに書き込んでいる

クエリパラメータの渡し方はおそらく
http://domain?QUERY_STRING=<value>

<value>がdata.jsonに書かれたnumという値と一致し、access.jsonに書かれたこちらのIPアドレスの時刻よりも後であればフラグを出力するらしい

どうやら、data.jsonにはこの問題でしようするパラメータを、
access.jsonにはアクセスしてきたIPのデータを保持させるらしい

フラグが出力されたかどうかに関わらず、access.jsonの時刻とoutpというパラメータは更新される。
更新された時刻より後であれば、フラグを出力させるチャンスが訪れる

さくらインターネットのDCにお邪魔させていただいたお話(写真は後ほど)

先週の土日、さくらインターネットのデータセンターにお邪魔させていただきました。
移動は主にバスで、何かを発見した女の子が外装に描かれていました。

データセンターでは、普段見られない低レイヤーな世界を覗けました。
見学していて思ったのですが、物理学って大事・・・

さくらインターネットスタッフの皆様、バスの運転手さん、JTBさん、はてなさんからは、
本当にたくさんのおもてなしをいただきました。

最後、さくらの皆さんが、DCからバスで出るときに、DC内を走って見送ってくれました。
ここまで見送っていただいたのは初めてです・・・(感動)

また参加したいッ!(≧▽≦)
毎年倍率高いと聞いていたのですが、今回行かせてもらえて本当によかったです。
たくさんのおもてなしや年齢問わず、少なくともさくらさんのDCを見学したいという思いで共通した仲間と知り合えたことが本当に嬉しいです。

話していいこと、話しちゃまずいこととあるので、今後少しづつ更新する予定です。

改めまして、さくらインターネットスタッフの皆様、バスの運転手さん、JTBさん、はてなさん
本当にありがとうございました!!!

Trend Micro CTF 2015 に参加した

チームsendyで、TMCTFに参加しました。
相も変わらず解ける問題が少なくて人権の無い日々が続きます

結局、僕が解けたのはProgramming 200だけです。
とりあえず、解けた問題について書いてみます。

まず、どんな問題なのか。
netcatで接続すると、計算問題が降ってきて、それに対して正しい答えを返さなければいけないという
どこかで見たことのある問題です。
問題には種類があって、

1.単純な数字の計算問題
2.ローマ数字の計算問題
3.英語表記数値の計算問題
4.1〜3の複合問題

の4つだったと思います。
①は単純にevalに渡してやればいいだけなのですが、②、③、④についてはそうもいきません。
②については、ググってローマ数字を単純な数字に変換するスクリプトを参考にしました。
③については、最初、まぁ、そこまできついのは出さないっしょ! 単純な one, two, three, ... tenの置き換えぐらいっしょ!と
甘くみていたのですが、そうではなく、six hundred eighty six thousand, seven hundred twenty oneみたいなのとかも出てきます。
こんな感じの英語表記全然みないぞ・・・(英語弱い勢)
僕の低レベルなプログラミング能力ではこれをパースするものはかけず、暴挙に出ました。

"""
Wolframというサイトにリクエストを投げ、数値を得るという方法をとります。
"""

早速スクリプト書いてGO!!
しばらく待って、・・・あれ・・・なんかフラグ出てないのに止まったぞ?
Time out(泣)

という結果になってしまい(予測できてはいたのですが・・・
仕方なく、one, two, three, ..., twenty, thirty , ...ninetyあたりは自分でパースします
それに加え、実行速度を上げるためにCythonを使います。

結果、時折失敗しますが(回線の問題で)だいたい通るのがかけました。
英語表記数字のパースの仕方がわからない人なので、もうだいぶむちゃくちゃな書き方しました
他の方のWriteUpみて、だいぶ恥ずかしくなりました
本当に汚いスクリプト(リモート、ローカルの分けもコメントアウトするだけのやつ)ですが、
一応のっけます

#-*- coding: utf-8 -*-
from requests import*
import socket
import re

HOST = "ctfquest.trendmicro.co.jp"
PORT = 51740
bufsize = 2048

DIGIT_STRINGS = {
    1: dict(enumerate("I II III IV V VI VII VIII IX".split(), 1)),
    2: dict(enumerate("X XX XXX XL L LX LXX LXXX XC".split(), 1)),
    3: dict(enumerate("C CC CCC CD D DC DCC DCCC CM".split(), 1)),
    4: dict(enumerate("M MM MMM".split(), 1)),
}

ROMAN_REGEX = re.compile(r"""
(?:(?P<_3000>MMM)|(?P<_2000>MM)|(?P<_1000>M))?
(?:(?P<_900>CM)|(?P<_800>DCCC)|(?P<_700>DCC)|(?P<_600>DC)
    |(?P<_500>D)|(?P<_400>CD)|(?P<_300>CCC)|(?P<_200>CC)|(?P<_100>C))?
(?:(?P<_90>XC)|(?P<_80>LXXX)|(?P<_70>LXX)|(?P<_60>LX)
    |(?P<_50>L)|(?P<_40>XL)|(?P<_30>XXX)|(?P<_20>XX)|(?P<_10>X))?
(?:(?P<_9>IX)|(?P<_8>VIII)|(?P<_7>VII)|(?P<_6>VI)
    |(?P<_5>V)|(?P<_4>IV)|(?P<_3>III)|(?P<_2>II)|(?P<_1>I))?
    $
""", re.VERBOSE | re.IGNORECASE)

eni = {"zero":"0", "one":"1", "two":"2", "three":"3", "four":"4", "five":"5", "six":"6", "seven":"7", "eight":"8", "nine":"9", "ten":"10",
 "eleven":"11", "twelve":"12", "thirteen":"13", "fourteen":"14", "fifteen":"15", "sixteen":"16", "seventeen":"17", "eighteen":"18", "nineteen":"19" 
,"twenty":"20", "thirty":"30", "forty":"40", "fifty":"50", "sixty":"60", "seventy":"70", "eighty":"80", "ninety":"90"}

# Roman => Integer
def roman_to_int(roman):
    if not roman:
        #raise ValueError("{} is not valid roman".format(repr(roman)))
        return roman
    m = ROMAN_REGEX.match(roman)
    if m is None:
        #raise ValueError("{} is not valid roman".format(repr(roman)))
        return roman
    ret = 0
    for key, value in m.groupdict().items():
        if value is not None:
            ret += int(key[1:])
    assert ret >= 0
    return ret

#English => Integer
def en2num(englishArray):
    #Wolframにリクエストを投げる
    uri = "http://www.wolframalpha.com/input/?i="
    englishText = ''.join(englishArray)
    #print "requesting " + (uri+englishText) + "..."
    res = requests.get(uri+englishText)
    result = re.search('javascript:showmathpop\(\'.+\'\)', res.text).group()
    #print "Result is " + str(result)
    #result = str(result.group()[24:32])
    result = re.sub(r'javascript:showmathpop\(\'(\d+)\'\)', r'\1', result)
    #print "Return : " + str(result)
    return str(result)

#English => Integer (1 or 2 digits)
def en2num_two_digits(en):
    if en.count(" ") >= 1:
        spl = en.split(' ')
        spl[0] = eni[spl[0]][0]
        spl[1] = eni[spl[1]]
        return str(spl[0]+spl[1])
    else:
        return eni[en]

#Parse Formula to evaluate formula.
def convert(q):
    splitted = q.split(' ')

    for (index, num) in enumerate(splitted):
        if not (num in list("+-*/1234567890") or re.search(r'\(\d+', num) or re.search(r'\d+\)', num)):
            splitted[index] = str(roman_to_int(num))

    splitted = ' '.join(splitted)
    splitted = re.sub('([\+\-\*\/\(\)])', r'@\1@' , splitted)
    splitted = re.split('@', splitted)

    for idx, spl in enumerate(splitted):
        splitted[idx] = splitted[idx].strip()
    for idx, spl in enumerate(splitted):
        if splitted[idx] in ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"]:
            splitted[idx] = eni[splitted[idx]]

    for idx, spl in enumerate(splitted):
        if re.match('^[a-zA-Z\s]+$', spl):    
            splitted[idx] = en2num(spl)

    return " ".join(splitted)


def solve():
    #Remote
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((HOST, PORT))

    while True:
        formula = s.recv(bufsize)
        print "Received: " + formula
        if re.match(r'Cong.*', formula):
            flag = s.recv(bufsize)
            print "Flag is " + flag
        formula = re.sub(r',', '', formula)
        formula = (convert(formula.split('=')[0])).strip()
        formula = re.sub(r' ', '', formula)
        #print "Formula Converted: " + formula
        formula = formula.replace('R', '(')
        formula = formula.replace('L', ')')
        #print "Finally formula = " + str(formula)
        s.send(str(eval(formula)))
        print "Sended: " + str(eval(formula.strip()))

    #Local
    """
    formula = "4,090 + ( one hundred ten thousand seventy six - 783,757 ) * ( sixty nine - two ) * sixty three - 2 + sixty three - 342 ="
    #print "Received: " + formula
    formula = re.sub(r',', '', formula)
    formula = (convert(formula.split('=')[0])).strip()
    formula = re.sub(r' ', '', formula)
    #print "Formula Converted: " + formula
    formula = formula.replace('R', '(')
    formula = formula.replace('L', ')')
    #print "Finally formula = " + str(formula)
    #print str(eval(formula))
    ##print "Sended: " + str(eval(formula.strip()))
    """

実行するときは

$ python setup.py build_ext --inplace
$ python
Python 2.7.10 (default, Jul 14 2015, 19:46:27)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.39)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import calcTMProg200
>>> calcTMProg200.solve()
...
省略
...
Congratulations!
The flag is TMCTF{U D1D 17!}

普通にハッシュテーブルをinitしておいて、あとは複雑な英語表記の解析をするというやり方が一番スムーズな気はします
そうすればCython使わなくても全然通るはず(笑)