Android root検出方法まとめ

13632 ワード

なぜroot検出を行うのですか?
セキュリティ上の理由から、私たちのアプリケーションはroot済みのデバイスで実行することを推奨しません.そのため、デバイスがrootされているかどうかを検出して、ユーザーが使用を続けるとリスクがあることを示す必要があります.
二rootになったらどんなリスクがありますか?
Linuxオペレーティングシステムではrootの権限が最も高く、スーパー権限の所有者とも呼ばれています.システムでは、各ファイル、ディレクトリ、およびプロセスは、あるユーザーに属し、他の一般ユーザーを許可しないと操作できませんが、rootを除きます.
rootユーザーの特権は、rootが任意のユーザーとユーザーグループを超えてファイルまたはディレクトリを読み取り、変更または削除することができる(システムの正常なライセンス範囲内).実行可能プログラムの実行、終了;ハードウェアデバイスの追加、作成、削除など;ファイルとディレクトリを所有者と権限に変更して、システム管理のニーズに適応することもできます(rootはシステムの中で最も権限の高い特権ユーザーであるため).rootは任意のユーザとユーザグループを超えており,ユーザIDに基づく権限メカニズムの砂箱はそれを隔離できない.
三不rootはどうして安全になったのですか?
AndroidセキュリティアーキテクチャはLinuxマルチユーザメカニズムに基づくアクセス制御である.アプリケーションは、デフォルトでは、連絡先データやemailデータなどのユーザーのプライベートデータの読み取りや書き込み、別のアプリケーションのファイルの読み取りや書き込みなど、他のアプリケーションを実行できません.
1つのアプリケーションのプロセスは、安全な砂箱です(制限された安全な環境でアプリケーションを実行し、砂箱のすべての変更がオペレーティングシステムに危害を及ぼさない).「permissions」が明示的に宣言されていない限り、他のアプリケーションを干渉することはできません.これにより、基本的な砂箱にはない追加の能力を得ることができます.Androidアプリケーションごとにインストール時に独自のLinuxユーザーIDが割り当てられ、他のアプリケーションと接触できない砂箱が作成されます.このユーザIDは、インストール時に割り当てられ、デバイス上で同じ数値が保持されます.
すべてのAndroidアプリケーションは証明書で署名認証を行う必要がありますが、この証明書の秘密鍵は開発者が保有しています.この証明書は、アプリケーションの作成者を識別するために使用できます.署名がセキュリティに影響を与える最も重要な方法は、誰が署名ベースのpermissionsに入ることができるか、誰がshareユーザーIDsに入ることができるかを決定することです.このようなメカニズムによりrootユーザを考慮せずに,各アプリケーションが互いに隔離され,一定のセキュリティが実現される.
四rootの方法はどれらがありますか?
通常は2種類に分けられ、不完全Rootと完全Root
現在Android root権限を取得する一般的な方法は、さまざまなシステムの脆弱性を通じて、SUプログラムをデバイスに置き換えたり追加したりしてRoot権限を取得することですが、root権限を取得した後、プログラムをインストールしてユーザーにプログラムの最高権限を与えるかどうかを注意し、悪意のあるソフトウェアをある程度防止することができます.通常はSuperuserやSuperSUを使用します.この方法は通常「不完全Root」と呼ばれています.
「完全ROOT」とは、デバイスの既存のROMを置き換えて、secure設定のキャンセルを実現することを意味する.
5 Androidデバイスのrootを検出するにはどのような方法がありますか?
1、システムがテスト版かどうかを確認する
リリースされたシステムバージョンはtest-keys(テスト版)かrelease-keys(正式版)かを確認できます.adb shellで次のコマンドを実行して表示できます.
root@android:/ # cat /system/build.prop | grep ro.build.tags
ro.build.tags=release-keys

この戻り結果「release-keys」は、このシステムが正式版であることを意味します.コードの検出方法は次のとおりです.
public static boolean checkDeviceDebuggable() {
        String buildTags = android.os.Build.TAGS;
        if (buildTags != null && buildTags.contains("test-keys")) {
            Log.i(LOG_TAG, "buildTags=" + buildTags);
            return true;
        }
        return false;
    }

非公式リリース版であれば、完全rootのバージョンである可能性が高く、使用リスクがあります.しかし、実際には、いくつかのメーカーの正式なリリースバージョンに遭遇したことがあります.test-keysでもあります.このマークにも特に注意していないかもしれません.だから具体的に使うかどうか、もっと考えなければなりません.問題を解決できるかもしれないし、自分に迷惑をかけるかもしれない.
2、Superuserが存在するかどうかを検査する.apk
Superuser.apkはrootアンドロイドデバイス用に広く使用されているソフトウェアなので、このappが存在するかどうかを確認することができます.検出方法は以下の通りである.
public static boolean checkSuperuserApk() {
        try {
            File file = new File("/system/app/Superuser.apk");
            if (file.exists()) {
                Log.i(LOG_TAG, "/system/app/Superuser.apk exist");
                return true;
            }
        } catch (Exception e) {
        }
        return false;
    }

3、suコマンドをチェックする
suはLinuxの下でユーザを切り替えるコマンドで、使用時にパラメータを持たずにスーパーユーザに切り替える.通常root権限を取得するのはsuコマンドを使用して実現されるので、このコマンドが存在するかどうかを確認できます.これにより、システムはPATHパスでsuを検索し、見つけたら実行し、実行に成功すると、本当のスーパー権限を取得します. 
public static synchronized boolean checkGetRootAuth() {
        Process process = null;
        DataOutputStream os = null;
        try {
            Log.i(LOG_TAG, "to exec su");
            process = Runtime.getRuntime().exec("su");
            os = new DataOutputStream(process.getOutputStream());
            os.writeBytes("exit
"); os.flush(); int exitValue = process.waitFor(); Log.i(LOG_TAG, "exitValue=" + exitValue); if (exitValue == 0) { return true; } else { return false; } } catch (Exception e) { Log.i(LOG_TAG, "Unexpected error - Here is what I know: " + e.getMessage()); return false; } finally { try { if (os != null) { os.close(); } process.destroy(); } catch (Exception e) { e.printStackTrace(); } } }

このsuを検出する方法は、最も頼りになるはずですが、root済みのデバイスでは、プロンプトボックスがポップアップされ、appにroot権限を開くように要求されるという問題もあります.このヒントはあまり友好的ではありません.ユーザーが気に入らないかもしれません.静かに検出したいなら、2つの方法の組み合わせを使うことができます.できるだけ安全に検出する必要がある場合は、suを実行しましょう.
4、busyboxの実行
AndroidはLinuxシステムに基づいていますが、端末Terminalで操作すると、基本的なコマンドが見つからないことがわかります.これはAndroidシステムが安全のためにリスクをもたらす可能性のあるコマンドをすべて削除したためで、最も典型的なのはsu、find、mountなどです.すでにスーパー権限を取得している人にとっては、不快なことなので、自分に必要な命令を加える方法を考えなければなりません.一つ一つコマンドを追加するのも面倒で、「埋め込みLinuxのスイス軍刀」と呼ばれるBusyboxを使うのが便利です.簡単に言えばBusyBoxは大きなツールボックスのようで、Linuxの多くのツールとコマンドを統合して圧縮しています.そのため、デバイスがrootになったら、Busyboxもインストールされている可能性があります.これによりbusyboxテストを実行するのも良い検出方法です. 
public static synchronized boolean checkBusybox() {
        try {
            Log.i(LOG_TAG, "to exec busybox df");
            String[] strCmd = new String[]{"busybox", "df"};
            ArrayList execResult = executeCommand(strCmd);
            if (execResult != null) {
                Log.i(LOG_TAG, "execResult=" + execResult.toString());
                return true;
            } else {
                Log.i(LOG_TAG, "execResult=null");
                return false;
            }
        } catch (Exception e) {
            Log.i(LOG_TAG, "Unexpected error - Here is what I know: "
                    + e.getMessage());
            return false;
        }
    }

5、アクセス/dataディレクトリ、読み書き権限の表示
Androidシステムでは、/data、/system、/etcなど、一般ユーザーがアクセスできないディレクトリがあります.例として/dataを使用して、読み書きアクセスを行います.慎重な態度で、私はまずファイルを書いて、それから読み出して、内容が一致しているかどうかを見て、一致すれば、システムがrootしていると思っています.
public static synchronized boolean checkAccessRootData() {
        try {
            Log.i(LOG_TAG, "to write /data");
            String fileContent = "test_ok";
            Boolean writeFlag = writeFile("/data/su_test", fileContent);
            if (writeFlag) {
                Log.i(LOG_TAG, "write ok");
            } else {
                Log.i(LOG_TAG, "write failed");
            }

            Log.i(LOG_TAG, "to read /data");
            String strRead = readFile("/data/su_test");
            Log.i(LOG_TAG, "strRead=" + strRead);
            if (fileContent.equals(strRead)) {
                return true;
            } else {
                return false;
            }
        } catch (Exception e) {
            Log.i(LOG_TAG, "Unexpected error - Here is what I know: "
                    + e.getMessage());
            return false;
        }
    }

六、具体的にどのように使いますか.
次のコードを呼び出すだけで
CheckRoot.isDeviceRooted()
  CheckRoot.java       :
import android.util.Log;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;

public class CheckRoot {
    private static String LOG_TAG = CheckRoot.class.getName();

    public static boolean isDeviceRooted() {
        if (checkDeviceDebuggable()) {
            return true;
        }//check buildTags
        if (checkSuperuserApk()) {
            return true;
        }//Superuser.apk
        //if (checkRootPathSU()){return true;}//find su in some path
        //if (checkRootWhichSU()){return true;}//find su use 'which'
        if (checkBusybox()) {
            return true;
        }//find su use 'which'
        if (checkAccessRootData()) {
            return true;
        }//find su use 'which'
        if (checkGetRootAuth()) {
            return true;
        }//exec su

        return false;
    }

    public static boolean checkDeviceDebuggable() {
        String buildTags = android.os.Build.TAGS;
        if (buildTags != null && buildTags.contains("test-keys")) {
            Log.i(LOG_TAG, "buildTags=" + buildTags);
            return true;
        }
        return false;
    }

    public static boolean checkSuperuserApk() {
        try {
            File file = new File("/system/app/Superuser.apk");
            if (file.exists()) {
                Log.i(LOG_TAG, "/system/app/Superuser.apk exist");
                return true;
            }
        } catch (Exception e) {
        }
        return false;
    }

    public static synchronized boolean checkGetRootAuth() {
        Process process = null;
        DataOutputStream os = null;
        try {
            Log.i(LOG_TAG, "to exec su");
            process = Runtime.getRuntime().exec("su");
            os = new DataOutputStream(process.getOutputStream());
            os.writeBytes("exit
"); os.flush(); int exitValue = process.waitFor(); Log.i(LOG_TAG, "exitValue=" + exitValue); if (exitValue == 0) { return true; } else { return false; } } catch (Exception e) { Log.i(LOG_TAG, "Unexpected error - Here is what I know: " + e.getMessage()); return false; } finally { try { if (os != null) { os.close(); } process.destroy(); } catch (Exception e) { e.printStackTrace(); } } } public static synchronized boolean checkBusybox() { try { Log.i(LOG_TAG, "to exec busybox df"); String[] strCmd = new String[]{"busybox", "df"}; ArrayList execResult = executeCommand(strCmd); if (execResult != null) { Log.i(LOG_TAG, "execResult=" + execResult.toString()); return true; } else { Log.i(LOG_TAG, "execResult=null"); return false; } } catch (Exception e) { Log.i(LOG_TAG, "Unexpected error - Here is what I know: " + e.getMessage()); return false; } } public static ArrayList executeCommand(String[] shellCmd) { String line = null; ArrayList fullResponse = new ArrayList(); Process localProcess = null; try { Log.i(LOG_TAG, "to shell exec which for find su :"); localProcess = Runtime.getRuntime().exec(shellCmd); } catch (Exception e) { return null; } BufferedWriter out = new BufferedWriter(new OutputStreamWriter(localProcess.getOutputStream())); BufferedReader in = new BufferedReader(new InputStreamReader(localProcess.getInputStream())); try { while ((line = in.readLine()) != null) { Log.i(LOG_TAG, "–> Line received: " + line); fullResponse.add(line); } } catch (Exception e) { e.printStackTrace(); } Log.i(LOG_TAG, "–> Full response was: " + fullResponse); return fullResponse; } public static synchronized boolean checkAccessRootData() { try { Log.i(LOG_TAG, "to write /data"); String fileContent = "test_ok"; Boolean writeFlag = writeFile("/data/su_test", fileContent); if (writeFlag) { Log.i(LOG_TAG, "write ok"); } else { Log.i(LOG_TAG, "write failed"); } Log.i(LOG_TAG, "to read /data"); String strRead = readFile("/data/su_test"); Log.i(LOG_TAG, "strRead=" + strRead); if (fileContent.equals(strRead)) { return true; } else { return false; } } catch (Exception e) { Log.i(LOG_TAG, "Unexpected error - Here is what I know: " + e.getMessage()); return false; } } // public static Boolean writeFile(String fileName, String message) { try { FileOutputStream fout = new FileOutputStream(fileName); byte[] bytes = message.getBytes(); fout.write(bytes); fout.close(); return true; } catch (Exception e) { e.printStackTrace(); return false; } } // public static String readFile(String fileName) { File file = new File(fileName); try { FileInputStream fis = new FileInputStream(file); byte[] bytes = new byte[1024]; ByteArrayOutputStream bos = new ByteArrayOutputStream(); int len; while ((len = fis.read(bytes)) > 0) { bos.write(bytes, 0, len); } String result = new String(bos.toByteArray()); Log.i(LOG_TAG, result); return result; } catch (Exception e) { e.printStackTrace(); return null; } } }

​​​​