
Androidの完全なエクスプロイトチェーン。CVE-2026-49881とCVE-2026-43284を組み合わせることで、ローカルの信頼されていないアプリからroot/カーネルへの権限昇格を可能にします。
ローカルの信頼されていないアプリからroot/カーネルへの権限昇格を可能にする完全なエクスプロイトチェーンです。メモリ破壊や競合状態を伴わないため、攻撃者は複雑なヒープスプレーやKASLR、MTE、CFIなどのメモリ破壊脆弱性に対する緩和策の回避を行う必要がなく、このエクスプロイトチェーンは脆弱なデバイスに対して100%の成功率を誇ります。
初期のAndroid 17公式リリースを実行しているPixel 10でテスト済みです。Pixel 6aでは動作せず、この問題は6.1.xxx-android14カーネルツリーを実行している他のデバイスでも、これらのカーネルの別のバグが原因で発生する可能性があることに注意してください。
使用方法: KernelSUアプリをインストールし、このアプリを開いて「Run userspace exploit」をクリックし、次に「Run kernel exploit and load KernelSU」をクリックします。エクスプロイトが成功すると、KernelSUが有効化され、他のアプリにrootアクセスを許可するために使用できます。既知の問題: カーネルエクスプロイトを既に実行していて、再度実行したい場合は、デバイスを再起動する必要があります。
画面録画: ここをクリック
このチェーンは2つの異なる脆弱性から構成されています。1つはTelecomサービスにおける0-day、もう1つは3ヶ月前に公開されたカーネルの1-dayです。しかし、AOSPおよびPixelデバイス(ベータ版QPRバージョンを実行しているものを除く)は、執筆時点でも脆弱なままです。
このチェーンの最初の脆弱性は、Android 17で導入された単純なロジックバグです。突飛な変更に由来し、InCallController.javaに以下のコードを追加します:
PackageManager packageManager = mContext.getPackageManager();
Context userContext = mContext.createContextAsUser(userHandle,
0 /* flags */);
PackageManager userPackageManager = userContext != null ?
userContext.getPackageManager() : packageManager;
List<ResolveInfo> entries;
entries = userPackageManager.queryIntentServices(
serviceIntent,
PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_COMPONENTS);
for (ResolveInfo entry : entries) {
ServiceInfo serviceInfo = entry.serviceInfo;
if (serviceInfo != null) {
boolean isMetaFlag = serviceInfo.metaData != null &&
serviceInfo.metaData.getBoolean(
"android.telecom.CLASS_EXISTENCE_CHECK", false);
if (isMetaFlag && !serviceClassExists(serviceInfo, userHandle)) {
continue;
}
}
}
関連するserviceClassExists()メソッドは以下のように定義されています:
/**
* Verifies that the class for a given ServiceInfo exists within its package.
* This prevents a system crash if a service is declared in the manifest but its
* class was not included in the compiled code.
* @param serviceInfo The ServiceInfo of the service to check.
* @param userHandle The user under which to check for the service.
* @return {@code true} if the class exists, {@code false} otherwise.
*/
private boolean serviceClassExists(ServiceInfo serviceInfo, UserHandle userHandle) {
Log.i(this, "serviceClassExists check");
try {
Context packageContext = mContext.createPackageContextAsUser(
serviceInfo.packageName,
Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY, userHandle);
ClassLoader classLoader = packageContext.getClassLoader();
Class.forName(serviceInfo.name, false, classLoader);
return true;
} catch (NameNotFoundException | ClassNotFoundException e) {
Log.w(this, "Skipping InCallService: class not found for " + serviceInfo.name);
return false;
} catch (Exception e) {
Log.e(this, e, "Error checking for existence of " + serviceInfo.name);
return false;
}
}
これは私がこれまで見た中で最も信じがたい脆弱性です。このコードはContext.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITYを使用して任意のアプリからコードを読み込みます。クラスの初期化を防ぐためにClass.forName()にfalseを渡すなど、任意のコード実行を防ぐためのいくつかの対策があるように見えますが、アプリはgetClassLoader()が呼び出されたときに呼び出されるカスタムAppComponentFactoryを宣言することができます。
一方、このバグはcom.android.phoneではなくcom.android.server.telecomパッケージの一部であるInCallController.javaに存在します。このパッケージはAndroidManifest.xmlでandroid:sharedUserId="android.uid.system"とandroid:process="system"を宣言しているため、Androidで最も特権的なユーザースペースプロセスの1つであるsystem_serverプロセスで実行されることは注目に値します。したがって、私たちはsystem_server内で任意のJavaコードを実行する能力を得ました。
AI時代にGoogleのエンジニアでさえこのような大きなミスを犯すことができるのは驚きです。私たちは2026年7月23日にこの問題を発見し、Androidセキュリティチームに報告しました。彼らは重複していると伝えました。Googleは月次セキュリティ速報を四半期リリースに切り替えており、これがAndroid 17のリリースから3ヶ月経っても脆弱性が修正されなかった理由を説明しているかもしれません。
この脆弱性にはCVE-2026-49881が割り当てられ、2026年9月にセキュリティ脆弱性に対処するためのserviceClassExistsロジックの削除によって修正されました。
最初のバグによりsystemへの権限昇格が可能になりますが、rootにはまだ遠いです。完全なrootには少なくともUID 0とSELinuxによる制限を受けないことが必要です。
ここでカーネルの1-dayを紹介します: DirtyFrag脆弱性です。基本原理についてはここでは詳しく説明しません。元の報告者のライトアップを参照してください。2つの亜種があります: CVE-2026-43500はAndroid Generic Kernelsでは無効化されているRxRPCを必要とし、CVE-2026-43284はxfrm-ESPを必要とし、Androidで悪用可能です。しかし、SELinuxは信頼されていないアプリがその機能を使用することを禁止しています:
# Privileged netlink socket interfaces.
neverallow { appdomain -network_stack }
domain:{
netlink_tcpdiag_socket
netlink_nflog_socket
netlink_xfrm_socket
netlink_audit_socket
netlink_dnrt_socket
} *;
許可されているドメインはsystem_server、network_stack、netdのみです。DirtyFragを悪用するには、攻撃者はまずホワイトリストに登録された特権プロセスの1つを侵害する必要があります。
2つのバグを組み合わせます。ユーザースペースのバグによりsystem_server内でJavaコードを実行できますが、SELinuxはsystem_serverが/dataからネイティブライブラリを読み込むことや匿名実行可能メモリをマッピングすることも禁止しています。これによりネイティブコードの使用が不可能になり、悪用の難易度が上がります。したがって、APKからネイティブコードを読み込むことができ、DirtyFragを悪用するのに十分な特権を持つcom.android.networkstack内でコードを実行することが望ましいでしょう。
幸いなことに、system_serverはActivityManagerが実行されるプロセスです。ActivityManagerはすべてのアプリプロセスのIApplicationThreadハンドルをJavaマップに格納しており、私たちはActivityManagerと同じプロセスで実行されるため、Javaリフレクションを使用してそれらを取得できます。これにより、com.android.networkstackに任意のコマンドを送信して、私たちのコードを強制的に読み込ませることができます。このトリックの詳細については、CVE-2026-0091に対する以前のエクスプロイトを参照してください。
DirtyFragを使用すると、読み取り専用ファイルを上書きできます。これはLinuxの世界では強力なプリミティブです。SUIDビットを持つsuバイナリを上書きできるからです。しかし、Androidの世界にはsuはありません。polygrapheneのDirtyPipe用エクスプロイトを参照して、DirtyFragをAndroidでのカーネルコード実行に変換します:
libc.so、libc++.so、/vendor/lib64/libstagefright_aidl_bufferpool2.soをパッチします。libstagefright_aidl_bufferpool2.soはvendor_fileドメインとしてラベル付けされているため、ネットワークスタックプロセスからアクセスできません。解決策は、まず/apex/com.android.runtime/bin/crash_dump64をパッチして実行し、crash_dumpドメインに遷移したらlibstagefright_aidl_bufferpool2.soを開くことができるようにすることです。initプロセスでのコード実行をトリガーします。libc++.soがパッチされているため、UID 0でinitドメインとしてコードが実行されます。次に/vendor/bin/modprobeを実行してvendor_modprobeドメインに遷移します。modprobeが実行されると、libc.soもパッチされているため、vendor_modprobeドメインでコードが実行されます。指定されたラベルを持つファイルに対してのみカーネルモジュールを読み込むことができます。vendor_fileラベルを持つlibstagefright_aidl_bufferpool2.soを読み込みます。libstagefright_aidl_bufferpool2.soをパッチしたため、そのファイルの実際の内容は独自のカーネルモジュールに置き換えられています。カーネルモジュールが読み込まれ、SELinuxポリシーの調整やSELinuxのpermissiveへの設定など、何でもできるようになります。