Frida gadgetを使ったAndroidアプリの動的解析・改ざん方法

コラム概要

■こんな方におすすめ:

  • アプリの動的な解析手法を知りたい
  • アプリの動的改ざんに対する脆弱性について知識をつけたい
  • 開発/技術担当の方

■難易度:★★☆

■ポイント:

Frida(Frida gadget)によって、Androidアプリを手軽に、動的な解析・改ざんができる。
ルート端末でなくても可能。
便利なデバッグツールとしても使えるものだが、
既存のアプリに対して攻撃も可能なため、必要に応じて対策しなければならない。


1.はじめに

今までのコラムで、アプリの解析や改ざんについて紹介してきました。
しかし、今までのコラムの解析や改ざんは、静的なものがほとんどで、動的なものはほとんどありませんでした。
本コラムでは、解析ツールを使ったアプリの動的な改ざんについて解説していきます。

2.Fridaとは

Frida ( https://frida.re/ ) とはアプリの動的解析ツールです。
今までブログで紹介してきた静的解析とは違い、実行しているアプリに対して直接解析することが可能です。
Fridaの機能を使うと、解析だけでなく改変もできてしまいます。
今回は、Fridaを使ったアプリの動的改ざんについて紹介します。
アプリは、Androidアプリを使用します。
Fridaを使う場合はFrida用のサーバーを端末で動作させるために、root権限を持った端末が必要です。
しかし、そのような端末を必ずしも持っているとは限りません。そこで、Frida Gadgetを使用します。
Frida Gadgetを使用することで、root権限を持っていなくともFridaを使うことができます。
その代わり、アプリへの静的改ざんも必要になります。
次で詳細を説明していきます。

3.Frida Gadgetの準備

まず、Frida Gadgetによる解析・改ざん方法を説明するためにサンプルゲームアプリを用意しました。
説明のため、アプリは限りなくシンプルな内容、構成としています。
画面に表示される文字が一定時間ごとに変化し、
表示と同じ文字のボタンをタップすると画面中央上の数字がランダムに(1~10)増えるというゲームです。
このアプリでは、今回の話を簡単にするために、
増加する数値を取得する部分をlibtest.soというライブラリに実装しています。

このサンプルに対して、数字の増加を1~10ではなく、他のものに改ざんしたい攻撃者を想定します。

今回はFrida Gadgetを利用するため、Frida Gadgetファイルを配置します。
そして、アプリのDEXファイルを改ざんして、配置したFrida Gadgetファイルをロードするようにします。
アプリへのファイルの配置や処理の変更は、以前のコラムで使用したApktoolを使用します。
Apktoolによって展開されたフォルダにFrida Gadgetのライブラリファイルを配置します。
Frida Gadgetのライブラリファイルには様々なものがありますが、
今回はarm64-v8a用のものを使用することにします。そのため"lib/arm64-v8a/"に配置します。
今回、Frida Gadgetのライブラリファイルの名前は、公式サイトの例に倣ってlibgadget.soに変更しています。
また、同階層に設定ファイルが必要です。
設定ファイルはjson形式のテキストファイルですが、拡張子を".so"に変更し、
同様に例に倣って"libgadget.config.so"という名前にして配置します。
"libgadget.config.so"は以下のようなjson形式のテキストファイルです。

{
  "interaction": {
    "type": "listen",
    "address": "0.0.0.0",
    "port": 27042,
    "on_port_conflict": "fail",
    "on_load": "resume"
  }
}


結果として、"lib/arm64-v8a/"以下に"libgadget.so"と"libgadget.config.so"が追加されます。
なお、armeabi-v7a用を使用する場合もフォルダが違うだけで同様です。

/lib/arm64-v8a/
    libgadget.config.so
    libgadget.so

AndroidManifest.xmlを確認して"android.permission.INTERNET"がなければ追加します。

    <uses-permission android:name="android.permission.INTERNET"/>

また、extractNativeLibsがfalseならtrueにしておきます。

<application ...
             android:extractNativeLibs="true"
             ...

Frida Gadgetをロードする処理を書きます。
介入するタイミングまでに実行できればどこでもいいですが、
AndroidManifest.xmlでintent-filterを確認して
MAINが付与されているActivityのクラスのonCreateメソッドの先頭あたりに追加すると良いでしょう。

.method protected onCreate(Landroid/os/Bundle;)V
    .locals 3
    const-string v0, "gadget"
    invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V

・ライブラリと設定ファイルの配置
・"android.permission.INTERNET"の追加
・Frida Gadgetのロード処理の追加

以上ができたのを確認したらApktoolを使ってapkファイルを再構築します。
そして、再構築したアプリは署名されていないため、署名します。
以上の工程によりFridaから動的改ざんを受け付けるアプリの完成です。
このアプリをインストールします。
特に問題なく動作するはずです。

4.アプリに対する動的改ざん例

実際に動的改ざんを行っていきます。
Fridaを使う方法はいくつかありますが、今回はPythonを使っていきます。
Pythonのインストール方法などは公式サイトを参照ください。
Fridaをインストールする際は、配置したFrida Gadgetのライブラリと対応するfrida-toolsのバージョンを使用します。(実際はfrida-toolsをインストールしてから対応するFrida Gadgetのバージョンがわかるので、
 前後するかもしれません。)

pip install frida-tools==<Version>

では、改ざんをしていきます。
まずは、"lib/arm64-v8a/"以下の.soファイルを見て、数値を増やすのに関係していそうなものを見つけます。
今回のために用意した、libtest.soというライブラリもあります。
このライブラリのシンボルを見てみましょう。
今回はreadelfで見ています。

readelf -s libtest.so
    34: 0000000000007000     0 NOTYPE  GLOBAL DEFAULT  ABS __end__
    35: 0000000000000dac    56 FUNC    GLOBAL DEFAULT   11 getPoint
    36: 0000000000006000     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start__

"getPoint"といういかにもな名前のものがありますね。
これは関数のようですので、どのような値を返却しているのか見てみましょう。
以下のスクリプトを実行してgetPointの返却値を出力してみます。

Python testPrint.py

testPrint.py

import sys
import frida

def on_message(message, data):
    print(message)

js = """\
Java.perform(function() {

Interceptor.attach(Module.getExportByName('libtest.so', 'getPoint'), {
  onLeave(returnval) {
    console.log('getPoint return value: ' + returnval);
  }
});

});

"""

process = frida.get_usb_device().attach('Gadget')

script = process.create_script(js)
script.on('message', on_message)
script.load()

sys.stdin.read()
getPoint return value: 0x6
getPoint return value: 0x3

画面上の増える数値と返却されている値が一致しています。
この数値に介入できれば増える数値を変更できるかもしれません。

実際に変更してみます。
以下のスクリプトはgetPointに代わって実行され、常に10を返却するように改ざんしたものです。

Python test10.py

test10.py

import sys
import frida

def on_message(message, data):
    print(message)

js = """\
Java.perform(function() {

const getPointPtr = Module.getExportByName('libtest.so', 'getPoint');
Interceptor.replace(getPointPtr, new NativeCallback(() => {
    return 10;
}, 'int', []));

});

"""

process = frida.get_usb_device().attach('Gadget')

script = process.create_script(js)
script.on('message', on_message)
script.load()

sys.stdin.read()

10ずつ増えるようになりました。
この後、スクリプトを終了するとまた元の動作に戻ります。

このように準備のために一部DEXなどを変更する必要はありますが、
元のsoファイル(今回ならlibtest.so)を変更することなく、動作を改ざんすることができることがわかりました。

5. まとめ

今回のことから、Frida Gadgetによってアプリの動的改ざんができることがわかりました。
では、アプリ開発者はこのような攻撃に対してどのように対策を打つべきでしょうか。

①プロセスの外部化
まずは、このような致命的なデータはローカル側、すなわちクライアント側で扱わないということが考えられます。
例えば、クライアント側が持つアプリで行うのは表示のみで、あらゆる処理をサーバーで行えるようにすれば、
元になるデータそのものがクライアント側にないため、データの改変は防げるでしょう。
しかし、現実的にすべてのデータをリアルタイムで送受信する訳にはいかないため、
扱うデータを上手く選択する必要があります。
また、UIの操作はどうしてもクライアント側で行う必要があるため、
UI操作関係の改ざんまでは防ぐのは難しいでしょう。

②シンボルの難読化
今回のサンプルではgetPointという判りやすい名前を使っています。
これを判りにくい名前にするだけで、改ざんの難易度は上がるでしょう。
もちろん、完全に分からなくなったりシンボルがなくなったりするわけではないので、
100%安心というわけではありません。
シンボルの難読化については過去のコラムでも一部説明しているのでそちらもご覧ください。

③署名チェック機能
本コラムの方法ではFrida Gadgetを使用するために、アプリの再構成および再署名が必要です。
そのため、アプリビルド時の署名と異なるかチェックすることができます。
署名チェックについても過去のコラムで一部説明しているのでそちらもご覧ください。

これら1つ1つは、破られる可能性がある対策です。
しかし、これらの対策を併用する、あるいは他の対策を追加することで、改ざんの難易度を上げることが可能です。


当サイトでは、アプリのセキュリティについて詳しくわかる資料をご用意しています。
アプリのセキュリティや対策についてご興味を持たれた方は、
ぜひダウンロードしていただけますと幸いです。

ダウンロード資料一覧

また、弊社では
アプリへの不正な解析・改ざん行為(クラッキング)を防ぐ対策製品
「CrackProof」の開発・販売を行っております。
詳しく話を聞いてみたいと思われた方は、
当サイトからお気軽にお問い合わせください。

お問い合わせはこちら

6. 参考

Frida公式サイト:
https://frida.re