ブラウザ・プラグインは、かねてより格好の攻撃対象として狙われてきました。過去数年を振り返ってみても、最も多くの攻撃を受けたターゲットがAdobe Flashであることに疑いの余地はありません。 2016年だけでも250件以上のCVE(脆弱性識別子)が作成されており、ほぼすべてのエクスプロイト・キットにFlashの脆弱性を悪用するエクスプロイトが含まれています。今やFlashエクスプロイトは至るところに蔓延しており、それだけ注意を払う必要があるのです。

セキュリティ研究者である私たちにとって、実際に拡散しているエクスプロイトを分析し、その内部的な動作についてできる限り多くの情報を集める必要性に迫られることは、日常茶飯事です。しかしこれは、多大な時間と手間のかかる煩雑な作業であり、決して楽しい仕事ではありません。また、ROPチェーンやシェルコード、ペイロードなど、エクスプロイトの核となる部分は実行時に生成されるケースがほとんどです。そこで私たちは、これまでとは異なるアプローチで分析にあたることを決断しました。エクスプロイトについてより多くの情報を入手するため、ネイティブ・レベルのデバッガが備える既存の機能を活用することにしたのです。

その結果、Flashエクスプロイトの内部的な仕組みについて、従来よりもはるかに広い視野が得られるようになりました。それに加えて、調査時間の大幅な短縮と管理作業の効率化にも成功しています。

本稿では、この新しい調査アプローチの詳細とメリットについて説明し、一般的な活用事例を紹介します。ただし、Flashのエクスプロイト手法について解説する前に、まずはFlashの再生専用ファイルSWFの基本について簡単におさらいしておきましょう。

Flashの基礎

SWFとは

SWFフォーマットは、ベクタ形式の画像をインターネット経由で配布するために開発されたファイル形式です(結果として、エクスプロイトの配布にも頻繁に利用されています)。ネットワーク経由での配布を念頭に設計されており、ファイル・サイズを最小化するため、圧縮やビット・パッキング、オプション・フィールド付きの構造を用いたバイナリ形式を採用しています。

基本的に、SWFファイルは、タグ付きの一連のデータ・ブロックで構成されています。タグでは、表示の際の形状や再生するオーディオ・ストリームを定義できますが、攻撃目的の観点では、ActionScript 3.0のバイトコードを格納できる点が最も重要となります。

DoABC

DoABCタグは、ActionScript 3.0の仮想マシンにより解析されるActionScriptバイトコード(ABC)のブロックを格納します。

また、Flashファイルが使用する静的な値、クラス、メソッドの定数プールを格納します。

例として、次のコードを見てみましょう。

このコードをコンパイルすると、次のようなバイトコードになります。

pushstring命令は、次にあるように、定数プールの文字列番号13(0d)を参照しています。

一方、getlexとcallpropvoidは、定数プールの修飾名(QNAME)を参照しています。

分析手法

Flashエクスプロイトの分析手法には、広く使用されている定番の方法がいくつか存在します。

ソースコードの逆コンパイルと編集

最も簡単なのは、FFDECなどの逆コンパイラを使用して実際のソースコードを生成し、編集する方法です。

エクスプロイトのソースコードと聞いて、興味津々の方がおられるかもしれませんが、事はそう単純ではありません。攻撃者は、高度な難読化手法とパッキング手法を駆使して、コードを非常に読みづらくしているからです。また、逆コンパイルを妨害するため、無意味なコードや到達しないラベルを挿入しているケースも少なくありません。

また、逆コンパイルで有効なコードを出力できたとしても、バイトコードの解釈ミスにより、不正確なコードや理解困難なコードが生成される場合があります。

逆アセンブルとトレース

もう1つのアプローチは、SWFファイルの逆アセンブルです。RABCdasmなどの逆アセンブラを使用すると、SWFファイルからABCタグのバイトコードを(逆コンパイルするのではなく)ダンプし、編集して再アセンブルできます。

バイトコードを変更できれば、Matt Oh氏が行っているように、トレースなどのデバッグ関数を重要ポイントに設定できます。これは間違いなく有効な方法ですが、逆アセンブルしたコードにトレース関数でフックを設定するという方法では、Flash Player用デバッガの対応範囲や機能の制約を受けることになります。

新しいアプローチ – 逆アセンブルとデバッグ機能の活用

状況によりますが、Flash Player用のデバッガを使用するのではなく、ネイティブ・レベルのデバッグ機能を使用した方がはるかに効果的な場合があります。これは特に、JITレベルの関数を分析する場合や、根本原因分析を実施する場合に当てはまります。

例えば、Internet Explorer(IE)に対するエクスプロイトを分析する場合には、引数にデバッグ文字列を指定した、あまり一般的でない関数(Math::Atan2()など)をエクスプロイトのコードに挿入するという方法が一般的です。そのうえで、エクスプロイトの実行状況をWinDbgで観察すると、メモリやコードのレイアウトをより明確に把握できます(http://d0cs4vage.blogspot.co.il/2013/06/windbg-tricks-javascript-windbg.html)。

私たちは、この手法をFlash ActionScriptに応用してみました。すると、Flash Player用のデバッガでは不可能なレベルで、エクスプロイト実行時のヒープの状況を正確に把握できることが判明しました。一般的でない関数(ここではJSON.stringifyを使用)を挿入することで、難読化された文字列という問題を解決し、動的に作成および割り当てられるバイト配列を確認できたのです。

つまり、逆アセンブルしたバイトコードを利用して、一般的でない関数をSWFファイルに追加すると、エクスプロイトの作成者が使用している逆コンパイル対策を回避できるということです。

この調査手法には、大きく分けて次の2つの工程があります。

  1. WinDbgで必要なブレークポイントを設定する。
  2. 挿入した関数で、元のSWFにフックを設定する。

ただし、Adobeはデバッグ・シンボルを提供していないので、多少のリバース・エンジニアリングが必要となります。

WinDbgでのブレークポイントの設定 – 使用するデバッグ関数の選択

ここで唯一の要件となるのは、挿入する関数で文字列を扱えること、そして可能であれば、その関数がエクスプロイト自体から一切呼び出されないことです。私たちは、この要件を満たす関数としてJSON.stringifyを選びました。

Flashライブラリで、対応するオフセットを見つけるのは難しくありません。文字列オブジェクトを割り当て、WinDbgを使用してメモリ内でそのオブジェクトを検索し、文字列へのアクセスにブレークポイントを設定してから、文字列に対してJSON.stringifyを実行します。次に例を示します。

ActionScriptプロジェクトを作成したら、作成されたSWFをローカルのhtmlファイルに埋め込み、そのファイルをIEで開きます。WinDbgをIEのプロセスにアタッチし、ExternalInterface.call()を使用して、先ほど作成したalert()にブレークポイントを設定します。

アラートが生成されると、Flashは一時停止します。ここで、Mona.pyを使用して、割り当てられた文字列オブジェクトを探し、このオブジェクトへのアクセスでブレークするようデバッガを設定します。

アラートの後でFlashを再開すると、予想どおりブレークポイントにヒットします。

ここで、この動作が本当にJSON.stringify()に関係した処理であるかどうかを確認します。IDAでこのアドレスを確認すると、そのとおりであることが分かります。

JSON.stringify()の機能を踏まえると、_memcpy_0は、フック場所として非常に有望であるように思えます。想定どおりに事が進めば、関数が戻ってきたとき、stringifiedオブジェクトへのポインタがeaxに格納されているはずです。

思ったとおりです! WinDbgコンソールで、次のようなブレークポイントを作成すれば、json.stringify()に渡されるすべての文字列を出力できることになります。

挿入した関数による元のSWFのフック – 独自のクラスを使用

もちろん、多様なオブジェクトやデータ・タイプを扱う場合は、プレーンな文字列を出力するだけでは不十分です。そこで私たちは、より複雑なロジックを実行し、各種のデータ・タイプを扱える独自のクラス「exploit_common」をFlashファイルに追加しています。このクラスでメインの関数となるのは、debugPrint()という関数です。

このクラスは、チェック・ポイントのGithubからダウンロードできます(https://github.com/CheckPointSW/flash_instrumentation/blob/master/exploit_common.as)。

この関数では、引数として任意のオブジェクトを取り、そのタイプに従ってオブジェクトを扱えるため、フック・プロセスが簡潔になります。ただし、ActionScriptコードをただ単純にプレーン・テキストとしてエクスプロイトに追加するわけにはいきません。そこで、Adobeが提供するFlex SDKのコピーを利用して、次のコマンドでライブラリをコンパイルします。

「as」は、ActionScriptライブラリへのパスです。続いて、新たに作成したSWFをRABCDasmで逆アセンブルします。

ライブラリの.asasmファイルは別の場所にコピーしておきます。このファイルは後の作業で必要になります。

活用事例

では、いよいよ実際のエクスプロイトを分析してみましょう。

Sundownは、最近のエクスプロイト・キットの中で特に広く使用されているキットの1つです。WebサイトMalware-traffic-analysisで公開されているPCAPファイルから、最新のFlashエクスプロイトを見てみましょう。

http://www.malware-traffic-analysis.net/2017/01/06/2017-01-06-Sundown-EK-sends-Terdot.A-Zloader.pcap.zip

先ほどと同様、まず不正なSWFからDoABCタグをダンプし、クラスを逆アセンブルします。

abcexportは、インデックスを付加した状態でDoABCタグをダンプします。ここでは、1つ目のタグ(ゼロベース)を逆アセンブルしています。

各タグを逆アセンブルすると、.class.asasmおよび.script.asasmファイルが複数、main.asasmファイルが1つ生成されます。

main.asasmファイルを編集して、使用するライブラリをincludeする必要があります。

先ほどコンパイルしたライブラリの.asasmファイルも追加します。

これで、私たちが用意した独自の関数をエクスプロイト内部から呼び出せるようになりました。一仕事完了です。ただし、難読化されたFlashエクスプロイトに目を通すのは非常に面倒に感じられるかもしれません。特にSundownの場合、一部のクラス名はランダムに生成されているように見えます。

シェルコードのダンプ

SWFのメイン・クラスには「unfaithfulness」という名前が付けられています。

メイン・クラスのinit関数の奥深くには、非常に長い難読化文字列が記述されている場所があります。どんな目的があるのか、気になりませんか? フックしてみましょう。

上の図で、赤い線で囲んだ1文は、ABC命令の次の部分と似ているように見えます。


図1 – 生成されたバイトコード

バイトコードでは、ローカル変数への代入はsetlocal_n命令で行われ、値はAVMスタックから取り出されます。そのためフックは、_loc6_への代入後に行う必要があります。例えば次のようになります。

.asasmファイルを保存し、.abcファイルにアセンブルしてから、エクスプロイトのDoABCタグを、フックを設定したタグに置き換えます。

abcreplace.exeは、次の3つの引数を取ります。

  • 変更対象の.SWFファイル
  • 置き換え対象のタグのインデックス
  • 用意したタグ

フックを設定したSWFをHTMLファイルに埋め込み、任意のデバッガで実行します。

ブレークポイントを設定し、エクスプロイトを実行すると、ブレークポイントがヒットします。

_loc6_が出力されます。その中身は、まず間違いなく、よくあるXORループで始まるシェルコードであるように見えます。

このように、デコードしたシェルコードをダンプできるのはとても便利です。しかし、この手法の真の強みは、ヒープ・レイアウトの理解に役立つ点にあります。実際にやってみましょう。

ヒープ・スプレーの検証

次に示すのは、エクスプロイト内部で最初に呼び出される関数の1つです。

spray_obj()で2つのベクタが作成されています。

ベクタobj20は、長さが0x200000の20バイトの配列を保持しています。各配列は、0xFACE0000から増分されるシグネチャで始まります。

ベクタ0bj4000は、「everyday」クラスを0x4000インスタンス保持しています。

「everyday」クラスとはいったい何でしょうか。

このオブジェクトは、ユニークなシグネチャで始まるスプレー可能なオブジェクトであるように見えます。

ヒープに対するすべての処理や操作をセットしたところで、脆弱性の悪用が開始され、78バイトの領域外メモリ参照が行われます。先ほど見たように、obj20のバイト配列はわずか0x200000バイト長です。このため、スプレーに成功した場合、obj20の最後のメンバの直後にあるリークしたオブジェクトは、「everyday」型になるはずです。コードでの実際の検証を見てみましょう。

次に示すのが、分かりやすい説明用の文字列と_local7の値でフックを設定した重要ポイントです。

このフックで、ヒープ・レイアウトに関する有用な情報が得られるはずです。
先ほどと同様、ファイルを保存し、アセンブルしてから、元のタグを新しいタグに置き換えます。

WinDbgで必要なブレークポイントを設定すると、次のポイントでヒットします。

0x78のオフセットに、最初の「everyday」メンバ0xffeedd00を見つけることができます。

obj20のバイト配列は0x200000長であるため、これは0x090a0000にあると考えられます。

ここまで見てきたのは、ネイティブ・レベルのデバッグがいかに強力であるかの一例に過ぎません。リークしたオブジェクトへのアクセスにもっと多くのブレークポイントを設定する、エクスプロイトに組み込まれたパターンやシグネチャをメモリ内で検索する、それらの割り当ての並びを把握するなど、他にもさまざまな用途で利用できます。

まとめ

エクスプロイト・キットから標的型攻撃まで、Flashエクスプロイトは至るところで使用されています。静的なシグネチャによる検出を回避し、分析を困難にするため、Flashエクスプロイトは入念に難読化されています。確かに、トレース関数を使用して、Flash Player用のデバッガでエクスプロイトを実行すれば、ネイティブ・レベルのデバッグを行わずに済むかもしれません。しかし、Flashのように仮想マシンをベースにしたアプリケーションが対象であれば、難しいネイティブ・レベルのデバッグを試す価値は十分にあります。一般的でない関数をブレークポイントに利用すれば、重要ポイントでエクスプロイトのフローを停止させ、有用なデータを出力して、一般的なデバッグ・プレイヤーよりも幅広い視点で不正なSWFをデバッグできます。