
Android complete exploit chain that enables privilege escalation from a local untrusted app to root/kernel, combination of CVE-2026-49881 and CVE-2026-43284
Полная цепочка эксплуатации, которая позволяет повысить привилегии от локального ненадёжного приложения до root/ядра. Она не включает повреждения памяти или гонки данных, поэтому атакующим не нужно выполнять сложный heap spraying или обходить механизмы защиты от уязвимостей повреждения памяти, такие как KASLR, MTE или CFI, что делает эту цепочку эксплуатации успешной на 100% на уязвимых устройствах.
Протестировано на Pixel 10 с первоначальным официальным релизом Android 17. Обратите внимание, что она не работает на Pixel 6a, и эта проблема также может возникать на других устройствах с деревьями ядра 6.1.xxx-android14 из-за другой ошибки в этих ядрах.
Использование: установите приложение KernelSU, откройте это приложение, нажмите «Run userspace exploit», затем «Run kernel exploit and load KernelSU». После успешной эксплуатации KernelSU будет активирован, и вы сможете использовать его для предоставления root-доступа другим приложениям. Известная проблема: если вы уже запускали эксплойт ядра и хотите запустить его снова, необходимо перезагрузить устройство.
Видеозапись экрана: нажмите здесь
Цепочка состоит из двух различных уязвимостей: одна — 0-day в сервисе Telecom, а другая — 1-day в ядре, раскрытая 3 месяца назад. Но 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 для загрузки кода из произвольного приложения; хотя, похоже, существуют некоторые меры, предназначенные для предотвращения произвольного выполнения кода, такие как передача false в Class.forName() для предотвращения инициализации класса, приложение всё равно может объявить собственный AppComponentFactory, который вызывается при обращении к getClassLoader().
С другой стороны, ошибка существует в InCallController.java, который является частью пакета com.android.server.telecom, а не com.android.phone. Стоит отметить, что пакет объявляет android:sharedUserId="android.uid.system" и android:process="system" в AndroidManifest.xml, поэтому он работает в процессе system_server, одном из наиболее привилегированных пользовательских процессов в Android. Таким образом, теперь у нас есть возможность выполнять произвольный Java-код внутри system_server.
Удивительно, что даже инженер Google может совершить такую большую ошибку в эпоху ИИ. Мы обнаружили её и сообщили в команду безопасности Android 23 июля 2026 года. Они сказали нам, что это дубликат. Google перешёл с ежемесячного на ежеквартальный выпуск бюллетеня безопасности, что может объяснить, почему уязвимость не была исправлена через 3 месяца после выпуска Android 17.
Уязвимости присвоен номер CVE-2026-49881, и она исправлена в сентябре 2026 года с помощью Remove serviceClassExists logic to address security vulnerability.
Первая ошибка позволяет нам повысить привилегии до system, но это всё ещё далеко от root. Полный root требует как минимум UID 0 и отсутствия ограничений со стороны SELinux.