🥚

FridaでAndroidの(簡易な)Root検出をバイパスする

2022/01/22に公開

※ 2022/06/22:リンク先が間違っていたのを修正

注意事項

  • 本記事は、モバイルアプリ診断用のためのTipsであり、悪用厳禁です。
  • Root化はメーカーの保証対象外となり、また意図しないセキュリティリスクを引き起こす場合があるため、実機で行う場合は、個人情報などの機微な情報が保存されていない端末での実施を推奨します。
    • 本記事では、AVDでのエミュレータで実施します。

概要

  1. Root化とRoot検出とは
  2. テストアプリで検証

Root化とRoot検出とは

  • Root化:本来ユーザ権限で動作しているAndroidシステムに対して、特権(root権限)を取得すること。
    • ユーザ権限だと制限されている行為を制限を迂回して実行できるようになる。
    • モバイルアプリ診断だと、アプリとサーバの通信傍受のためのプロキシ証明書をシステムレベルで信頼させる、Fridaなどの診断用ツールのインストールなどの目的で利用する。
  • Root検出:アプリを実行している端末が、Root化されていないかをチェックする行為のこと。
    • 通常の(Root化されていない)端末では存在しない権限やファイルなどをチェックする場合が多い。
      • Superuser.apkがインストールされているか
      • /sbin/suディレクトリが存在するか
      • システムディレクトリに書き込み権限が付与されているか など

テストアプリで検証

  • 今回は、AndroGoatというテスト用Appを使用します。
  1. AndroGoatを起動し、「ROOT DETECTION」をタップすると、下記のような画面が表示されます。

    • このページでは、「CHECK ROOT」ボタンをタップすると、Root検出メソッドが起動し、このAppを実行している端末がRoot課されているかをチェックします。
    • Root化されている場合「Device is rooted」が、そうでない場合は「Device is not rooted」のメッセージが表示されます
  2. AndroGoatを解凍する。

    $ unzip AndroGoat.apk -d AndroGoat
    
  3. jadxで、2.で解凍したディレクトリにあるclasses.dexを開きます。

    • 開いた後、owasp.sat.agoat.RootDetectionActivityを開きます。
    RootDetectionActivity.java
    public final boolean isRooted() {
        // デフォルトでは非Root判定(false)
        boolean result = false;
        // Root端末でよく用いられるAPKやコマンドのパスリストに対して、存在するかを確認する
        for (String files : new String[]{
            "/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"
        }) {
            // 上記のリストのパスが存在している場合、trueが返される
            result = new File(files).exists();
            if (result) {
                break;
            }
        }
        // Root判定の結果を返す
        return result;
    }
    
    • 実際にAVDにadbコマンドで接続し、suコマンドのパスを確認すると下記のとおりとなるため、上記で検出される状態です。

      generic_x86:/ $ which su
      /system/xbin/su
      
    • → この関数がfalseを返すようにすれば、実際はRoot端末であってもRoot検出を回避できます。

  4. Fridaを使用して、この関数(isRooted)をフックし、常にfalse(非Root)を返すようなスクリプトを作成します。

    Java.perform(function () {
      send("Start Script");
    
      // AndroGoatのROot DetectionのActivityを指定
      var rootDetectionActivity = Java.use("owasp.sat.agoat.RootDetectionActivity");
      // isRooted関数をフック
      rootDetectionActivity.isRooted.implementation = function () {
        send("Hooking isRooted method");
        // 常にfalse(非Root)の結果が返るように変更
        return false;
      }
    })
    
    • 作成したFridaのJSコードをPythonで実行するためには、拙作の『FirdaでAndroid APKをHookingする』を参考ください。
    • 結果として、下記のようにRoot検出を回避できます。

Discussion