ネットエージェント株式会社 オフィシャルブログ

愛甲健二

PCTF 2011 参戦記

 こんにちは、愛甲です。今回は、先週末に行われた PCTF 2011 も含めた、セキュリティコンテスト全般について書かせていただこうと思います。とはいっても、最近 CTF 参戦記ばかり書いている気がしますので(汗)、今回は少し違った視点でセキュリティコンテストについて考えてみたいと思います。「そもそも CTF って何?」という方は、前回の記事をご参照ください。

-----

■ PCTF 2011
blog2 PCTF とは、アメリカの PPP_CMU というチームが主催するセキュリティコンテストです。彼らは今年の CODEGATE 2011 CTF の優勝チームでもあり、この度、ロッキードマーティン社をスポンサーとして、CTF を開催することになりました。
 PCTF はオンラインで、日本時間の 4月23日(土)6:30 ~ 4月25日(月)6:30 までの 48 時間で行われました。それで、いきなりなのですが、sutegoma2 は 5 位という結果に終わりました。実は競技終了の 3 時間ほど前に一時暫定 1 位となったのですが、その後は得点が伸びずに…、といった具合で、結局は 5 位で終了となりました。まぁ結果は結果で仕方がないので、より勉強していこう、頑張っていこう、という新たな決意で再スタートしたいと思います。
 さて、前回の記事で問題の傾向や雰囲気について書きましたので、今回はまた別の角度から CTF について紹介したいと思います。

■競技時間
 実は CTF は主催国によって若干の時間的な有利不利が存在します。例えば、前回の CODEGATE 2011 CTF は韓国のチーム主催ですので、韓国の時間で実施しやすい 48 時間が競技時間として選ばれます。韓国と日本の時差はほとんどないので、日本時間にしても 3月4日(金)21:00 ~ 3月6日(日)21:00 までの 48 時間という、日本に住んでいる人にとって(というよりも韓国に住んでいる人にとって)有利な時間となりました。
 しかし、PCTF は開催国がアメリカですので、日本時間にすると 4月23日(土)6:30 ~ 4月25日(月)6:30 までの 48 時間という、月曜日の予定に大きく影響の出る時間帯となります。ほとんどの社会人は月曜日の朝から仕事がありますし、学生であっても月曜日の予定が確実に空くとは限りません。つまり、日本にとっては終盤に進むにつれて確実に不利になっていきます(逆に韓国主催の CODEGATE では、序盤がアメリカ不利となります)。
 よって、CODEGATE で予選 1 位になれたのは、時間的な優位性も少なからずあったのかもしれません。また逆に、次回 6 月に行われる DEFCON CTF では、アメリカ開催であるため、時間的に不利な立場になることが予想できます。

■出題される問題
 CTF にはセキュリティに関する全般的な問題が出題されます。@IT にて辻伸弘氏が「第1回 プレイ・ザ・ゲーム! CTFが問いかけるハックの意味」と題して、CTF の問題について紹介、解説をされていますので、興味がある方はぜひご参照ください。
 さて、前回の記事で、CTF には主に 6 つの分野から問題が出題されると書きました。それらは「暗号」「フォレンジック」「リバースエンジニアリング」「脆弱性」「パケット解析」「トリビア」の 6 つですが、今回は、これらとは別の角度で出題される問題を分類したいと思います。

●タイプ 1 :問題ファイルを解析するタイプ
 代表的なものはフォレンジックとパケット解析系の問題です。問題として与えられるファイルの中にパスワードがあるタイプで、例えば「 pkt ファイルを解析して、サーバからダウンロードされている実行ファイルの MD5 ハッシュ値を求めよ」といったものや、「 NTFS を解析して削除されたパスワードファイルを復元せよ」といった問題になります。
 サイズの大きな問題ファイルに対してのアプローチ方法、いかに巨大なファイルから欲しいデータを取りだすか、といった技術力が問われます。

●タイプ 2 :問題ファイルを復号するタイプ
 代表的なものは暗号問題ですが、バイナリ系の一部もこちらに属します。問題ファイルの中や問題文自体にパスワードが隠されているという点ではタイプ 1 と同じですが、こちらは探すことではなく、それらを「復号すること」がメインとなります。EXE ファイルの実行中にメモリ内を参照するとパスワードが見つかる、といったリバースエンジニアリング系の問題や、謎のデータ列が渡され、それを解読してパスワードを得る暗号系の問題をこのタイプに分類します。
 今年の CODEGATE では、予選と決勝の両方で、以下のような問題が出題されました。

 解読せよ:SCMPKBOUPDPHYTIAVIVRBTMVORUDNBDFNETDOIVTXRO
      UNDKOBFWBPVOEQLTGKKARACYCGDNAECBXIZIKPTLEER
      ZTYCYKIVXCPKPTPOVCAQRHRVKJUWMTWCMSXKADYHRVN
      AHCBRVSVSSCQCZQYDJXGSNRVSWCESTTBHIFCIASXRTA
      HKRRTUMVOKWITZPFZDISXZVVLGETPPLKSELDPGKELSH
      CBJBWXBIFCPEZYNBWXCDYMGAOVWNDKAKKKWBBQKPTIO
      DKMGGHRVVNHINFCQESDYMLACVVBWBBQROPBBDFOXOSK
      DIGZWXFNTKFYIICWHRVVNHIYILTKHRVXPISB

 これはコンピュータ技術に関するものではなく、純粋な暗号の問題になります。答えはヴィジュネル暗号なのですが、このような問題を解くためには実用的に使われる現代暗号のみではなく、古典暗号も含む、全般的な暗号に関する知識が必要になります。

●タイプ3 :リモート環境にあるパスワードを取得するタイプ
 代表的なものは Exploit と Web 系の問題です。Exploit 系の問題は、脆弱性を探し出し、権限を奪うことで Key ファイルへのアクセス権を獲得し、その Key ファイルの中に書かれてあるパスワードを取得します。Web 系の問題もほぼ同じですが、こちらは SQL インジェクションを始めとした Web に関するテクニックを用いて、例えばデータベースの中にあるパスワード等を得るタイプです。
 タイプ 1 やタイプ 2 とは明確に異なり、問題ファイルや問題文の中にはパスワードはなく、実際に外部のサーバや Web サイトを攻撃してパスワードを得る必要があります。リモート環境の権限を得る、データベースにアクセスする、といった分かりやすい目的であるため、ある意味もっともシンプルなタイプと言えます。
 問題文においても、サーバのIPアドレスとポートのみが書かれてあったり、問題となる Web ページの URL のみが書かれてある、といったシンプル具合です。

●タイプ4 :パスワードを推測するタイプ
 代表的なものはトリビア問題で、クイズのような問題がこのタイプに当てはまります。検索しなければ分からない問題や、業界の歴史やイベントなどを知ってないと解けないものも多いです。
 今回の PCTF では、トリビアとして以下の問題が出題されました。

 Figure out what the corrupted value is.
 1.3337 ~= XXXXXXX/3145727

 XXXXXXXに入る文字列が解答となるのですが、いかがでしょうか? ちなみにこの答えは「4195835」で、解説については Pentium FDIV bug を参照してください。個人的にはまさにトリビアらしく、かつ、なかなか興味深く、面白い問題だったと思います。

 CTF の問題は以上の 4 タイプに分けられますが、逆に考えれば、上記の 4 タイプのいずれにも当てはめ難いならば、それは CTF には使われ難い問題と言えます。またタイプ 4 はほとんどトリビア問題でしか扱われないため、実質、上記の 1~3 のタイプが考察対象となります。
 では、例えば、XSS を考えましょう。サーバ内にあるパスワードを XSS で取得するといった問題は作れませんので、タイプ 3 はありません。またタイプ 1 やタイプ 2 のような、htmlファイル内を探索、あるいは解読してパスワードを得るといったものも XSS の問題とは言えませんので無理でしょう。よって、XSS は CTF では扱いにくい問題と言えます。CSRF も同じ理由で難しいです。
 逆に、SQLインジェクション、またセッション管理系の成りすまし(Cookie関連)などはタイプ 3 に分類できますし、JavaScript の暗号化や難読化といった分野はタイプ 2 に分けられます。あと、バッファオーバーランを利用した Exploit や Pack された実行ファイルのリバースエンジニアリングなどは、比較的出題しやすい問題だと思います。
 出題し難い問題は、他に ARP スプーフィングのような TCP/IP 以下のレイヤーを扱うもの、また、ターゲットサーバのカーネルを対象とした問題なども出題が難しいです。そもそもカーネルを扱う問題自体、CTF ではほとんど出ません。
 このように考えていくと、CTF がどのような技術を競うもので、どういった分野を学んでおくことが重要であるかが分かってきます。このような「傾向」を考えてみるのも面白いかと思います。

■運用システムの問題
 CTF はセキュリティに関する様々な問題を扱いますが、分野は違えど、最終的に「解答となるパスワードを探す」という点においては同じです。これは極論すると、すべての問題が「与えられたファイルや情報からパスワードを探せ」というスタンスでしかなく、そのパスワードを submit したかどうかでのみ正解、不正解が決められます。
 これはシステムとして運用しやすく、オンラインでの開催が容易である反面、パスワードさえ分かってしまえば問題そのものを解く必要がないとも言えます。つまり、カンニングやチーム間での教え合いが容易に可能であり、またそれを制限できません。よって、その気になれば、1 位が確定したチームは 2 位以下のチームに解答パスワードを教えることで、そのランキングを自由に操作できますし、同じ国同士のチームで解答を共有し、ランキングを占有、なんてこともやろうと思えば可能です。
 そのような不正を防ぐために、CODEGATE CTF では決勝戦へ進むチームに対して、解答レポートの提出を義務付けています。これは、「どのようにして解答パスワードを得るに至ったか?」を書くレポートであり、予選終了後、3~4 日までに提出する必要があります。
 このレポート提出により、極端な不正は防止できると思いますが、ランキングの上位では数百点の差に 2~3 チームがひしめき合うなんてことは普通に起こり得ます。そして、それらのチームが予選通過の条件である 8 位以内に入るために 1~2 問程度を仲の良いチームと交換する、といったことが起こらないとは限らず、またそれを防ぐことは難しいです。これは、運営や不正を行うチームが悪いと言っているわけではなく(まぁ不正は良くないですが…)、現状の CTF のシステム的な限界だと思います。それも踏まえて、決勝戦へ進むためには、予選においてある程度「余裕勝ち」しておく必要があるのかもしれません。

■その他のセキュリティコンテスト
 セキュリティコンテストは CTF 以外にもたくさんあります。例えば、韓国のセキュリティカンファレンス Power of Community 2010(以下、POC)で、Hacker's Dream という CTF と同じオンライン型のセキュリティコンテストが開かれました。ただ、ルールは CTF とは大きく異なり、解答をレポートにまとめて submit するという、また少し違った形式のコンテストだったようです。ちなみに、2010 年は日本人が優勝しており、優勝者である大居氏のブログからコンテストの詳細が確認できます。
 他にも、詳しくは知らないのですが、F-Secureリバースエンジニアリングチャレンジ、Panda Challengeといった、カンファレンスではなく企業が実施しているセキュリティコンテストも多くあります。
 CTF もそうなのですが、ここ 2~3 年でセキュリティ系のコンテストは数多く実施されるようになりました。参加資格が国内在住者限定のものも多いですが、オンライン上で、誰でも参加できるものもあるため、力試しに参加してみても面白いかもしれません。

東北地方太平洋沖地震 お知らせ

 このたびの東北地方太平洋沖地震により被害を受けられました皆様に心よりお見舞い申し上げます。お一人でも多くの人命の救済と、一日も早い復旧を心よりお祈り致します。

 本日以降の計画停電により、弊社業務につきましても通常より縮小したかたちで行うこととなりました。弊社サイトおよび各種ソフトウェアのアップデート等の停止や、製品出荷、サポート業務の提供におきましても支障をきたす場合が予想されます。ご迷惑をおかけ致しますことをお詫び申し上げますとともに、ご理解をお願いいたします。

 今後の状況につきましては、明らかになり次第適宜当社サイトに掲載して参ります。また当ブログに関しましても、誠に恐縮ですが、状況が安定するまでお休みとさせていただきたいと思います。何卒よろしくお願いいたします。

CODEGATE 2011 CTF 参戦記

 こんにちは、愛甲です。今日は、先週末に行われたセキュリティ技術を競い合う世界大会 CODEGATE 2011 CTF(Capture the Flag) について書かせていただこうと思います。

-----

■ CODEGATE 2011 CTF とは
 CODEGATE 2011 CTF とは、韓国のセキュリティカンファレンス CODEGATE が主催しているセキュリティ技術の世界大会であり、毎年 3 ~ 4 月頃に世界中のセキュリティエンジニアが優勝賞金 2000 万ウォンを賭け、その技術力を競います。毎年 300 近いチームがエントリするため、本大会は予選と決勝に分かれており、予選で勝ち残った上位 8 チームで改めて決勝戦を行います。
 決勝戦は CODEGATE の会場で行われますが、予選はオンラインで実施されるため、今週末は弊社のセミナールームを借り、3月4日(金)21:00 ~ 3月6日(日)21:00 までの 48 時間、チーム名 sutegoma2 で CODEGATE 2011 CTF の予選を戦うことになりました(参考)。
 sutegoma2 というのは、tessy 氏yoggy 氏が中心となって運営している Web コミュニティで、主に世界中で開催されるセキュリティコンテストに参加することを目的としています。CTF 勉強会と並行して運営されており、CTF に参加したい、勉強したいといった人たちのコミュニティとなっています。勉強会から発足したコミュニティなので、学生、社会人問わず誰でも参加でき、企業の垣根を越えて、純粋にセキュリティ技術に興味のある方々で構成されています。
 また今回エフセキュアブログのメンバーである福森氏も sutegoma2 に参加しており、今回の参戦記を記事にされていますので、ぜひご確認ください。

■大会のルールは?
 大会は、運営チームから出題されるセキュリティに関する問題を解いていくことで点数を獲得し、獲得点数の合計値によって最終的なランキングが決定します。
 昨今のセキュリティ技術は多様化が進んでおり、一概に何がセキュリティ技術で、何がセキュリティ技術ではないのか、その切り分けが難しいところですが、一般的なセキュリティコンテストにおいては、大きく以下の 6 分野に分けられます。
  1. 暗号技術
  2. デジタル・フォレンジック
  3. リバースエンジニアリング
  4. 脆弱性調査
  5. パケット解析
  6. トリビア
 大会によって若干の差異はありますが、大体、上記 6 分野に分けられ、各分野に対してレベル 100 ~ 500 の難易度が設定された 5 問(× 6 分野 = 合計 30 問)が出題されます。これを 48 時間以内に解答し、ランキング 1 位を目指します。そして、そのランキングの上位 8 チームが決勝進出となります。

■技術力だけの勝負ではない
crypto100 世界大会というと、技術的難易度の高い問題ばかりが出題されるイメージがありますが、実際には、技術力以外のスキルも必要になります。今回は CODEGATE 2011 CTF 予選にて出題された問題の中から、弊社代表の杉浦が解いた Crypto 100 と、研究開発部の長谷川がセキュそば勉強会へ行く前日に解いた Binary 100 を紹介します。
 まず Crypto 100 ですが、右図がその問題です。この画像からパスワードとなるキーを探します。暗号の問題ではありますが、暗号の専門的知識は必要なく、ひらめきさえすればコンピュータを知らない方でも解けます。せっかくなので 1 分ほど考えてみてください。

 正解は「携帯電話のキー入力」です。携帯電話のキーパッドを確認すると、アルファベットは各数字に以下のように対応しています。

 1: 2: ABC  3: DEF 
 4: GHI  5: JKL  6: MNO 
 7: PQRS  8: TUV  9: WXYZ 

 最初は 444 なので I 、2番目は 66 なので N 、といった具合にアルファベットに変換していくと、解答を含む文字列(英文)に変換されます。そこに適切に空白を入れると以下になります。

in cryptography asubstitution cipher is a method of encryption by which units of plain text are replaced with cipher text according to a regular system the units maybe single letters pairs of letters triplets of letters mixtures of the above this cipher text is encrypted by telephone keypad so we call this keypad cipher

 以上から、keypad cipher が解答となります。
 この問題は、言われてみれば「ああ、なるほど」と思いますが、実際にこのデータ列を前にしてすぐにピンとくるかというと、やはり個人差があります。技術力以外の何か、そういうものも重要であると実感させられる問題でした。

■だけど技術ももちろん必要
script そうこう言いつつも、やはりセキュリティ技術を競う大会ですから、技術力も必要です。次はまさに技術力で解答する Binary 100 を紹介します。
 Binary 100 はテキストファイルですが、エディタで開くと良く分からない記号の羅列が表示されます(右図)。まずはこの記号の羅列が何なのかを探る必要がありますが、この記号の羅列を一目見て「ああ、これは Windows Script Encoder だね」と言ったのは、次の日に朝一でセキュそば勉強会へ参加する予定があるにも関わらず、深夜の 2 時まで CTF に付き合ってもらった長谷川でした。
 このエンコーダに対してはデコーダ(Windows Script Decoder)が存在し、これを使うことで、データを元に戻します。元に戻すと、次は難読化された JavaScript らしきコードが見つかります。NetAgent Security Contest 2010 の Level7 を解く際に dojo で関数フックする方法を紹介しましたが、それを用いて、最終的な JavaScript コードを出力させます。すると CodeGate_JavaScriptEncode_Key という文字列が入った JavaScript が得られます。この JavaScript の末尾に alert(CodeGate_JavaScriptEncode_Key) のようなコードを追加して実行すると、120a151156120a163t111163120lea163u162e! という文字列が得られます。最初はこれが解答かと思いましたが、良く見ると、3 桁で区切られる数値が見えます。

 「120 a 151 156 120 a 163 t 111 163 120 lea 163 u 162 e!」 

 これは 10 進数に見えますが、正解は 8 進数でした。3 桁の数値を 8 進数で変換すると、PainPastIsPleasure! という文字列が得られます。これが正解となりました。

■勉強になる
 Crypto 100 と Binary 100 を紹介しましたが、いかがだったでしょうか。さて、何か得意げに解説させていただきましたが、実は私は競技時、この 2 問はどちらもまったく解けていませんでした(汗)。なので、ただ二人を眺めながら「すごいなぁ…」と驚いていただけなのですが、個人的には CTF の醍醐味はまさにここにあると思っています。
 自分一人では絶対に分からない問題にぶつかったとしても、その問題を他の誰かが解き、その解答方法を知ることで、いつの間にか自分も同じ問題を解けるようになっています。それはつまり、CTF という競技を通して、チーム内で技術や知識を教え合っているとも言えます。
 さらに、競技が終了すれば、各チームがそれぞれ解答を Web に公開し始めます。当然、各チームとも同じ解答方法ではありませんし、自チームでは解けなかった問題の解答なども読めます。競技中はチーム内で技術を共有し、終了後はチーム間で技術を共有します。まさに、世界中のチーム同士で技術や知識を教え合っている状態と言えます。個人的には、これほど効率がよく、充実した学習方法はまずないと思っています。
 とは言いつつも、やはり問題が解けないとつまらなかったり、48 時間も PC に向かい続けるのは辛いと感じることもあります。ただ、より多くの問題を解けるのは技術力の証明になると思いますが、仮に 1 問も解けなかったとしても、それが技術力の無さには繋がりません。CTF に求められるスキルセットと自身が持つ技術が異なっていることもありますし、そもそも CTF に合わない(問題として出題しにくい)技術もあります(XSS や kernel関系など)。また、48 時間ずっと起きている人も中にはいますが、基本的には仮眠を取りつつやっていく人の方が多いです(眠いとやはり集中力が持たない)。
 なので、あまり気負わずに、普通の勉強会に参加する感じでトライしてみるのも良いのではないかと思います。

■最後に結果を少し
rank 48 時間に及ぶ長い闘いでしたが、私たち sutegoma2 は見事予選を 1 位で通過できました。トップランキングを見ると分かりますが、アメリカがもっとも強く、次いで韓国チームが多いです。やはり IT 立国の強さは伊達じゃありません。しかし、そんな中でも日本やスウェーデンがトップランキングに入っていることを考えると、アメリカや韓国以外の国もまだまだ頑張れると感じます。そして、日本の技術力もまた、十分に世界に通用することを実感できた結果でもありました。
 しかし、この結果に満足せず、さらなる上を目指し、これからも一歩ずつ努力していきたいと思います。

 さて、最後になりますが、予選終了から今日で 3 日目なので、他の参加チームから徐々に writeup が公開され始めています。もし今回紹介した問題以外にも興味があれば、ぜひ読んでみてください。

Oracle padding attacks (Codegate crypto 400 writeup)
Codegate CTF 2011 Vuln300 Writeup
CODEGATE YUT 2011: Issue 500 writeup
Some mini writeups on Codegate 2011 Prequals: Issue100,200, Net100,200, Crypto100,200.
Codegate CTF 2011 Forensic 300, Issue 300

ptraceを使ったデバッギング

 こんにちは、愛甲です。今回は、リバースエンジニアリングを行う際に有用な ptrace と udis86 を用いたデバッギングについて解説させていただこうと思います。なお、本記事は Ubuntu Linux (x86) で動作確認を行っています。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 10.04.1 LTS
Release: 10.04
Codename: lucid

-----

■ ptrace とは
 ptrace は、他プロセスの制御を行う手段として提供されているシステムコールです。Manpage of PTRACE に詳しい解説が載っており、主にデバッガを作成する際に利用されます。しかし ptrace だけでは逆アセンブルができないため、今回は逆アセンブルライブラリである udis86 も使います。udis86 は x86( x86_64 含む) 用の逆アセンブルライブラリで、ファイル、もしくはメモリ上からデータ列を読み込み、逆アセンブルした結果を文字列として返します。
 これらを利用することで、デバッガに似たプログラムを作成できます。今回、本記事用のサンプルプログラムとして ptracer.c を用意しました。以降はこのソースコードを元に解説を進めますので、適宜コードを参照しながら読み進めてください。

■トレースログの出力
 ptracer は、他プロセスを実行した際に「実際に処理されたアセンブラコード」を逐次出力するプログラムです。OllyDbg のような一般的なデバッガにも同じような(トレースログを出力する)機能を持つものがあり、それらとほぼ同等の機能と考えてください。
 仕組みとしては、まず fork で子プロセスを作成し、その後に execve によりターゲットプログラム(プロセス)に成り替わるのですが、その前に PTRACE_TRACEME を実行し、他プロセスからの ptrace アクセスの許可をしておきます。これで、親プロセスから子プロセスへ、任意のタイミングでアタッチ可能となります。
 子プロセスがターゲットプログラムに成り替わったら、親プロセスから実行を制御します。PTRACE_SINGLESTEP で 1 命令ずつ進めながら、PTRACE_GETREGS でレジストリを確認します。現在実行中のアドレスは regs.eip で取得できるため、これを元に逆アセンブルし、実行されたマシン語をアセンブラコードとして出力します。アタッチしている他プロセスのアドレス空間のデータを「読む」ためには PTRACE_PEEKDATA を使います。
 以下のサンプルプログラムを作成し、そのトレースログを出力します。

$ cat target1.c
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
if(argc != 2){
printf("%s <PASSWORD>\n", argv[0]);
return 1;
}
if(strcmp("HELLO", argv[1]) == 0)
printf("OK!\n");
else
printf("ERR\n");
return 0;
}
$ gcc -Wall target1.c -o target1
$ ./target
./target <PASSWORD>
$ ./target ABCD
ERR

 target1 は、引数に HELLO を渡さなければエラーを返すプログラムです。これを ptracer を使って逆アセンブルします。なお、他のライブラリ内での処理は無視したいので、target1 のテキストセクションの領域をあらかじめ調べておき、その領域内においてのみの逆アセンブル結果を表示します。逆アセンブルの領域を指定するには -a オプションを使います。

$ readelf -S target1 | grep text
[14] .text PROGBITS 080483a0 0003a0 0001bc 00 AX 0 0 16
$ ./ptracer -a 080483a0 0001bc target1 ABCD
080483a0: 31ed xor ebp, ebp
080483a2: 5e pop esi
(省略)
08048498: 85c0 test eax, eax
0804849a: 750e jnz 0x10
080484aa: c7042499850408 mov dword [esp], 0x8048599
080484b1: e8befeffff call 0xfffffffffffffec3
ERR
080484b6: b800000000 mov eax, 0x0
080484bb: c9 leave
080484bc: c3 ret

 ERR が表示される前に、0804849a で jnz 命令が処理されています。おそらくこの部分が、ERR と OK! のどちらを表示するかを判断している箇所だと考えられます。

■任意のアドレスの値を書き換え
 実際に処理されたアセンブラコードを逐次出力するだけならばデバッガのトレースログでもよいのですが、解析をやっていると、実行されるマシン語をリアルタイムに改ざんし、挙動を変更させたい場合があります。アタッチしているプロセスのアドレス空間へデータを「書く」ためには PTRACE_POKEDATA を使います。
 実行されるマシン語のリアルタイムの改ざんは、-w オプションを使うことで実現できます。

$ ./ptracer -a 080483a0 0001bc -w 0804849a 75 74 target1 ABCD
080483a0: 31ed xor ebp, ebp
080483a2: 5e pop esi
(省略)
08048498: 85c0 test eax, eax
0804849a: 750e jnz 0x10
0804849c: c7042495850408 mov dword [esp], 0x8048595
080484a3: e8ccfeffff call 0xfffffffffffffed1
OK!
080484a8: eb0c jmp 0xe
080484b6: b800000000 mov eax, 0x0
080484bb: c9 leave
080484bc: c3 ret

 今度は処理が変わり OK! が表示されました。
 リアルタイム改ざん機能は、その命令が実行される直前に書き換えられ、命令の実行が終わった直後に元の値に戻されます。よって逆アセンブル結果は 750e のままですが、実際にCPUが処理を行ったのは 740e というマシン語命令です。
 このように実行の直前と直後に変更を加えることで、コードの改ざん検知(あらかじめテキストセクションのコードが変更されていないことを確認して処理を開始するアンチデバッギングテクニック)を回避できます。

■forkされた子プロセスを追う
 サーバ系のプログラムだと、重要な通信はすべて fork された後の子プロセスに任せられ、親プロセスは accept で待つだけの役割を担う場合が多いです(とは言っても、最近のサーバ系プログラムはほぼすべてスレッド実装なので子プロセスを追う必要もありませんが…)。このようなプログラムの場合、重要なのは親プロセスよりもむしろ子プロセスなので、fork されたら、デバッグする対象を子プロセスに切り替えなければいけません。
 fork や write といった「システムコール呼び出しをフックする」には PTRACE_SYSCALL を使います。ptracer では、システムコール呼び出しである int 80h を発見するまでステップ実行を行い、発見したら PTRACE_SYSCALL を呼び出して、システムコール呼び出し前と呼び出し後に処理を行っています(環境によっては int 80h が sysenter だったり、syscall であったりします)。
 では、以下のサンプルプログラムを作成します。

$ cat target2.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>

int main(int argc, char *argv[])
{
int st;
if(argc != 2){
printf("%s <PASSWORD>\n", argv[0]);
return 1;
}
if(!fork()){
if(strcmp("HELLO", argv[1]) == 0)
printf("OK!\n");
else
printf("ERR\n");
exit(0);
}
wait(&st);
return 0;
}
$ gcc -Wall target2.c -o target2
$ ./target2 ABCD
ERR

 fork している部分以外は target1 と変わりません。ただ、target2 では、子プロセスを追わなければ出力結果を変えられません。fork 後の子プロセスを追いかけるためには -f オプションを使います。-f の後に DETACH と入力すれば「親プロセスをデタッチして」子プロセスを追いかけ、KILL と入力すれば「親プロセスを強制終了して」子プロセスを追いかけます。

$ ./ptracer -a 08048430 0001ec -f DETACH target2 ABCD
08048430: 31ed xor ebp, ebp
08048432: 5e pop esi
(省略)
08048531: 85c0 test eax, eax
08048533: 750e jnz 0x10
08048543: c7042459860408 mov dword [esp], 0x8048659
0804854a: e89dfeffff call 0xfffffffffffffea2
ERR
0804854f: c7042400000000 mov dword [esp], 0x0
08048556: e8c1feffff call 0xfffffffffffffec6

 子プロセスを追いかけて ERR が表示されました。では、さらに子プロセスの 08048533 の値 750e を 740e に変更します。これで OK! が表示されます。

$ ./ptracer -a 08048430 0001ec -w 08048533 75 74 -f DETACH target2 ABCD
08048430: 31ed xor ebp, ebp
08048432: 5e pop esi
(省略)
08048531: 85c0 test eax, eax
08048533: 750e jnz 0x10
08048535: c7042455860408 mov dword [esp], 0x8048655
0804853c: e8abfeffff call 0xfffffffffffffeb0
OK!
08048541: eb0c jmp 0xe
0804854f: c7042400000000 mov dword [esp], 0x0

 無事、子プロセスの処理を変更できました。
 このように ptrace を使うことで、かゆいところに手が届く感じで解析を進められます。もちろん、デバッガや逆アセンブラをメインで使っていくのはこれまでと変わりませんが、ptrace の使い方を知っておくだけで、思わぬところで楽ができます。
 では最後に ptrace を用いて、他プログラムの関数を呼び出すサンプルを解説して終わろうと思います。

■他プログラムのコードを利用する
 ptrace をうまく使うと、他プロセスのコードを横取りして利用できます。以下のサンプルコードを見てください。

$ cat target3.c
#include <stdio.h>
#include <string.h>

unsigned long f(char *x, int y)
{
unsigned long a;
char b;
int i;

a = 0xFFFFFFFF;
if(y == 0)
return ~a;

while(1){
b = *x;
a = a ^ b;
x++;

for(i=0; i < 8; i++){
if(a & 1){
a = a >> 1;
}else{
a = a >> 1;
a ^= 0xEDB88320;
}
}
y--;
if(y == 0)
break;
}

return ~a;
}

int main(int argc, char *argv[])
{
if(argc != 2){
printf("%s <PASSWORD>\n", argv[0]);
return 1;
}
if(f(argv[1], strlen(argv[1])) == 0xd59b8359)
printf("OK!\n");
else
printf("ERR\n");
return 0;
}

$ gcc -Wall target3.c -o target3
$ ./target3 ABCD
ERR

 target3 は argv[1] を引数として関数 f を呼び出し、その戻り値を d59b8359 と評価しています。見ての通り、関数 f は不可逆なので、出力が d59b8359 となる入力データを復号できません。よって、もし入力データを知りたい場合は総当たりによるパスワードクラック等を行う必要がありますが、そのような場合に ptrace は有効です。ちなみに入力データは同じく HELLO です。
 まずは関数 f を呼び出したいので、f のアドレスを調べます。また一時的に処理を止めておきたいアドレスも決めます。これはどこでも良いので、適当に main 関数の先頭にしておきます。

$ objdump -d target3 | grep \<f\>:
08048454 <f>:
$ objdump -d target3 | grep \<main\>:
080484c0 <main>:

 関数 f は引数を 2 つとります。第 1 引数に入力データ、第 2 引数に入力データのサイズが入りますので、第 1 引数を HELLO、第 2 引数を 5 として、スタックにこれらを積み、f を呼び出します。
 以上の条件を設定したプログラムが pcaller.c です。target3 を渡して実行すると、target3 プロセス内にある関数 f を実行した結果が出力されます。

$ ./pcaller target3
retn: d59b8359

 このように ptrace は使い方によっていろいろと面白いことが可能です。Manpage of PTRACE を眺めると、今回紹介した機能以外にも様々なオプションがあるので、興味があればぜひ試してみてください。

NetAgent Security Contest 2010 解答 Level5 ~ Level8 (1/2)

 こんにちは、愛甲です。今回は、2010年11月27~28日に当サイトで行われたNetAgent Security Contest 2010のLevel5~Level8までの解答を行いたいと思います。問題文とファイルも公開していますので、興味がある方、またはコンテストに参加できなかった方は、よろしければ挑戦してみてください。

-----

■Level5 Crack ZIP files (FILE)
NASecConPic13 Level5は、Level2で見つけた暗号化されているxyz.zipファイルを解析し、中に入っているhoukoku.zipを取り出す問題です。まずポイントとして、xyz.zipの中にはa.txt、kaigi1.jpg、kaigi2.jpgが入っていること。そして、これらはサイズから考えてもxyz.zipと同じディレクトリにあるものと同一であるということが挙げられます。
NASecConPic11 つまり、xyz.zipの中の一部のファイルに関しては、同一のものがすでに手元にある状態と言えます。また、ヒントの「Paul C. Kocher」と「zip」という単語で検索すると、Eli Biham & Paul C. KocherのZIPクラックに関する論文にたどりつきます。
 詳細な理論は論文を読んでいただくとして、内容を簡単に説明すると、暗号化されたZIPファイルの中に存在する任意のファイルと同一のファイルをすでに保持している場合、ZIP内のそれ以外のファイルも抜き出せる(復号できる)可能性がある、というものです。そして、それを行うツールpkcrackも公開されています。
 今回の場合は、暗号化されたZIPファイルの中に存在する任意のファイルと同一のファイルとして、「a.txt」「kaigi1.jpg」「kaigi2.jpg」の3つがあります。この3つのいずれかを利用して、それ以外のファイル、つまりhoukoku.zipを取り出します。

# unzip xyz.zip
Archive: xyz.zip
[xyz.zip] houkoku.zip password:
skipping: houkoku.zip incorrect password
skipping: a.txt incorrect password
skipping: kaigi1.jpg incorrect password
skipping: kaigi2.jpg incorrect password

 見ての通り、パスワードロックがかかっています。上記のツール(pkcrack)を使用して、xyz.zipの中のhoukoku.zipを取り出します。

# ./pkcrack-1.2.2/src/pkcrack -C xyz.zip -c kaigi1.jpg [折返し]
-P kaigi1.zip -p kaigi1.jpg -d houkoku.zip
Files read. Starting stage 1 on Sat Nov 20 16:22:23 2010
Generating 1st generation of possible key2_57064 values...done.
Found 4194304 possible key2-values.
Now we're trying to reduce these...
Lowest number: 879 values at offset 45937
Lowest number: 864 values at offset 45925
(省略)
Lowest number: 337 values at offset 44136
Done. Left with 337 possible Values. bestOffset is 44136.
Stage 1 completed. Starting stage 2 on Sat Nov 20 16:23:23 2010
Ta-daaaaa! key0=dd26feae, key1=12282822, key2=b236d71f
Probabilistic test succeeded for 12933 bytes.
Ta-daaaaa! key0=dd26feae, key1=12282822, key2=b236d71f
Probabilistic test succeeded for 12933 bytes.
(省略)
Ta-daaaaa! key0=dd26feae, key1=12282822, key2=b236d71f
Probabilistic test succeeded for 12933 bytes.
Stage 2 completed. Starting zipdecrypt on Sat Nov 20 16:23:38 2010
Decrypting houkoku.zip (c6984a31f30fd10fda881fcb)... OK!
Decrypting a.txt (82b394ae79f29601b935fa40)... OK!
Decrypting kaigi1.jpg (2c6c04366c4b29a5d1872844)... OK!
Decrypting kaigi2.jpg (66d60c194f07f4643b743fba)... OK!
Finished on Sat Nov 20 16:23:38 2010

 kaigi1.jpgを利用して暗号化されたxyz.zipの中からhoukoku.zipを取り出しました。houkoku.zipはロックがかかっていないただのZIPファイルであるため、展開し、パスワードを得られます。

# unzip houkoku.zip
# cd houkoku
# ls
?+?q???X?g.xls ?R?s?[Book1.xls
# strings * | grep Password
Password: sangokushi38
Password: sangokushi38

 xlsファイルなので本当はExcelで開く必要がありますが、解答パスワードを得るだけなら、stringsコマンドを用いてもよいです。上記の結果から、解答パスワードは「sangokushi38」です。
NASecConPic12 模範解答は上記に記述した通りですが、問題文を完全に無視し、純粋にフォレンジックを行うことでも、この問題は解答できます。実は、配布されたイメージファイル(Forensics.img)には、未使用領域にhoukoku.zipが置かれています。つまり、未使用領域を探せばhoukoku.zipそのものが得られます。
 右図のページ(KEYWORD SEARCH)より、houkokuをキーワード検索します。そして見つかったデータをダンプするとhoukoku.zipと同じものとなり、展開するとxlsファイルが得られ、解答パスワード「sangokushi38」が得られます。

■Level6 UnPack EXE (FILE)
6 Level6はパックされた実行ファイルを解析し、解答パスワードを得る問題です。
 パックされているため、IDAProでは解析が難しく、OllyDbgではパッカーのデバッガ検知ルーチンが働いてうまく実行できないため、WinDbgを使います。WinDbgでは正常に実行できますが、今度はIsDebuggerPresent関数の呼び出しによりデバッガ検知されるため、まずはIsDebuggerPresent関数にブレイクポイントをセットして実行します。1度目のIsDebuggerPresent関数呼び出しは無視し、2度目の呼び出しの際に、呼び出し元まで処理を進めます。

// WinDbg
00403889 call dword ptr [ScriptGame+0x11e094 (0051e094)]
0040388f test eax,eax
00403891 jne ScriptGame+0x38d2 (004038d2)
00403893 mov eax,dword ptr [ebp-15Ch]
00403899 cmp eax,offset +0x270e (0000270f)
0040389e jg ScriptGame+0x38c8 (004038c8)
004038a0 cmp edi,offset +0x270e (0000270f)
004038a6 jg ScriptGame+0x38c8 (004038c8)
004038a8 cmp dword ptr [ScriptGame+0x4a6794 (008a6794)],0
004038af jne ScriptGame+0x36c0 (004036c0)

 デバッガ検知ルーチンの後に、0000270eという数値とcmpされています(00403899)。0000270eは10進数で9999であるため、ここが試合数の比較処理だと推測できます。EIPをjgのジャンプ先004038c8に変更すると、強制的にゲームセットとなり、解答パスワードが表示されます。
NASecConPic14 解答パスワードは「45MM3R」となります。また、当問題で使われているパッキング技術については TLS Callbacks を参照してください。
 この問題は TLS Callbacks により、エントリポイントより前の段階でテキストセクションの復号処理を行っています。

C:\>dumpbin /HEADERS /TLS ScriptGame_pro.exe
Microsoft (R) COFF/PE Dumper Version 10.00.30319.01
Copyright (C) Microsoft Corporation. All rights reserved.
(省略)
TLS Callbacks

Address
--------
008A9382
00000000

 008A9382が、エントリポイントへ進む前に実行されます。OllyDbgなどの動的デバッガで開くと以下のコードが見つかります。

// 最初の1度だけ実行されるようにフラグを確認
008A9382 CALL ScriptGa.008A9387
008A9387 POP EAX
008A9388 CMP DWORD PTR DS:[EAX+BF],1
008A9392 JNZ SHORT ScriptGa.008A9397
008A9394 RETN 0C

008A9397 PUSH EBP
008A9398 PUSH ESI
008A9399 PUSH EDI
008A939A CALL ScriptGa.008A939F
008A939F POP EBP
008A93A0 MOV ESI,EBP
008A93A2 XOR EAX,EAX
008A93A4 CDQ
008A93A5 MOV ECX,EDX
008A93A7 SUB ECX,1
008A93AD SHL ECX,9
008A93B0 AND ESI,ECX
008A93B2 INC EDX
008A93B3 SHL EDX,9

// 5A4Dh("MZ")を頼りにモジュールの先頭を探索
008A93B6 CMP WORD PTR DS:[ESI],5A4D
008A93BB JNZ SHORT ScriptGa.008A93D2
008A93BD MOV EAX,DWORD PTR DS:[ESI+3C]
008A93C0 MOV EBP,ESI
008A93C2 LEA EBP,DWORD PTR SS:[EBP+EAX]
008A93C6 MOV EAX,DWORD PTR SS:[EBP]
008A93C9 NOT EAX
008A93CB CMP EAX,FFFFBAAF
008A93D0 JE SHORT ScriptGa.008A93D9
008A93D2 SUB ESI,EDX
008A93D4 JMP ScriptGa.008A93B6

// エントリポイントがint 3hなら終了(OllyDbg 1.xx対策)
008A93D9 MOV EAX,DWORD PTR SS:[EBP+28]
008A93DC OR EAX,ESI
008A93DE CMP BYTE PTR DS:[EAX],0CC
008A93E1 JNZ SHORT ScriptGa.008A93EC
008A93E3 MOV BYTE PTR DS:[EAX],0C3
008A93E6 POP EDI
008A93E7 POP ESI
008A93E8 POP EBP
008A93E9 RETN 0C

008A93EC XOR ECX,ECX
008A93EE MOV ECX,1
008A93F3 SHL ECX,6
008A93F6 XOR EDI,EDI

// 復号キーを作成(EDI=b31ce937)
008A93F8 MOV EAX,DWORD PTR DS:[ESI+ECX*4]
008A93FB XOR EDI,EAX
008A93FD ROR EDI,4
008A9400 ROR EAX,9
008A9403 XOR EDI,EAX
008A9405 LOOPD SHORT ScriptGa.008A93F8

// テキストセクションのアドレスとサイズを取得
008A9407 ADD EBP,18
008A940D MOV EDX,DWORD PTR SS:[EBP+14]
008A9410 MOV ECX,DWORD PTR SS:[EBP+4]
008A9413 OR EAX,ESI
008A9415 OR ESI,EDX
008A9417 ADD ECX,-0C1
008A941D XOR EAX,EAX
008A941F MOV EBP,ESI

// テキストセクションを復号
008A9421 MOV EAX,EDI
008A9423 XOR BYTE PTR DS:[ESI],AL
008A9425 XCHG EBP,EDI
008A9427 MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]
008A9428 XCHG EBP,EDI
008A942A ROR EDI,8
008A942D DEC EDI
008A942E LOOPD SHORT ScriptGa.008A9421

008A9430 POP EDI
008A9431 POP ESI
008A9432 POP EBP
008A9433 CALL ScriptGa.008A9438
008A9438 POP EAX

// このコードが実行されたことを示すフラグをONにする
008A9439 MOV DWORD PTR DS:[EAX+E],1
008A9440 RETN 0C

 復号を行う前にOllyDbg 1.xx対策として、エントリポイントのint 3hを評価しています。つまり、もしエントリポイントが0xCCならばプログラムを終了します。
 この演算を行うアンパッカーを作成すれば、元々のテキストセクションが得られます。

■Level7 JavaScript (FILE)
NASecConPic15 難読化されたJavaScriptを解読する問題です。「データ(文字列)をJavaScriptとして実行する」方法については、「難読化JavaScriptで利用可能なテクニック」を参照してください。
 この問題のポイントは、文字列をJavaScriptとして実行するタイミング(関数呼び出し)を捉えることです。
NASecConPic16 では、解析を始めます。FirefoxのアドオンであるFirebugを使います。適当な場所にブレイクポイントを仕掛けて、GOボタンをクリックし、ステップオーバー(F10)で処理を進めていくと、1160行目で、_(アンダーバー)変数に怪しいデータが展開されます。これは6文字で構成されたJavaScriptです。
 1165行目からのコードには ','(カンマ) が多く使われています。カンマで1165行目以降のソースコードを区切っていくと、以下になります。

__[$.$$_]+((+[])[$.$_]+"")[$.$__$]+"="+(+[]),
-[],
+[],
/{/[$.$$$_+__[$.$__]+$.$$$_+$.$$__]('}'),
_,
+($._$$+""+$.$___+$.$__),
/{/[$.$$$_+__[$.$__]+$.$$$_+$.$$__]('}'),
(!""+"")[-~[]]+(![]+"")[$._$$]+$.$_$_+"-"+$.$$_$+$._+$.$_$_+(![]+"")
[$._$_]+"-"+(!""+"")[$._$_]+(![]+"")[$._$$]+$.$$$_)

 難読化されていますが、これは各行が引数になっています。この直前まで処理を進めた後に、最後の引数である (!""+"")[-~[]]+(![]+"")[$._$$]+$.$_$_+"-"+$.$$_$+$._+$.$_$_+(![]+"")[$._$_]+"-"+(!""+"")[$._$_]+(![]+"")[$._$$]+$.$$$_) をalertで表示すると「rsa-dual-use」という文字列になっています。この文字列を検索すると、crypto.generateCRMFRequestに関するサイトがヒットします。「難読化JavaScriptで利用可能なテクニック」でも紹介されていますが、crypto.generateCRMFRequestを利用したものだと分かります。
 次に6文字で構成されたJavaScriptですが、まずは「記号だけのJavaScriptプログラミングの基本原理」を参照してください。ポイントは「JavaScriptは記号を組み合わせて十分な種類の文字を作り出せる」という点です。記号を組み合わせて平文なJavaScriptを作成したら、evalや前述のcrypto.generateCRMFRequestなどを利用してコード実行します。
 6文字JavaScriptはfirebugでは追いにくいので、JavaScriptをコードとして実行する関数をフックして特定します。dojoライブラリとfirebugを使います。

<script src="dojo.xd.js" type="text/javascript"></script>
<script>
dojo.connect(Number, "constructor",
null, function(a) {
console.debug("constructor:" + a);
});
dojo.connect(null, "setTimeout",
null, function(a) {
console.debug("setTimeout:" + a);
});
dojo.connect(null, "Function",
null, function(a) {
console.debug("Function:" + a);
});
dojo.connect(null, "eval",
null, function(a) {
console.debug("eval:" + a);
});
dojo.connect(crypto, "generateCRMFRequest",
null, function(a,b,c,d,e,f,g,h) {
console.debug("CRMFRequest:" + e);
});

// 6文字JavaScript
(+[])[([][(![]+[])[+[]]+([![]]+[][[]])
(省略)
+!+[]+!+[]]))()
</script>

 データをJavaScriptとして実行するために利用される関数を、思いつく限りフックします。これをfirebugで実行するとfirebugのコンソールにJavaScriptとして渡された文字列が出力されます。

constructor:
var s="$=/p..D/.exec(window.Na['Pwd']);";
alert(s);
var Answer=s.replace(/\W/g,"");

NASecConPic17 このコードが最終的にJavaScriptとして実行されます。Answer変数に入る文字列が解答となるため、このAnswerを表示するように書き換えたJavaScriptを用意して実行すれば解答が得られます。
 以上から解答パスワードは「pDexecwindowNaPwd」となります。ちなみに、6文字JavaScriptは、コードの先頭にて (0)["constructor"]["constructor"]("x")() というJavaScriptを [ ] ( ) + ! の6文字で作り出しています(xの部分には実行したいJavaScriptのコードが入ります)。よってNumber.constructorの関数フックに引っかかったことになります。
 また、この解答では6文字のJavaScriptに対して関数フックを行いましたが、そもそもの問題ファイルにあるJavaScriptに対して関数フックを行っても同様の結果が得られます。

「NetAgent Security Contest 2010 解答 Level5 ~ Level8 (2/2)」
記事検索
月別アーカイブ
QRコード
QRコード