隠ぺい型モバイル悪性アプリの分析
本レポートは、アンラボコリアより発表された内容を翻訳したものです。
----------------------------------------------------------------------------------------------
モバイルデバイスの需要が高まるにつれ、モバイルデバイスへの攻撃も激しくなってきている。
ここでは、Android Dalvik実行可能ファイル(DEXファイル)でコードを隠ぺいする手法と動作原理について説明する。また、攻撃者がこの手法を悪用してマルウェアを作成する場合の対策、すなわち、隠ぺいされたコードを検知する方法を紹介する。
DEXファイルの構成
Androidアプリで最も重要なのは、DEXファイルである。DEXファイルは、Javaバイトコードに類似したDalvikバイトコードを使用してコンパイルされたアプリケーションのクラスに関する情報を持っている。DEXファイルの構成については、Androidリファレンスに公式に明示されている。 ※[図 1]
[図 1] DEXファイルの構成
● header – ファイルハッシュ、チェックサム、ファイルを分析するためのサイズ、offset情報などを持つ。
● arrays – 文字列識別子、タイプ識別子、プロトタイプ識別子、フィールド識別子、メソッド識別子、クラス定義に関する配列がある。配列に記録された値は、概ねdata sectionに存在する関連データに対するoffsetである。
● data section – コードコマンド、文字列、フィールド、デバッグ情報などを持つ。data section 以外のセクションはデータに対するoffset情報のみを持つため、data sectionは DEXファイルの最も核心的な部分と言える。
各クラスは、クラスに属するフィールドとメソッドに関する情報を持っているdata sectionのclass_data_item構造体として記述される。クラスに属する全体メソッドは、direct methodとvirtual methodの2つの配列に定義され、各メソッドはclass_data_itemのencoded_method構造体を通じて宣言される。
構造体の内容は次の通りである。
● method_idx_diff – この値は、Androidリファレンスに次のように定義される。「名前と技術者を含め、メソッドを識別するためのmethod_idsインデックス一覧の以前の要素のインデックスからのoffsetを表現する。一覧の1つ目の要素はインデックスが直接的に表現される。」
分かりやすく言うと、この値は、method_idsのインデックスに対する増分値である。例えば、前のメソッドのインデックスが3、現在のメソッドのmethod_idx_diffが1だとしたら、現在のメソッドのインデックスは3 + 1 = 4になる。
● access_flags – 共用アクセスに対してACC_PUBLIC = 0x1値を持つ。ACC_PRIVATE、ACC_PROTECTED、ACC_STATIC、ACC_FINALなどがある。
● code_offset – ファイルのスタート位置からのoffset、メソッドのコード位置を表す。
正常なDEXファイルでメソッドが宣言される方法は、[図2]の通りである。メソッドAは、メソッド一覧で一番最初に宣言されたメソッドである。このため、メソッドAの method_idx_diffは、直接的にメソッドインデックス(method_idx)を表す。code_offsetはメソッドAのコード位置を指している。
[図 2] 正常なDEXファイルのclass_data_item構造体、メソッドA宣言
この状態でメソッドBがメソッド一覧に追加されると、メソッドBのインデックスはメソッドAのインデックスとメソッドBのmethod_idx_diffを足した値で計算できる。メソッドBのcode_offsetは、メソッドAと異なる個別位置を指している。
[図 3] 正常なDEXファイルのclass_data_item構造体、メソッドA、B宣言
また、メソッド一覧にメソッドCが追加されると、メソッドCは、メソッドBのインデックスとメソッドCの method_idx_diffを足した値で計算される。メソッドCのcode_offsetはメソッドA、Bとは異なる個別位置を指している。
[図 4] 正常なDEXファイルのclass_data_item構造体、メソッドA、B、C宣言
DEXコード隠ぺい手法
ここでは、DEXファイルでコードを隠ぺいする手法について説明する。DEXファイルでコードが存在できる部分はメソッドのみであり、DEXファイルでコードを隠ぺいするということはメソッドを隠ぺいすることと同じ意味になる。
DEXコード隠ぺい手法にはいくつかの方法があるが、どのような場合でも次の3つの段階が必ず必要である。
● DEXファイルでencoded_methodで記述されているメソッドの宣言を改ざんする。すなわち、隠ぺいするメソッドの宣言を改ざんし、すでに存在する他のメソッドを指すようにする。
● DEXファイルを再計算する。隠ぺい手法はファイルの内容を改ざんする作業のため、ファイルの有効性を確認するために使用されるSHA-1ハッシュとadler32チェックサムを再計算してファイルヘッダーに有効な値を記録する必要がある。
● 改ざんされたDEXファイルを使用してAPKファイルをリビルド(Rebuild)する。
実際にメソッドの宣言を改ざんしてメソッドを隠ぺいする具体的な方法を2つ紹介する。
1つ目は、隠ぺいされたメソッドの前のメソッドが2回参照される方法である。 ※ [図 5]
[図 5] DEXファイルでのメソッドの隠ぺい
[図 5]の赤い線はメソッドBを隠ぺいする手法を表す。メソッドAのインデックスとコードが2回参照されており、メソッドBのコードは隠ぺいされている。また、隠ぺいするメソッドはメソッドBと表現されており、こうすることでDalvik有効性検証機がDEXファイルを検証する際、名前やディスクリプタなどをすべて含めて隠ぺいされたメソッドを前のメソッドと完全に同様のメソッドとして誤認識する。
● 隠ぺいするメソッドのcode_offsetを前のメソッドのcode_offsetと同様の値で設定する。異なるcode_offsetを設定すると、Dalvik有効性検証機がエラーを起こす可能性がある。
DEXファイルでメソッドは、必ずお互い連続的に存在するように宣言されなければならない。この原則を破らないために、隠ぺいしたメソッドの後に続くメソッドの宣言も共に改ざんしなければならない。すなわち、次のメソッドのmethod_idx_diff値を大きく増加させ、隠ぺいされるメソッドがまるで最初から存在しなかったようにするのである。こうすることで、 隠ぺいされるメソッドは前のメソッドと完璧に同様の参照を持ち、DEXファイル規約にも完璧に当てはまることとなる。
[図 6]では、隠ぺいされたメソッドBがメソッドAと同様のメソッドインデックスとコードを参照している。メソッドBとメソッドAが同様のものだと認識させ、メソッドBの実際のコードが一般的な方法では参照されないようにしている。
[図 6] 前のメソッドを2回参照する手法
2つ目の手法は、隠ぺいされるメソッドの次のメソッドを2回参照する方法である。1つ目の手法とほとんど概念は同じなので簡単に説明する。
隠ぺいされるメソッドのインデックスが次のメソッドのインデックスと同様に計算されるようにmethod_idx_diff値を大きく増加させる。 code_offsetも次のメソッドと同様のコードを参照するように次のメソッドのcode_offset値に設定する。それから、次のメソッドのmethod_idx_diffを0に設定すると、隠ぺいされるメソッドが次のメソッドと完璧に同様の参照を持つようになる。
[図 7]では、隠ぺいされるメソッドBが次のメソッドであるメソッドCと同様のメソッドインデックスおよびコードを参照している。メソッドBとメソッドCが同様のものだと認識させ、メソッドBのコードが一般的な方法では参照されないようにしている。
[図 7] 次のメソッドを2回参照する手法
2つの手法とも、隠ぺいされただけで、改ざんされたクラスは相変わらず隠ぺいされたメソッド情報を含んでいる。すなわち、クラスのメソッド数は絶対に変更してはならない。
有効なDEXファイルのビルド
DEXファイル内容の改ざん後、ファイルの有効性を続けて保証するためにはDEXファイルのヘッダーを必ず修正しなければならない。修正する部分はヘッダーの一番前の部分である。このうち、初めの値はDEXマジック値であり、絶対に他の値に修正してはならない。次にadler32チェックサム、SHA-1ハッシュの2つのフィールドが続くが、この2つの値を正しい値に変更しなければならない。チェックサムは転送や保存時のエラーを検知するために使用され、ハッシュは整合性を検証するために使用される。2つのフィールドの値はDEXファイルの残りの部分に対して計算された値である。SHA-1ハッシュはDEXマジック、チェックサムと自らを除外した全体ファイルに対して計算された値であり、adler32チェックサムはDEXマジックと自らを除外した全体ファイルに対して計算された値である。
次の順序に沿って2つの値を計算することで、有効なDEXファイルをビルドできる。
● DEXファイルのSHA-1を計算してヘッダーに記録する。
● DEXファイルのadler32チェックサムを計算してヘッダーに記録する。adler32チェックサムの計算は前の段階で記録したSHA-1を含むため、計算順序を守る必要がある。
上記の2段階を終えると、DEXファイルは有効性を持つようになる。
[図 8] 有効なDEXファイルのコード例
APKファイルのリビルド
ここまででDEXファイルに対する準備がすべて終わり、 Androidアプリケーション(.apk)をリパッケージングする。
方法は次の通りである。
● 元のAPKファイルの圧縮を解除する。
● 圧縮解除した元のファイルおよび改ざんされたDEXファイル(classes.dex)をZip形式で圧縮する。
● Jarsignerと開発者キーで署名し、新しいAPKファイルをビルドする。
上記過程はMakefileを使用して簡単に自動化することができる。Makefileの例は次の通りである。
[図 9] APKファイルリビルドMakefileの例
DEX隠ぺいコードの呼び出し
攻撃者が改ざんした隠ぺいコードをアプリケーションの起動時に実行できる方法について説明する。
● 現在のアプリケーションのDEXファイルをオープンしてメモリ上の配列に配置する。これはandroid.content.res.AssetManagerのopenNonAssetメソッドを使用する。このメソッドは直接アクセスして呼び出せない関数のため、Javaリフレクションを使用する。
● メモリ上のDEXファイルが配置された配列をアンパッチする。改ざんした値を本来の値にすべて戻す。
● アンパッチされたメモリ上のDEXファイルを新しいクラスとしてオープンする。メモリ上のDEXファイルに対しJavaリフレクション手法を使用してopenDexFile()を呼び出せば良い。このメソッドはcookieを返還するが、実際のこのcookieの値はDEXファイル内部データ構造体に対するポインターである。そして、defineClass()を使用してアンパッチされたクラスをロードする。このメソッドはクラスをロードするためにopenDexFile()が返還したcookieを利用する。
● アンパッチされたDEXファイルでgetDeclaredMethods()を利用して隠ぺいされたメソッドを探索する。
● アンパッチされたDEXファイルから生成されたオブジェクトインスタンスで隠ぺいされたメソッドを呼び出す。
[図 10] DEX隠ぺいコードの呼び出しの例
DEX隠ぺいコードの検知
DEXコード隠ぺい手法は、アプリマーケットプレイスのフィルタリングを迂回するために利用されたり、逆分析を困難にするためのアンチリバーシング手法として利用されたりする可能性がある。さらに、マルウェアの制作者がマルウェアを隠ぺいするためにこの手法を利用する場合も考えられる。
ここでは、隠ぺいコードの検知に利用できる検知方法をいくつか紹介する。
● encodeded_methodでmethod_idx_diffが0または0より小さい値を持つ場合、もしくは、method_idx_diffで計算したmethod_idxが重複使用された場合
[図11] 隠ぺいコードの検知の例 : method_idx_diffが<=0、またはmethdod_idx重複使用の場合
● encodeded_methodでcode_offが重複使用された場合
[図 12] 隠ぺいコードの検知の例 : code_off 重複使用の場合
● method_idsのインデックスの中から使用されていないmethod_idxの抽出
[図 13] 隠ぺいコードの検知の例 : 使用されていないmethod_idxの抽出
まとめ
ここまで、AndroidでDEXファイルの有効性検証時に暗号化されたメソッド(encoded_method)を完璧に検証できない脆弱性を利用して任意のコードを隠ぺい・実行する攻撃手法について説明した。隠ぺいされたコードはDEX ファイルにそのまま残っていたが、一般的な分析ツールや分析家には見えないようになっていた。しかし、幸いにDEXコード隠ぺい手法を検知する方法もあった。