Root Detectionは、スーパーユーザー権限を持つデバイスでのAndroidアプリケーションの実行を防ぐセキュリティメカニズムです。銀行、決済、エンタープライズアプリケーションは、root化されたデバイスで機能をブロックまたは制限します。rootアクセスはAndroidサンドボックスの制限を解除し、トラフィックの傍受、プロセスメモリの読み取り、データ改ざんの可能性を開くためです。OWASP Mobile Top 10 (2024)によると、Root Detectionの欠如はカテゴリM8(Security Decisions via Untrusted Inputs)に該当します。Root Detectionは、ファイルシステムの静的チェックとランタイム動作の動的分析の組み合わせに基づいています。
重要ポイント
Root Detectionは、Androidデバイス上のrootアクセスの有無を検出するソフトウェアメカニズムです。rootアクセスはオペレーティングシステムを完全に制御し、アプリやスクリプトがUID 0でコマンドを実行できるようにします。root化されたデバイスでは、アプリの分離(Android Sandbox)が失われ、キーボード入力の傍受、他のアプリのSQLiteデータベースの読み取り、プロセスへのコード注入、信頼できるストアのSSL証明書の置換が可能になります。
金融およびエンタープライズアプリケーションにとって、root化されたデバイスでの実行は許容できないリスクをもたらします。攻撃者はトークン、セッションキー、個人データにアクセスできるようになります。PCI Security Standards Councilを含む規制当局は、決済アプリケーションにrootアクセスの検出と対応を義務付けています。これに対応して、Android開発者はプロアクティブな保護戦略の一部としてRoot Detectionを組み込んでいます。
検出には2つのアプローチがあります。ファイルシステムとインストール済みパッケージを分析する静的アプローチと、ランタイムでチェックを実行する動的アプローチです。組み合わせたアプローチが最も信頼性が高いと考えられています。これは、さまざまな回避ベクトルをカバーするためです。NowSecure(2025)の調査によると、Google Playのトップ100にある銀行アプリの76%が何らかの形のRoot Detectionを含んでいます。
静的メソッドはアプリケーション起動時に実行され、ルート化ツールがファイルシステムに残すrootアクセスの痕跡を確認します。これらのメソッドは特権コマンドの実行を必要とせず、通常のアプリケーションのコンテキストで動作します。
rootアクセスの主な指標は、標準パスでのsu実行可能ファイルの存在です:/system/bin/su、/system/xbin/su、/sbin/su、/su/bin/su。アプリケーションはFile.exists()またはlibcのaccess()のネイティブ実装を介してファイルの存在を確認します。さらに、su --versionまたはsu -c idを実行して終了コードを確認することもできます。
rootアクセスを管理する代表的なアプリケーション:Superuser、SuperSU、Magisk Manager、KingRoot。これらの存在は、PackageManager.getPackageInfo()または/data/app/ディレクトリの読み取りを介して確認されます。確認するパッケージ:com.topjohnwu.magisk、eu.chainfire.supersu、com.noshufou.android.su、com.thirdparty.superuser、com.koushikdutta.superuser、com.zacharee1.systemuituner。
AndroidはSystem.getPropertyおよびBuild.TAGSを介してアクセス可能なシステムプロパティにシステム状態情報を保存します。Build.TAGSにrelease-keysではなくtest-keysが含まれている場合、これはカスタムファームウェアを示し、多くの場合rootアクセスが可能です。さらに、/system/build.propを読み取ってro.build.tags、ro.debuggable、ro.secureを確認します。
public class RootDetectionChecker {
private static final String[] SU_PATHS = {
"/system/bin/su",
"/system/xbin/su",
"/sbin/su",
"/su/bin/su",
"/system/sd/xbin/su"
};
public boolean checkRootByFiles() {
for (String path : SU_PATHS) {
if (new File(path).exists()) {
return true;
}
}
return false;
}
public boolean checkRootByPackages(Context ctx) {
String[] packages = {
"com.topjohnwu.magisk",
"eu.chainfire.supersu",
"com.noshufou.android.su",
"com.koushikdutta.superuser"
};
for (String pkg : packages) {
try {
ctx.getPackageManager().getPackageInfo(pkg, 0);
return true;
} catch (PackageManager.NameNotFoundException e) {
// package not found
}
}
return false;
}
}
動的メソッドはアプリケーションの動作中に実行され、実行環境を分析します。静的メソッドとは異なり、Magisk HideやZygiskを介して隠されたルート化を検出できます。ファイル構造だけでなくシステムの動作をチェックするためです。
rootアクセスがあると、一部のシステムパーティションはro(読み取り専用)ではなくrw(読み書き)フラグでマウントされます。アプリケーションは/proc/mountsを読み取り、/systemがroとしてマウントされていることを確認します。/systemがrwとしてマウントされている場合、これは変更されたシステムを示します。さらに、Magiskを介した/suマウントの有無も確認されます。
Androidセーフモードは、root管理アプリを含むサードパーティアプリケーションを無効にします。適切に実装されたRoot Detectionは、デバイスがセーフモードで動作しているかどうかを確認できます。アプリケーションがroot管理アプリは表示されないがsuバイナリが存在することを検出した場合、これはMagisk Hideの指標です。
ProcessBuilderまたはRuntime.execを介してsu -c idを実行しようとすることは、rootアクセスの直接的なテストです。ただし、Magiskはこの呼び出しをインターセプトできます。より信頼性の高いアプローチは、ネイティブコードを介した確認です:/proc/1/limitsまたは/proc/self/mapsを開き、実行中のプロセスのUIDを分析します。アプリケーションがUID 0を取得できる場合、またはrootのみがアクセス可能なファイルを読み取れる場合、デバイスは危険にさらされています。
public boolean checkRootDynamically() {
// Build flags check
String buildTags = Build.TAGS;
if (buildTags != null && buildTags.contains("test-keys")) {
return true;
}
// Checking /system mount
try {
BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream("/proc/mounts"))
);
String line;
while ((line = reader.readLine()) != null) {
if (line.contains("/system")
&& line.contains("rw")) {
reader.close();
return true;
}
}
reader.close();
} catch (IOException e) {
// error reading mounts
}
return false;
}
Javaで実装されたRoot Detectionは、Javaメソッドをインターセプトして戻り値を置き換えるXposedモジュールやFridaを介して簡単に回避できます。JNIを介したC++でのネイティブ実装は、はるかに耐性があります。Javaレベルで動作する動的分析ツールは、stat、access、popen、dlopenなどのネイティブlibc呼び出しを認識できないためです。
#include <unistd.h>
#include <sys/stat.h>
#include <cstring>
#include <vector>
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_example_checker_RootCheck_nativeCheck(
JNIEnv* env, jobject instance) {
std::vector<const char*> paths = {
"/system/bin/su",
"/system/xbin/su",
"/sbin/su",
"/data/local/su"
};
struct stat st;
for (const char* path : paths) {
if (stat(path, &st) == 0) {
return JNI_TRUE;
}
}
return JNI_FALSE;
}
ネイティブチェックはJava APIを使用しないため、Dalvik/ARTレベルで動作する回避ツールからは見えません。追加の保護として、定数(パスリスト)を読み取り専用セクションに保存するのではなく、単純な可逆関数を介して計算することをお勧めします。libcのstat呼び出しはJavaラッパーをバイパスしてLinuxカーネルに直接アクセスし、Xposedを介してインターセプトすることはできません。
保護開発者は、堅牢な検出システムを構築するために、既存の回避手法を理解する必要があります。各回避手法には、適切なレベルでの対策が必要です。
MagiskはAndroid 9–14で最も人気のあるルート化ツールです。Magisk Hideは/procからsuの存在を隠し、パスチェックの結果を偽装します。Magiskはカーネルレベルで動作し、アプリケーションが確認する前にstat()とaccess()をインターセプトします。対策:/sbin/.magiskの存在を介してMagisk自体の有無を確認するか、アプリケーション自身のmapsを読み取って確認します。Magiskはすべてのプロセスに自身のライブラリを注入します。
Fridaは、PtraceまたはDobbyを介してネイティブ関数をインターセプトできる動的インストルメンテーションツールです。Fridaは任意のチェックの戻り値を置き換え、statの結果をENOENTに偽装します。対策:メモリ内の命令のチェックサムを計算してネイティブ関数の整合性を検証し、frida-agent.soまたはfrida-helperの存在について/proc/self/mapsを分析してFridaを検出します。
Javaで実装されたRoot Detectionは2〜3分で削除できます。APKはapktoolを介して逆コンパイルされ、smaliコードでメソッドの戻り値がfalseに変更され、APKが再構築されて署名されます。対策:重要なロジックをネイティブコードに移行し、Signature APIを介してランタイムでアプリケーションのデジタル署名を検証するか、APKハッシュをサーバー上の参照と比較します。
効果的なRoot Detectionは多層アーキテクチャに基づいて構築されています。単一のメソッドだけでは十分な保護を提供できません。静的および動的チェック、ネイティブコード、サーバー側検証の組み合わせが最大の耐性を提供します。
クライアント側のチェックだけに依存しないでください。Root Detectionの結果をワンタイムセッショントークンとともにサーバーに送信します。サーバーが機能をブロックまたは制限するかどうかを決定します。これにより、クライアントアプリケーションが変更される可能性があるがサーバーが信頼できるままであるAPIレベルでの攻撃を防ぎます。
Root Detectionコードは難読化する必要があります。攻撃者がjadxでsuパスチェックの明確なシーケンスを見た場合、回避には数分しかかかりません。ProGuardまたはDexGuardを使用して制御フローを難読化し、文字列を暗号化します。難読化により、保護コードの分析時間が数分から数時間に増加します。
チェック対象のパス、パッケージ、インジケーターのリストは、アプリケーションのリリースごとに更新する必要があります。新しいルート化ツールや回避ツールは毎月登場します。1年間変更されていない静的リストでは、最新の手法を検出できません。チェックを実行する前に、アプリケーション起動時にサーバーから最新のシグネチャをロードすることをお勧めします。
よくある質問
Root Detectionは、Androidサンドボックスが無効になっているデバイスでのアプリケーション実行から保護します。root化されたデバイスでは、任意のアプリケーションが他のアプリケーションのデータを読み取ることができます。銀行および決済アプリケーションは、PCI DSS要件およびOWASP Mobile Securityの推奨事項に従って、root化されたデバイスでの動作をブロックする義務があります。
Magisk Hideはマウント名前空間メカニズムを使用します。除外リスト内の各プロセスに対して、Magiskはsuバイナリが見えない隔離された名前空間を作成します。この名前空間でのシステムコールstat、access、openはMagiskファイルを認識しません。/proc/self/mapsの存在を確認し、magiskダンプを検索することでMagiskを検出できます。
はい、アプリケーションがコードの整合性を検証しない場合可能です。Fridaを介して、Javaのチェックメソッドをインターセプトし、強制的にfalseを返させることができます。対策は、C++での重要なロジックのネイティブ実装とDEXファイルハッシュによる整合性検証です。難読化なしでは、Java上のRoot Detectionは5〜10分で回避されます。
SafetyNet(非推奨)とPlay Integrity APIは、デバイスの整合性を確認するGoogleのサーバー側チェックです。ブートローダー、システム署名、rootステータスの確認が含まれます。Play Integrity APIはSafetyNetの推奨代替であり、BASIC、DEVICE、STRONGの3つのレベルを提供します。クライアント側のRoot Detectionは、サーバー側のアテステーションを補完します。
実際のroot化されたデバイス(MagiskをインストールしたPixelなど)にアプリケーションをインストールします。ブロックが機能するか確認します。次に、アプリケーションに対してMagisk Hideを介してrootを隠し、テストを再実行します。詳細なテストには、Fridaを使用してターゲットメソッドをインターセプトし、ネイティブ保護が回避できないことを確認します。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。