概要
モバイルアプリセキュリティテストの演習を行うことができるAndroGoatを用いて、セキュリティテストのやり方をまとめていく。
検証環境:Android11エミュレータ(Pixel4a)
使用ツール:adb, jadx, Frida(16.6)
問題
Check Rootというボタンがあり、それを押すとDevice is rootedとでる。
これが出ないようにするのが今回の目的である。
ルート化してる状態でどのようにしてその検知を逃れるかを実際に見ていこう。


解答
1. ルート化とは
OSを改変し、ルートユーザーとしてコマンドを実行できるようにすることです。
これにより、OSを完全に制御できるようになり、アプリのサンドボックス化などの制限を回避できます。これらの権限により、コードインジェクションや関数フックといったテクニックをより容易に使用できるようになります。
しかしながら以下のようなリスクを伴います。
- デバイスの保証が無効になる
- デバイスが「ブリック」して(通称文鎮化)使えなくなる
- 追加のセキュリティリスクが生じる
よって普段遣いするような個人用デバイスはルート化せず、実機をルート化する場合テストデバイスを用意しましょう。(趣味で検証するのであればエミュレータで事足ります)
2.ルート化検知の仕組み
先ほど述べたように、ルート化は様々リスクを引き起こします。よってアプリ開発者(得にカード情報など重要な個人情報などを取り扱う場合は特に)は、ルート化を検知してルート化されているデバイスでは通常通り動かないようにすることが重要です。
以下MASTGの0x5jに具体例なルート化検出の方法が書かれています。
そこではRootBeerというライブラリを使って実装するやり方の他に、プログラムの検出で最も一般的なのはルート化されたデバイスに通常存在するファイルをチェックすることとあります。
/system/app/Superuser.apk
/system/etc/init.d/99SuperSUDaemon
/dev/com.koushikdutta.superuser.daemon/
/system/xbin/daemonsu
またディレクトリの他にデバイスがルート化された後にインストールされるバイナリを探すこともよくあるとのことです。
/sbin/su
/system/bin/su
/system/bin/failsafe/su
/system/xbin/su
/system/xbin/busybox
/system/sd/xbin/su
/data/local/su
/data/local/xbin/su
/data/local/bin/su
では実際に今回のアプリで実際にどのようにしてルート化検知が行われているか見てみましょう。
jadxを使ってowasp.sat.agoat内のRootDetectionActivityクラスを見てみます。
onCreate関数内にある18行目のonClick関数を見てみると、isRootedメソッドにてデバイスがルート化されているかを判断していることがわかります。
すぐ下のisRooted()をみてみると、以下のfileが存在するかを確かめています。
String[] file = {"/system/app/Superuser/Superuser.apk", "/system/app/Superuser.apk", "/sbin/su", "/system/bin/su", "/system/xbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su", "/system/bin/failsafe/su", "/data/local/su", "/su/bin/su", "re.robv.android.xposed.installer-1.apk", "/data/app/eu.chainfire.supersu-1/base.apk"};
先程挙げた方法と合致しています。
これでどのようにしてルート化を検知するかがわかりました。
しかしながらMASTGにはこうも記載されています
ルート化検出自体はそれほど効果的ではありませんが、アプリ全体に複数のルート化チェックを実装することで、全体的な改ざん防止スキームの有効性を高めることができます。
つまりルート化をするだけでは根本的な解決にならないと言うことです。それを次のステップで確認していきます。
3.ルート化検知のバイパス
では今回の目的を果たすために、先ほどのルート化検知を実際に回避して見たいと思います。
3.1~3とありますが、今回は3のFridaというツールを使った方法で行います。
Fridaの公式サイトには
Dynamic instrumentation toolkit for developers, reverse-engineers, and security researchers.
開発者、リバース エンジニア、セキュリティ研究者向けの動的インストルメンテーション ツールキットとあります。
今回のようなAndroidアプリに対するセキュリティ診断の場面では、apkをhookしたりして使います。
hookとはアプリの実行過程に割り込んで、処理の監視や変更を行うことです。
今回はroot化検知のメソッドを無理矢理trueになるように変更します。
え、そんなことできるのと思われるかもしれませんが、できます。しかもAndroidだけでなくiosやLinux,windows、MacOSまで 幅広く使えて無料です。
Fridaのダウンロードは、自身のパソコンとテストするスマホの両方に入れる必要があります。
正確にはfrida-toolsをパソコンに入れて、スマホにfrida-serverを入れる必要があります。
まずvenvなどで仮想環境をactivateした後、pipでダウンロードしてバージョンを確認します。
pip install frida-tools
frida --version
その後以下のリポジトリにあるfrida-serverから先ほどのバージョンとエミュレータ(スマホ)のアーキテクチャに一致するものをダウンロードします。
アーキテクチャはx86やArmなどがあり、エミュレータならSDKマネージャー(エミュレータを立てる画面)やadbでシェルに入った後getporp | grep cpuなどで調べられます。
frida-serverをダウンロードしたら、adb push path/frida-server1x.. /data/local/tmp
でスマホ/エミュレータに送る。(1x..はバージョン)
その後adb shellに入った後suで昇格して、先ほど入れたfrida-serverの権限をchmodで変える。
その後frida-serverのパスを指定して実行。
# chmod 755 /data/local/tmp/frida-server1x..
# /data/local/tmp/frida-server1x... &
(adb内かつsuであることを示すために#を先頭に入れています。)
最後に&をつけているのは、ターミナルを新たにたてるのが面倒なのでバックグラウンドで実行しています。
fgで切り替えられます。
これでセットアップが完了しました。
Fridaでhookする方法はいくつかありますが、今回はJavaScriptでhookコードを書いてpythonでそれを実行したいと思います。
以下がhook用JSコードです。
Java.perform(function() {
send("Start Script");
var rootDetectionActivity = Java.use("owasp.sat.agoat.RootDetectionActivity");
rootDetectionActivity.isRooted.implementation = function (){
send("Hooking is Rooted method")
return false;
}
})
まず、Java.use(パッケージ+クラス名)を変数に確認します。クラス名とパッケージ名は先ほどのjadxでコードから確認します。
その後 rootDetectionActivity.isRooted.implementation = function(){
でhookする関数の処理をimplementationというfrida独自の関数を使って、以降の無名関数内の処理に書き換えます。
今回は確認のためのコンソールログとfalseを返すようにしています。
これでどんなときもfalseが返るので、ルート化検出をバイパスすることができるようになります。
以下がpythonのコードです。
import frida
import sys
# ターゲットのプロセス名を記入
APK_PROCESS_NAME = "AndroGoat - Insecure App (Kotlin)"
# Hookするデバイス名を記入
DEVICE_NAME = "emulator-5554"
def on_message(message, data):
if message["type"] == "send":
print(f"[*] {message['payload']}")
else:
print(message)
jscode = """
先ほどのJSコードをここに入れる
"""
device = frida.get_usb_device()
process = device.attach(APK_PROCESS_NAME)
script = process.create_script(jscode)
script.on("message", on_message)
print("[*] Running")
script.load()
sys.stdin.read()
deviceから説明します。
get_usb_device()は文字通りusb接続しているデバイスを取得しますが、内部的にはエミュレータはUSBと同等に扱われるのでこれで問題ないです。
frida.get_remote_device()もありますが、エミュレータはデフォルトでTCPに接続していないため、adb forwardでポートフォワードする必要があるので面倒です。
その後attach(APK_PROCESS_NAME)でプロセスをアタッチしています。
補足:基本的にプロセス名はパッケージ名と一致しますが、今回のように変えられているばあいもあります。
そのようなときは、パソコンのターミナルで
frida-ps -Uというコマンドで調べることができます。数が多いのでgrepをパイプすることをおすすめします。今回はgrep oatでました。
frida-ps -U | grep oat
そしてcreate_scriptでjsのスクリプトを読み込み、on("message", on_message)でメッセージを受信します。
最後にscript.load()で実際にhookを有効化しています。
sys.stdin.read()は標準入力を受け付けていて、勝手にプロセスが終わらないようにしています。
これをfrida-serverを実行状態にしてpython script.pyで実行すると
Device is not rootedが出ます。

まとめ
今回はルート化検知の重要性とともに、ルート化検知だけでは簡単にバイパスできることを確認しました。
実際には、Fridaの検知などその他の防御策と組み合わせてルート化検知を行うことによって、強大な力を発揮します。ルート化検知を実装したから安心と思わず、1防御策の一つと認識して実装しましょう。