Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
ResourcePoison — CVE-2025-22441のライトアップとエクスプロイト:Androidでインストール済みアプリからSystemUIプロセスへの権限昇格。信頼されていないApplicationInfoがLoadedApkに渡されることが原因。 | Kitploit
ツール/GitHubGitHub/michalbednarski/resourcepoison
Androidセキュリティ特権昇格脆弱性分析エクスプロイトモバイルセキュリティ論文と研究
GitHubmichalbednarski/resourcepoison

ResourcePoison

CVE-2025-22441のライトアップとエクスプロイト:Androidでインストール済みアプリからSystemUIプロセスへの権限昇格。信頼されていないApplicationInfoがLoadedApkに渡されることが原因。

リポジトリを見る
1062610ヶ月前Kitploit レビュー済み

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

この問題の修正はCVE-2025-22441として公開されています: bulletin patch follow up

ApplicationInfoの受け渡し

ApplicationInfoは、インストールされたアプリに関するさまざまな情報を定義する構造体であり、特にリソースとコードが読み込まれるAPKファイルへのパスが最も重要です。

通常はシステムからアプリケーションへ渡されますが、システム以外の呼び出し元が独自のものを提供できるケースが存在することもあります。例えば過去には、bindBackupAgent()メソッドの脆弱性があり、攻撃者がuidとsourceDirの値を持つ独自のApplicationInfoオブジェクトをパラメータとして渡せたものの、それらがシステムに実際にインストールされているアプリと照合されていませんでした。このメソッドはsystem_serverによって内部的に呼び出されることを想定していましたが、adb shellに公開されていました。

今回は、RemoteViews内のApplicationInfoフィールドを詳しく調査しました。

RemoteViewsは、別のプロセスから取得できるビューを記述するオブジェクトです。これは特にホーム画面ウィジェットに使用され、ウィジェットを提供するアプリがRemoteViewsを構築し、それがホーム画面プロセス内で"適用"されます。

RemoteViewsが使用される他の場所としては、通知(SystemUIプロセスによって適用)やオートフィルダイアログ(オートフィルサービスによって提供され、system_serverによって適用)があります。

RemoteViews.mApplicationフィールドはParcelを通じてシリアライズされるため、リモートプロセスから取得される可能性があり、RemoteViewsが適用されるたびに以下のメソッドで使用されます(ソーススニペット):```java private Context getContextForResourcesEnsuringCorrectCachedApkPaths(Context context) { if (mApplication != null) { if (context.getUserId() == UserHandle.getUserId(mApplication.uid) && context.getPackageName().equals(mApplication.packageName)) { return context; } try { LoadedApk.checkAndUpdateApkPaths(mApplication); return context.createApplicationContext(mApplication, Context.CONTEXT_RESTRICTED); } catch (NameNotFoundException e) { Log.e(LOG_TAG, "Package name " + mApplication.packageName + " not found"); } }

root@kitploit:~
return context;

}

root@kitploit:~
ここで最も興味深いのは、`LoadedApk.checkAndUpdateApkPaths()` の呼び出しです。これは静的メソッドであり、いくつかのグローバル状態を変更します [(スニペットのソース)](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/LoadedApk.java;l=2275-2302;drc=5916ee589c4880e2d8a1a9ad6dc852108e4c44c1)```java
public static void checkAndUpdateApkPaths(ApplicationInfo expectedAppInfo) {
    // Get the LoadedApk from the cache
    ActivityThread activityThread = ActivityThread.currentActivityThread();
    if (activityThread == null) {
        Log.e(TAG, "Cannot find activity thread");
        return;
    }
    checkAndUpdateApkPaths(activityThread, expectedAppInfo, /* cacheWithCode */ true);
    checkAndUpdateApkPaths(activityThread, expectedAppInfo, /* cacheWithCode */ false);
}

private static void checkAndUpdateApkPaths(ActivityThread activityThread,
        ApplicationInfo expectedAppInfo, boolean cacheWithCode) {
    String expectedCodePath = expectedAppInfo.getCodePath();
    LoadedApk loadedApk = activityThread.peekPackageInfo(
            expectedAppInfo.packageName, /* includeCode= */ cacheWithCode);
    // If there is load apk cached, or if the cache is valid, don't do anything.
    if (loadedApk == null || loadedApk.getApplicationInfo() == null
            || loadedApk.getApplicationInfo().getCodePath().equals(expectedCodePath)) {
        return;
    }
    // Duplicate framework logic
    List<String> oldPaths = new ArrayList<>();
    LoadedApk.makePaths(activityThread, expectedAppInfo, oldPaths);

    // Force update the LoadedApk instance, which should update the reference in the cache
    loadedApk.updateApplicationInfo(expectedAppInfo, oldPaths);
}

これらのメソッドで何が行われているのか説明しましょう

まず、cacheWithCode をtrueとfalseの両方に設定して、3パラメータのcheckAndUpdateApkPathsを呼び出しています。LoadedApkオブジェクトは2つのモードで構築でき、mIncludeCodeがtrueかfalseのいずれかになります。これはSDK的には、CONTEXT_INCLUDE_CODEフラグが設定されたContextかどうかに対応します

そのメソッドはまずActivityThread.peekPackageInfo()を使用します。これはすでにキャッシュされたLoadedApkインスタンスを返すため、アプリが以前に一致するpackageNameとincludeCodeでLoadedApkを構築していなければ、peekPackageInfo()はnullを返し、checkAndUpdateApkPaths()は何も行いません

RemoteViews.getContextForResourcesEnsuringCorrectCachedApkPaths()に戻ると、そこにはcontext.createApplicationContext(mApplication, Context.CONTEXT_RESTRICTED)があります。CONTEXT_INCLUDE_CODEフラグは指定されておらず(そのメソッドに引数として渡されるコンテキストも同様)、そのためそのメソッドは常にmIncludeCode=falseのLoadedApkを使用します

したがって、RemoteViewsの更新バージョンにコードを含める必要はありません。RemoteViewsは常にコードなしのContextを使用するためです。コミットメッセージには、ApplicationInfoがキャッシュされる場所が2つあるとだけ記載されており、コード付きでバージョンを更新する処理を削除してもここでは問題は発生しないと思います(checkAndUpdateApkPaths()はAppWidgetHostViewとRemoteViewsでのみ使用されるため)。ただし、これに対する反論としては、さまざまなキャッシュが同期されなくなる可能性を回避できるという点があり、cacheWithCode=trueでの呼び出しを削除することでこのバグの最も深刻な影響は取り除かれますが、コードではなくリソースのみの変更が攻撃者にとって依然として価値がある可能性があるため、問題を完全に修正するわけではありません

LoadedApk.updateApplicationInfo()

これまでに示したコードはRemoteViews(ウィジェット、通知など)でのみ使用されますが、ここからは新しいスプリットがインストールされた後に実行中のアプリプロセスを更新するためにも使用されるLoadedApk.updateApplicationInfo()に入ります(たとえば、Play Feature Deliveryのオンデマンド配信を使用する場合など)

ここで、RemoteViewsからの信頼できないApplicationInfoオブジェクトがupdateApplicationInfo()に渡されます。そのメソッドが何を行うか見てみましょう (スニペットのソース)```java public void updateApplicationInfo(@NonNull ApplicationInfo aInfo, @Nullable List oldPaths) { if (!setApplicationInfo(aInfo)) { return; }

root@kitploit:~
`setApplicationInfo()` を見てみましょう([スニペットのソース](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/LoadedApk.java;l=392-422;drc=5916ee589c4880e2d8a1a9ad6dc852108e4c44c1))```java
private boolean setApplicationInfo(ApplicationInfo aInfo) {
    if (mApplicationInfo != null && mApplicationInfo.createTimestamp > aInfo.createTimestamp) {
        Slog.w(TAG, "New application info for package " + aInfo.packageName
                + " is out of date with TS " + aInfo.createTimestamp + " < the current TS "
                + mApplicationInfo.createTimestamp);
        return false;
    }
    // Snip: assign fields such as mAppDir and mResDir on this object from aInfo
    return true;
}

createTimestamp フィールドは通常 SystemClock.uptimeMillis() に設定されますが、このオブジェクトは攻撃者から渡されるものであるため、攻撃者が将来の値を提供して、それ以降の updateApplicationInfo() 呼び出しの実行を防ぐことができることを意味します。

updateApplicationInfo() に戻ると、(スニペットのソース)```java final List newPaths = new ArrayList<>(); makePaths(mActivityThread, aInfo, newPaths); final List addedPaths = new ArrayList<>(newPaths.size());

// Snip: populate addedPaths with items that are in newPaths and not in oldPaths (passed in argument) synchronized (mLock) { createOrUpdateClassLoaderLocked(addedPaths);

root@kitploit:~
`addedPaths` のリストは構築されます。新しいスプリットをインストールする場合、`oldPaths` には新しいスプリットのインストール前に使用されていたパスのリストが含まれ、`addedPaths` には既存の `ClassLoader` に追加する必要がある `.apk` ファイルのリストが含まれます。ただし、`checkAndUpdateApkPaths()` の場合、`oldPaths` はまったく同じ `ApplicationInfo` オブジェクトから構築されるため、`addedPaths` は空になり、攻撃者はここで既存の `ClassLoader` に新しいパスを追加することはできません。

`createOrUpdateClassLoaderLocked()` は長いメソッドですが、ここで興味深い点はほんのわずかです:

* [`mIncludeCode` が `false` の場合、`apk`/`dex` ファイルを参照せずに `ClassLoader` を作成するだけです](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/LoadedApk.java;l=968-990;drc=5916ee589c4880e2d8a1a9ad6dc852108e4c44c1)
* 最も危険なのは、攻撃者が制御する `ApplicationInfo` を使用して設定されたばかりのパスを使用して新しい `ClassLoader` を作成することですが、これは [`mDefaultClassLoader` が `null` の場合にのみ発生します](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/LoadedApk.java;l=1005;drc=5916ee589c4880e2d8a1a9ad6dc852108e4c44c1)。つまり、これはその `LoadedApk` インスタンスに対する最初の `createOrUpdateClassLoaderLocked()` であることを意味します(`mDefaultClassLoader` フィールドは、[`AppComponentFactory.instantiateClassLoader()`](https://developer.android.com/reference/android/app/AppComponentFactory#instantiateClassLoader(java.lang.ClassLoader,%20android.content.pm.ApplicationInfo)) を適用する前に使用されていた `ClassLoader` のインスタンスを指します)
* また、[その `ClassLoader` のネイティブライブラリ検索パスに新しいパスを追加する処理もあります](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/LoadedApk.java;l=1050-1058;drc=5916ee589c4880e2d8a1a9ad6dc852108e4c44c1) が、これを悪用するには被害者アプリが通常は存在しない `System.loadLibrary()` を実行する必要があります
* そして、[`addedPaths` 引数から既存の `ClassLoader` に `apk`/`dex` パスを追加する処理もあります](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/LoadedApk.java;l=1060-1065;drc=5916ee589c4880e2d8a1a9ad6dc852108e4c44c1) が、`RemoteViews` からのコードパスでは `addedPaths` は空になります

再び `updateApplicationInfo()` に戻ると、このメソッドが行う関連する処理がもう1つあります。それは、[新しい `mResDir` 値を使用するインスタンスで `LoadedApk.mResources` を置き換えることです](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/LoadedApk.java;l=382;drc=5916ee589c4880e2d8a1a9ad6dc852108e4c44c1)。これは新しいインスタンスであり、既に存在するインスタンスを更新するものではないことに注意してください。新しく作成された `Context` はこの新しい `Resources` インスタンスを返す/使用しますが、既に作成された `Context` は引き続き古い `Resources` を使用します。

# 影響のまとめ

上記のセクションを要約すると、被害者プロセスが `RemoteViews` を適用するたびに、この脆弱性により以下が可能になります:

* そのプロセス内で新しく作成された Activity の [`Resources`(ローカリゼーション文字列、レイアウトなど)](https://developer.android.com/guide/topics/resources/providing-resources) を置き換える
* `System.loadLibrary()` が使用するネイティブライブラリ検索パスに追加する。ただし、これを悪用するには被害者が通常は存在しないライブラリの名前を渡して `System.loadLibrary()` を呼び出す必要があり、これは考えにくい
* 被害者プロセスが `createPackageContext(CONTEXT_INCLUDE_CODE)` を使用したが、その `Context` で [`getClassLoader()`](https://developer.android.com/reference/android/content/Context#getClassLoader()) を呼び出していない場合、任意の Java コードをロードする。ただし、コードのロードは `CONTEXT_INCLUDE_CODE` フラグを使用する理由であるため、これも自然に発生する可能性は低い

さて、この場合に Java コードをロードする可能性は自然には発生しにくいですが、私はそれをトリガーすることができました。

# `WebView` のロード

最新の Android バージョンでは、`WebView` はシステムの一部ではなく、[システム構成](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/res/res/xml/config_webview_packages.xml) で定義された通常の `apk` からロードされ、[システムアプリであるか、システム内で定義された署名と一致する署名を持っています](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java;l=688-705;drc=f8f9e9ab11e65322aaaeb7373efd77cf1d671928)

その `apk` のロードは、[`Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY` フラグを渡して新しい `Context` を作成することで行われます](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/webkit/WebViewFactory.java;l=521;drc=f8f9e9ab11e65322aaaeb7373efd77cf1d671928)

次に、そのメソッドの呼び出し元を見てみましょう [(スニペットソース)](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/webkit/WebViewFactory.java;l=541-563;drc=f8f9e9ab11e65322aaaeb7373efd77cf1d671928)```java
// Overall snip: try/catch/finally, Trace, logging and timing measurement
webViewContext = getWebViewContextAndSetProvider();
if (android.content.res.Flags.registerResourcePaths()) {
    Resources.registerResourcePaths(webViewContext.getPackageName(),
            webViewContext.getApplicationInfo());
} else {
    // Snip: old resource update path, won't be used on latest Android builds
}
ClassLoader clazzLoader = webViewContext.getClassLoader();

getWebViewContextAndSetProvider()はContextを作成するメソッドであり、Resources.registerResourcePaths()は、別スレッドでcheckAndUpdateApkPaths()を呼び出して任意のJavaコードをロードするための遅延を導入できる唯一の機会です。webViewContext.getClassLoader()は、mIncludeCodeがtrueでmDefaultClassLoaderがnullのLoadedApkが存在するウィンドウの終端だからです。

Resources.registerResourcePaths()はappendLibAssetsLocked()に到達し、これはシングルトンのmResourceImplsフィールドを反復処理します。このフィールドには、このプロセスにロードされたすべてのリソースが含まれるため、RemoteViewsを通じて仕込まれたApplicationInfoを使って構築されたリソースも含まれる可能性があります。

私の当初のアイデアは、ApplicationInfoに、すべて同じhashCode()を持つ多数のオーバーレイパスを入れることでした。これにより、createNewResourceKeyIfNeeded()呼び出しによる重複排除が遅くなります。インタープリタを使用している場合(デバッガ使用時や新しくインストールしたアプリ内など)は、これは大きな遅延になりましたが、ランタイムが最適化を行うと、ハッシュ衝突による遅延はそれほど顕著ではありませんでした。しかし、別の遅延要因が現れました。これらのオーバーレイパスが既存のファイルを指していなかったため、ロードに失敗したオーバーレイごとにスタックトレース付きのログメッセージが出力され、実際に遅延が発生し、この競合状態の悪用が現実的になりました。

SystemUIへの侵入

アプリは通知内のRemoteViewsをSystemUIに渡すことができます。私の悪用アプリはPOST_NOTIFICATIONS権限を要求しますが、これは合理的なユーザー操作だと思います。ただし、一部の通知ではこの要件を回避できる可能性があります。

通常、SystemUIはWebViewを使用せず、実際には使用することもできません。なぜなら、SystemUIはデバイス保護ストレージを使用(つまり、ロック画面の認証情報で保護されていないストレージ)しており、WebViewの実装がそれを許可していないからです。

ただし、この悪用によりResourcesを変更できるため、まずSlicePermissionActivityで使用されるレイアウトを<WebView />要素を含むように変更し、そのActivityを起動して、SystemUIのメインスレッドでWebViewの初期化をトリガーしました。

次に、コードのロードに使用されるパスを置き換えるために、別のスレッドでRemoteViews.getContextForResourcesEnsuringCorrectCachedApkPaths()を呼び出させる必要があります。

通常、通知の投稿にはSystemUIプロセス内の複数のスレッドが関与します(メインスレッドも含まれるため、WebViewの初期化中は別の通知を投稿できません)。ただし、RemoteViewsの適用は別のスレッドで行われ、RemoteViews内にImageViewを配置して、自分のContentProviderから画像をロードするように要求することで、その操作を一時停止できます。

リソースのみの攻撃

この悪用は、SlicePermissionActivityで使用されるレイアウトを置き換えることでWebViewをロードします。コードの更新が不可能な場合でも、SlicePermissionActivityへの攻撃は依然として価値があります。攻撃者はレイアウトを置き換えて元のメッセージを完全に隠し、例えば変更ログを表示したり、許可ボタンを「了解」に置き換えたり、拒否ボタンを空文字列にして実質的に見えなくしたりできます。Slicesフレームワークは非推奨ですが、Settingsスライスはまだ存在し、例えばユーザーの操作なしにモバイルデータ設定を変更できます。

SystemUI内のもう1つの重要な権限プロンプトはMedia Projectionの確認ですが、これはDialogにアプリケーションコンテキストを使用しているため、脆弱ではないようです。

また、Preferenceフラグメントも興味深いです。Preferenceによって起動されるIntentを定義できるからです。ただし、SystemUIの場合、Preference ActivityはSystemUI TunerとDemo Modeに関連するものだけであり、これらは通知を表示するプロセスとは異なるプロセスで実行されます。

ツールをダウンロード