ページの先頭です
2021年に行われたVirus Bulletin(VB2021 localhost)において、CTO及びCTO Function Listerというツールを発表しました(注1)。その後も断続的ではありますが、機能追加などの改善を行っています。本稿では、これらのツールをマルウェア解析のどの部分に適用しているのかを、実際の解析事例と共に紹介します。
なお、今回使用した検体はselfmake3という、SpiderPigと呼ばれるRATをダウンロードして実行するためのダウンロード型マルウェアで、標的型攻撃で使用されたものです。SHA256ハッシュ値は以下のとおりです。
7DA969010A55919AA66ED97A2D2D6D6A0BE3D8DC6151EEB6CEBC15E4F06D4553
CTO、CTO Function Listerは共にIDA Pro(注2)上でプラグインとして動作します。起動はEditメニューのPlugins、ツールバーのボタン、ショートカットキーのいずれかから行うことができます。図-1では、IDAウィンドウのツールバーの右端に中年男性風のアイコンがあるのが見えます。これらがCTOとCTO Function Listerのアイコンです。これらをクリックすることでウィンドウの左側にCTO Function Listerが表示され、右側にCTOが表示されます。CTOは主に関数呼び出しの親子関係を可視化するツールです。CTO Function Listerは関数一覧や、それぞれの関数が持つ特徴を抽出して保持し、フィルタリング機能でそれらの情報を一括検索するのが主な機能になります。図中では、それぞれのツールがIDAの逆アセンブルビュー(IDA View-A)で表示している"_WinMain"関数(正確にはMFCのAfxWinMain関数)のアドレスと同期し、そのアドレスの情報を各々表示しているのが分かります。
マルウェア作者はコンフィグなどのデータや通信に暗号化やエンコードを行い、発見しづらくしていることが多いのは皆さんご存じのことと思います。その際、AESやRC4といった既存の暗号アルゴリズムが使われている場合と、簡易的にxor命令でカスタムエンコーディングが行われている場合があります。明示的にxorでカスタムエンコーディングをしている場合に加え、前述のアルゴリズムを含む多くの既知の暗号アルゴリズムにもxor命令が入っています。またCPUのレジスタ以上に長いデータを暗号処理するためには、必然的にループ構造も必要になります。そのためCTOでは、IDAが認識している関数を全走査し、xor命令を見つけ、それがループの中にあるかをチェックし、結果を一覧するビルトインコマンドがあります。また、該当した関数名がデフォルトから変更されていない場合は、"xorloop_"という接頭辞を付与して改名することで、関数名からも簡単に見つけられるようにもしています。
図-2はそのコマンドの実行方法です。CTOからもショートカットで実行することは可能ですが、ここではCTO Function Lister上のメニューからの実行方法について説明します。
まず、ドロップダウンメニューのボタンをクリックし、そこから、"Built-in scripts"、"Find xor instructions in a loop"を選択します。解析中のプログラムサイズにもよりますが、本マルウェア(コードセクションのサイズが約280KB)であれば、2~3秒程度で終わります。
結果はOutputウィンドウにも表示されますが、CTO Function Lister上で該当関数のみにフィルタして表示することも可能です。それを行うためには図-3のとおり、再度ドロップダウンメニューを開き、"Preset filters"、"xor instruction in a loop"を選択します。
これにより、図-4のようにループ内にxor命令がある構造を持つ関数のみが列挙されます。IDAはFLIRTやLumina(注3)により、静的リンクされたC言語などのライブラリ関数の名前をある程度変更してくれます。ご覧いただいて分かるとおり、それらによって最初の2つの関数以外はすべて名前が付いています。よって、優先的に見なければならないのは最初の2つの関数(sub_1025F0とsub_102AB0)です。先ほどのコマンドは該当xor命令上に"CTO-xorloop"というコメントを付与しており、それをCTO Function Lister上でフィルタして表示しています。cmtというサブツリーがそれにあたり、CTO Function Lister上の該当行をクリックすることで、IDAの逆アセンブルビュー上で該当アドレスにジャンプすることができ、周辺のコードを確認することが可能です。
前述のコマンドで得られた2つの関数の周辺コードを確認したところ、1つは悪意のあるサーバからダウンロードしたペイロードを復号する際に使用するルーチン、もう一方は本検体に内蔵されているC&Cサーバのホスト名やIPアドレスなどのコンフィグデータを復号するルーチンでした。このような重要なコードを瞬時に見つけることができます。
今回はxor命令によるカスタムエンコーディングを例に取りましたが、AESなどの暗号アルゴリズムや、SHA256やMD5といったハッシュアルゴリズムは多くの場合、特徴的なmagic value(注4)やテーブルを持っています。そのような特徴を検出するために、findcrypt(注5)やIDA Signsrch(注6)などのサードパーティスクリプトやプラグインが公開されています。CTO Function Listerはこれらの結果も認識し、フィルタして表示することもできます。これらを組み合わせて活用することにより、暗号化/復号ルーチンやエンコード/デコードルーチンを効率良く発見し、迅速にその周辺コードを確認していくことができます。
CTOは該当アドレスへの、もしくはそこからの経路を表示することができます。図-5はCTO Function Listerで発見したxor命令を右クリックして"Find the path(s) to this node"を選択した際の結果です。結果は画面右側にコールツリーグラフとして表示されます。
このグラフは、各関数アドレスやそれを参照するコードやデータの関連性を示してはいるものの、残念ながら純粋な実行パスではありません。なぜかというと、仮にある関数内に関数ポインタがあったとして、それが必ずその場で呼び出されるわけではないからです。例えば、関数ポインタをレジスタやHeapに格納して、かなり先の関数で呼び出すこともあるでしょう。またC++のvftableのような仕組みでは、間接呼び出しが多用されます。正確な実行位置を得るためにはクラスのインスタンスを追いかけ、すべてのアクセスを見つけ、vftableから関数ポインタを取り出して実行する処理を見つけなければならず、コードが非常に複雑になるからです。そのためCTOでは、コードが関数ポインタにアクセスした時点でそのアドレスを抽出して、このような親子関係のグラフを作っています。ただし、それでも十分役に立つものになっています。
この例であれば、パスの最初はdynamic initializerという関数です。この関数は、CRT(C-Runtime)内のinitterm(注7)という関数で処理されます。このマルウェアはMFCを使って記述されていることがコードを読むことで分かります。MFCアプリケーションはメインアプリケーションクラスをグローバル変数として宣言する必要があります。その宣言により、dynamic initializerでカプセル化されたメインアプリケーションクラスのコンストラクタがinittermから呼び出され、そのクラスインスタンスがグローバル変数に格納されます。表示されている関数名はLumina(注8)で自動的に付けられたものであり、for以降は明らかに間違った名称が付けられてしまっています。しかしCTOが表示したパスが示しているとおり、そのコンストラクタ内で、Cselfmake3Appというクラス名のvftableのアクセスが確認できます。またこのクラスがCWinAppクラスを継承していることがClass Informer(注9)の結果の1つである、クラス継承の階層構造からも確認できました。これらの事実から、Cselfmake3Appがこのマルウェアのメインアプリケーションクラスであることは明白です。
次にCselfmake3Appのvftableは、sub_101030という関数と接続しています。CTOは、関数内に存在するグローバル変数へのアクセスを抽出してキャッシュとして持っています。特にその中でも変数名の先頭やそのアドレスに付与されたコメントの最後にvftableやvtableという文字列を発見した場合、そのグローバル変数をvftableとして扱ってテーブルをパースし、一定の法則に従って関数ポインタ群をそのvftableに属するものであると認識します。IDAはRTTIを認識することが可能なため、このアドレスのコメントにvftableを含む文字列が付与されます。よってCTOの初回実行時にvftable解析処理が実行され、CTO内でsub_101030がこのvftableの一部であると認識済みです。そのためvftableに属する関数へのアクセスが発生すると、CTOはこのようにこの関数ポインタを仮想メソッドとして接続できるのです。図-6は、Cselfmake3Appのvftableのノード(上から3番目の「??_7Cselfmake3App@@6B@」)をCTO上でクリックしたときのIDAの画面を表示したものです。「IDA View-A」ではvftableの先頭から関数ポインタが連続していますが、先頭から0x50の位置にsub_101030があることが分かります。ちなみに、32-bitのMFCメインアプリケーションクラスでは、vftableの0x50にInitInstanceという仮想メソッドが存在します。つまり、sub_101030はInitInstanceです。
MFCアプリケーションは前述のとおりCRT内でメインアプリケーションクラスのコンストラクタを処理した後、WinMain関数(厳密にはAfxWinMain)内でInitInstanceやRunなどのいくつかのメソッドを実行します。特にInitInstance関数は、MFCアプリケーションの規約上、必ずOverrideすることが定められており(注10)、ここが実質的にマルウェアのメイン関数となっていることが多いです。このマルウェアもInitInstance(sub_101030)が呼び出されており、その中でマルウェアのコンフィグをデコードするルーチン(sub_102AB0)を呼び出しているのがCTOのコールツリーグラフから簡単に分かります。
また、CTOのパス探査のもう1つの特徴は、クロスリファレンス(注11)さえあれば、グローバル変数(文字列を含む)であってもパスを作成できる点です。IDAにもProximity View(もしくはBrowser)という機能がありますが、関数にしか利用できません。これはCTOを使う利点の1つであると言えます。
注意点として、今回紹介したようにCTO Function Lister上でこの機能を使うためには、CTOをあらかじめ起動しておく必要があることを付け加えておきます。
C++で書かれたマルウェアの多くは、std::stringやstd::wstringを文字列操作に使っています。これらのクラスはコンストラクタや一部のメソッドがインライン展開されてしまうため、一見ではこのクラスが使われていることを知るのが困難な場合があります。ただし、クラスレイアウトの初期化を行うコードには特徴的な初期値が使われるため、多少の誤検知はあるものの、単純なパターンマッチングで検出することが可能です。
これらの検出は、先ほども紹介したCTO Function Listerのドロップダウンメニューから"Built-in scripts"、"Find notable instructions"を選択することで行えます。また、フィルタしてコマンドの結果を確認するためには、同じくドロップダウンメニューから"Preset filters"、"Notable instruction"を選択すれば、表示することができます。
例として、マルウェアによってデコードされたコンフィグデータをパースしていくコードで使われていたstd::stringを見ていきます。図-7はCTOで検出したstd::stringの初期化部分のコードを表示したものです。図中1つ目の赤枠では、即値0xfでスタック変数が初期化されているのが分かります。これは、Visual Studioのstd::stringで長年使われている初期化コードの一部です。その2命令下(2つ目の赤枠)には、1バイト分のNULL文字でバッファの先頭部分(前述の0xfで初期化したアドレスより-0x14の位置)を初期化しているコードも見えます。これらがセットで存在した場合、筆者はstd::stringであると判断して構造体を適用するようにしています。
std::stringのクラスレイアウトはドキュメント化されていない内部的なものであり、我々が調べた限りでもVisual Studioのバージョンに応じて数パターンあります。このマルウェアはVisual Studio 2008でコンパイルされたものであることが分かりました。そのため、そのバージョンに対する適切な構造体をロードして、スタック上のstd::stringインスタンスの先頭にその構造体を適用することで、図-8のようにきれいにstd::stringを認識できます。
紹介したIDAプラグインを使ってマルウェア解析をする講義を、2023年2月に行われたGCC 2023 Singapore(注12)で実施してきました。ランダムに選ばれた4~6人で1チームを作り、講義の最後に本稿で示したマルウェア検体の特徴的な機能やコードをCTF形式で出題し、ゲーム感覚で解答してもらいながらマルウェア解析をしてもらいました。
受講生は各国から選抜された学生たちでしたが、IDAの使用やリバースエンジニアリング自体の経験がない人も多く、CTFを始める前に簡単なレクチャーを行いました。それでも、ここで紹介したようなテクニックを駆使して時短をしていくことで、優秀なチームはこのマルウェアであれば1時間半ほどで大部分の解析を終えられるようになりました。また三分の二以上のチームは3時間程度で重要な部分をほぼ解いていました。この講義ではリバースエンジニアリングのみを実施してもらうために実行ファイルそのものは渡さず、そのファイルをロードしたIDAのデータベースのみを渡して解析してもらいました。実行してしまえば、このマルウェアは動作が単純なので簡単に動作概要を把握できますが、コードを読んで正確にマルウェアの挙動を把握する能力も必要であるため、あえて厳しいやり方を受講生に課しています。そのような状況下でも、紹介したツールやテクニックを使って勘所を養うことで、急成長していく様子はとても感動的でした。
CTOやCTO Function Listerは、本稿で紹介した以外にも、過去のマルウェア解析で必要性を感じた機能を実装しています。これからも継続的に自動化などのアイデアを考えて実装していきます。これらのツールが皆さんのマルウェア解析の一助になれば幸いです。
執筆者プロフィール
鈴木 博志 (すずき ひろし)
IIJ セキュリティ本部 セキュリティ情報統括室 マルウェア&フォレンジックアナリスト。
IIJのCSIRTチームであるIIJ-SECTのメンバーであり、社内、顧客のインシデント対応に従事。主にマルウェア解析とフォレンジック調査を担当。そこから得られた知見を元に、Black Hat(USA、Europe、Asia)、Virus Bulletin、FIRST TCなどの国際カンファレンスや、内閣サイバーセキュリティセンター(NISC)、総務省、法務省、IPA、産総研などで講演を行う。また、Black Hat USA、FIRST(Annual、TC)、Global Cybersecurity Camp、MWS、セキュリティキャンプ全国大会やサイバーコロッセオなど、国内外のカンファレンスや教育プログラムでの専門家や学生向けのトレーニング講師も兼ねる。特にBlack Hat USAでは日本人として初めてトレーニング講師に選ばれ、フォレンジック調査とマルウェア解析を使用したインシデントレスポンスに関するトレーニングを提供している。この分野では17年を越える経験を有する。
ページの終わりです