初めてのAndroidマルウェア解析に挑戦してみた
はじめに
こんにちは、m(@art_of_the_nerd)です。
今回はふとAndroidマルウェアを解析してみたいという欲が湧き上がってきたので、初めて解析した作業内容と調査結果をブログにまとめました。
対象読書
- Androidマルウェアに興味がある方
- マルウェア解析全般に興味がある方
- Androidアプリの権限や挙動がどうマルウェアに悪用されるか知りたい方
- jadxなど解析ツールを使ってみたい方
- 開発者経験がないけど解析に挑戦したい方
注意点
- 本記事では、実際のAndroidマルウェアのAPKファイルを用いて解析を行っています。
- マルウェアの取り扱いには十分な注意が必要です
- 筆者はAndroidアプリ開発の実務経験がありません。
- そのため、アプリ挙動の解析やコード構造の理解において、開発者視点での捉え方が不足している可能性があります。
- 本記事の解析内容は静的解析に限定しており、動的解析(実行・挙動の監視)やコード難読化の解除などは行っていません。
- 本記事は学習目的での調査・記録であり、悪用を助長する意図は一切ありません。
環境準備
jadx-gui
今回は静的解析のみを実施します。
導入したツールはjadx-guiのみで、APKやDEXファイルをJavaソース風に変換してくれる逆コンパイルツールです。
自分でもあまり詳しくなかったので、あらためてDEXについて調べてみました。
DEX(Dalvik Executable)は、Androidアプリで使用される中間コード形式のファイルです。開発者がJavaやKotlinで書いたコードはまず .class ファイル(Javaバイトコード)にコンパイルされ、それらが複数まとめられて .dex(Dalvik Executable)ファイルになります。
この .dex ファイルが、Androidの仮想マシン(Dalvik や Android Runtime)上で実行される形式になるようです。
インストール
私の環境はmacOSのため、brewを用いて以下のコマンドで導入しました。
brew install jadx
起動
以下のコマンドで立ち上がります
jadx-gui
調査対象
今回の調査用検体はマルウェア解析スキル向上のためにダウンロードしました。
繰り返しになりますが、マルウェアを保存する場合は正当な理由が必要になるため取り扱いには注意してください。
調査対象のapkマルウェアは、Malwarebazaarからダウンロードしました。
※念の為、URLはマスキングしています
hxxps://bazaar.abuse[.]ch/sample/f690e30b6ee25c153effc5620fd7ec61481a449a127b54a67c7afc4c13d7917f/
調査開始
AndroidManifest.xml
手始めにAndroidManifest.xmlを確認します。
AndroidManifest.xml は、Androidアプリの構造や機能を宣言する最も重要なファイルの一つです。マルウェア解析においては、アプリが要求する権限(パーミッション)やメインで起動するクラス(MainActivity)など、アプリの基本的な挙動や意図を知る手がかりを得ることができるため、最初に確認すべきファイルです。
要求されている権限は5種類です。
以下にどのような機能かまとめてみました。
- WRITE_EXTERNAL_STORAGE: 外部ストレージへの書き込み
- INTERNET: 外部との通信が可能
- REQUEST_INSTALL_PACKAGES: アプリ内で他のAPKをインストール可能
- FOREGROUND_SERVICE: 長時間バックグラウンドで動作するサービスの実行
- WAKE_LOCK: 端末がスリープしないようにする(常駐目的)
次に、アプリのエントリーポイント(最初に実行されるクラス)を確認するため"android.intent.category.LAUNCHER"を探します。
その結果、以下のような記述を発見しました。
android:name="com.app.applaunch1234.main"
このことから、com.app.applaunch1234.main"
がアプリの起動時に最初に呼び出されるクラスであることがわかります。
エントリーポイント(com.app.applaunch1234.main)
mainクラスを調査していると、switch文によって制御される処理が存在していることがわかりました。
この switch文の中で最も注目すべきはcase 11です。
なぜなら、ここでC2サーバーから別のAPKファイルをダウンロードする処理が実行されているからです。
そこで逆方向に追跡し、「どのような条件を満たすと case 11に到達するのか」を分析しました。
▪️ Case9
case 11に進むためにはまずcase 9に入る必要があります。case 9では this._resultの値によって分岐しており、trueのときのみcase 11に進みます。
▪️ Case16
そのthis._resultの値は、case 16でobjArr[0]から受け取って設定されています。
つまりcase 16がcase 9の前提条件となっています。
▪️ Case8
さらにcase 16に進むためには、まずcase 8に入り、以下のように main._checkinstallationrequirements()を呼び出す必要があります。
Common.WaitFor("complete", main.processBA, this, main._checkinstallationrequirements());
この関数は非同期で、外部APKのインストール要件を判定しています。
この関数はResumableSub_CheckInstallationRequirementsをラップしており、チェック処理の本体はその中にあります。
この関数内のswitch文には、インストール可否に関する3種類のメッセージが含まれています。
- ストレージ使用不可の警告
- "Storage card not available. Make sure that your device is not connected in USB storage mode.
- 外部ストレージが書き込み不可の場合
-インストール誘導(イタリア語) - "Vuoi installare questa app sicura?"
- Android 8.0以上で「提供元不明アプリ」の許可がOFF
- 提供元不明アプリの許可を促す
- "Please enable installation of non-market applications.\nUnder Settings - Security - Unknown sources\nOr Settings - Applications - Unknown sources"
- Android 7以下で「提供元不明アプリ」の設定がOFF
つまり、この「ResumableSub_CheckInstallationRequirements」は、外部APKのインストールが可能かどうかを判断し、可能ならtrueを返し、無理ならfalseを返す関数です。
▪️ Case13
case 8に進むには、まずcase 13 を通過する必要があります。
if (this.index3 >= this.groupLen3)
この条件がtrue(=全パーミッションの確認完了)になればcase 8に進みます。
- index3について
index3はCase1にて0で変数として設定されています。
また、Case14にてindex3++している処理を発見しました。
- groupLen3について
groupLenはCase1にて定義されています。
this.group3 = new Object[]{RuntimePermissions.PERMISSION_READ_EXTERNAL_STORAGE, RuntimePermissions.PERMISSION_WRITE_EXTERNAL_STORAGE};
このことからindex3はgroup3配列のインデックスとして使われており、groupLen3はその配列の要素数(=チェックすべきパーミッションの数)であることがわかります。
つまり、外部ストレージへの書き込み/読み込み権限である「PERMISSION_READ_EXTERNAL_STORAGE」「PERMISSION_WRITE_EXTERNAL_STORAGE」
が利用できるかチェックしているか確認しています。
このように、アプリの起動直後に段階的にパーミッションとシステム状態を確認し、条件を満たすと最終的にcase 11にてC2サーバーから別のAPKをダウンロードしてインストールする、という一連の処理が組み込まれていることが分かりました。
C2サーバーの調査(51.38.113[.]144)
ダウンロードURL
前述の解析から、アプリが外部からAPKファイルをダウンロードしようとしているC2サーバーのURLが判明しました。
- hxxp://51.38.113[.]144/AppUpdateExample.txt
- hxxp://51.38.113[.]144/tap.apk
これらのURLは2025年6月1日時点ではアクセス不能で、実際にどのようなファイルが配布されていたかは確認できません。Wayback Machineなどのアーカイブサービスにも履歴は残っておらず、当時の内容を直接確認できませんでした。
VirusTotalによる調査
代替手段として、VirusTotalで対象URLおよびファイルのハッシュ値を調査しました。
検体のURL情報
下記URLのスキャン結果より、tap.apkの存在はVirusTotalでも観測されていました。
この記録によると
- 初回観測日:2021年12月15日
- 最終観測日:2024年5月8日
およそ2年半にわたり公開されていた可能性があるC2関連ファイルであることがわかります。
ハッシュ値による確認
また、tap.apkのSHA-256(f526ac987a6ebb1d53722a3d4ae59dff72c88559133ab945f9e404c7726733cb)をもとに、VirusTotal上で個別ファイルの情報も確認しました。
VirusTotalの結果では、tap.apkは名前こそAPK形式のように見えるものの、実際のレスポンスの MIMEタイプは「text/html」であり、中身はAPKではなかったことが分かります。
これはファイルの差し替え、またはアクセス条件によって返す内容を変える仕組みの可能性もあります。
調査結果
ここまでの解析から見えてきたこのAPKの全体像を、改めて整理します。
このアプリは起動直後から、ストレージの読み書きや「提供元不明アプリのインストール可否」など、システムの状態を段階的にチェックしていました。
条件が整えば、C2サーバーから別のAPKファイルをダウンロード・インストールする処理が実行されます。
調査の結果から不審なアプリと感じたポイントは以下の通りです。
- 外部ストレージへのアクセス許可を要求
- インストール許可の誘導を複数パターンで行う
- インターネット経由での外部APK取得処理
- C2サーバーの存在と、そこから取得されるファイル(APK)
加えて、サービスが常駐化されていることから、ユーザーがアプリを閉じても裏で動作を続ける設計になっているのも気になった点です。
さらに、C2からダウンロードされるtap.apkのファイルは、VirusTotal上では現在HTMLとして認識されていましたが、初期はAPK形式だった可能性も捨てきれません。少なくとも、解析の対象になっていた期間が2年半近くあることから、実際に野良で使われていた可能性は高いと考えます。
最後に
今回が自分にとって初めてのAndroidマルウェア解析でしたが、想像以上に多くの学びがありました。
これまでAndroidアプリの開発経験がなかったため、パーミッションの仕組みや非同期処理の流れ、C2通信の挙動など、調査を通じて初めて知ることばかりでした。一方で、開発者視点からの推測力が不足していると感じる場面も多く、今後の課題として意識したいところです。
不明点や深掘りできそうな箇所はまだ残っていますが、まずはひとつの実サンプルを通して、実践的に手を動かしながら理解を深められたのは大きな収穫でした。
今後は今回のようなインストーラ型だけでなく、情報窃取やSMS傍受を行うタイプのマルウェアにも挑戦してみたいと思います。
Discussion