
# Vollständige Android-Exploit-Kette, die eine Privilegieneskalation von einer lokalen, nicht vertrauenswürdigen App zu Root/Kernel ermöglicht, kombiniert aus CVE-2026-49881 und CVE-2026-43284
Eine vollständige Exploit-Kette, die eine Privilegieneskalation von einer lokalen, nicht vertrauenswürdigen App bis zu Root/Kernel ermöglicht. Sie beinhaltet keine Speicherkorruptionen oder Race Conditions, sodass Angreifer kein komplexes Heap-Spraying durchführen oder Mitigationen gegen Speicherkorruptions-Schwachstellen wie KASLR, MTE oder CFI umgehen müssen, was diese Exploit-Kette auf verwundbaren Geräten zu einer 100%igen Erfolgsrate macht.
Getestet auf Pixel 10 mit dem offiziellen Android-17-Erstrelease. Beachten Sie, dass es auf Pixel 6a nicht funktioniert und dieses Problem auch auf anderen Geräten auftreten kann, die 6.1.xxx-android14-Kernel-Bäume verwenden, aufgrund eines weiteren Bugs in diesen Kerneln.
Verwendung: Installieren Sie die KernelSU-App, öffnen Sie diese App, klicken Sie auf „Run userspace exploit“ und dann auf „Run kernel exploit and load KernelSU“. Nach einer erfolgreichen Ausnutzung wird KernelSU aktiviert und Sie können es verwenden, um anderen Apps Root-Zugriff zu gewähren. Bekanntes Problem: Wenn Sie den Kernel-Exploit bereits ausgeführt haben und ihn erneut ausführen möchten, müssen Sie das Gerät neu starten.
Bildschirmaufzeichnung: hier klicken
Die Kette besteht aus zwei verschiedenen Schwachstellen: eine ist ein 0-Day im Telecom-Dienst, während die andere ein Kernel-1-Day ist, der vor 3 Monaten offengelegt wurde. Aber AOSP- und Pixel-Geräte (außer denen mit Beta-QPR-Versionen) bleiben zum Zeitpunkt des Schreibens verwundbar.
Die erste Schwachstelle der Kette ist ein einfacher Logikfehler, der in Android 17 eingeführt wurde. Er stammt aus einer verrückten Änderung, die den folgenden Code zu InCallController.java hinzufügt:
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;
}
}
}
Die relevante serviceClassExists()-Methode ist wie folgt definiert:
/**
* 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;
}
}
Dies ist die unglaublichste Schwachstelle, die ich je gesehen habe. Der Code verwendet Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY, um Code aus einer beliebigen App zu laden. Obwohl es einige Maßnahmen zu geben scheint, die beliebige Codeausführung verhindern sollen, wie z. B. die Übergabe von false an Class.forName(), um die Klasseninitialisierung zu verhindern, kann die App dennoch eine benutzerdefinierte AppComponentFactory deklarieren, die aufgerufen wird, wenn getClassLoader() aufgerufen wird.
Andererseits existiert der Bug in InCallController.java, das Teil des Pakets com.android.server.telecom und nicht von com.android.phone ist. Es ist erwähnenswert, dass das Paket android:sharedUserId="android.uid.system" und android:process="system" in AndroidManifest.xml deklariert, sodass es im system_server-Prozess läuft, einem der privilegiertesten Userspace-Prozesse in Android. Wir haben nun also die Fähigkeit, beliebigen Java-Code innerhalb von system_server auszuführen.
Es ist eine Überraschung, dass selbst ein Google-Ingenieur im KI-Zeitalter einen so großen Fehler machen kann. Wir haben ihn gefunden und am 23. Juli 2026 dem Android-Sicherheitsteam gemeldet. Sie sagten uns, es sei ein Duplikat. Google hat das monatliche Sicherheitsbulletin auf eine vierteljährliche Veröffentlichung umgestellt, was erklären könnte, warum die Schwachstelle 3 Monate nach der Veröffentlichung von Android 17 nicht behoben wurde.
Der Schwachstelle wurde CVE-2026-49881 zugewiesen und sie wurde im September 2026 durch Entfernen der serviceClassExists-Logik zur Behebung der Sicherheitslücke behoben.
Der erste Bug erlaubt uns eine Privilegieneskalation zu system, ist aber noch weit von Root entfernt. Ein vollständiges Root erfordert mindestens UID 0 und darf nicht durch SELinux eingeschränkt sein.
Jetzt ist es an der Zeit, den Kernel-1-Day vorzustellen: die DirtyFrag-Schwachstellen. Die zugrunde liegenden Prinzipien werden hier nicht näher erläutert; bitte beziehen Sie sich auf den Writeup des ursprünglichen Melders. Es gibt 2 Varianten: CVE-2026-43500 erfordert RxRPC, das für Android-Generic-Kernel deaktiviert ist; CVE-2026-43284 erfordert xfrm-ESP und ist auf Android ausnutzbar. Allerdings verbietet SELinux nicht vertrauenswürdigen Apps die Nutzung dieser Funktion:
# Privileged netlink socket interfaces.
neverallow { appdomain -network_stack }
domain:{
netlink_tcpdiag_socket
netlink_nflog_socket
netlink_xfrm_socket
netlink_audit_socket
netlink_dnrt_socket
} *;
Die einzigen erlaubten Domänen sind system_server, network_stack und netd. Um DirtyFrag auszunutzen, müssen Angreifer zuerst einen der auf der Whitelist stehenden privilegierten Prozesse kompromittieren.
Kombinieren Sie zwei Bugs miteinander. Während der Userspace-Bug uns erlaubt, Java-Code innerhalb von system_server auszuführen, verbietet SELinux system_server auch, native Bibliotheken aus /data zu laden oder anonymen ausführbaren Speicher zu mappen. Dies macht es unmöglich, nativen Code zu verwenden, was die Schwierigkeit der Ausnutzung erhöht. Es wäre daher vorzuziehen, Code innerhalb von com.android.networkstack auszuführen, das nativen Code aus unserer APK laden kann und ausreichende Privilegien hat, um DirtyFrag auszunutzen.
Glücklicherweise ist system_server der Prozess, in dem ActivityManager läuft. ActivityManager speichert IApplicationThread-Handles jedes App-Prozesses in einer Java-Map, und da wir als derselbe Prozess wie ActivityManager laufen, können wir sie mithilfe von Java-Reflection abrufen. Damit können wir beliebige Befehle an com.android.networkstack senden, um es zu zwingen, unseren Code zu laden. Weitere Informationen zu diesem Trick finden Sie in meinem vorherigen Exploit für CVE-2026-0091.
DirtyFrag erlaubt das Überschreiben von schreibgeschützten Dateien. Dies ist ein mächtiges Primitive in der Linux-Welt, da wir die su-Binärdatei überschreiben können, die das SUID-Bit hat. Allerdings haben wir in der Android-Welt kein su. Wir beziehen uns auf polygraphenes Exploit für DirtyPipe, um DirtyFrag in eine Kernel-Codeausführung auf Android umzuwandeln:
libc.so, libc++.so und /vendor/lib64/libstagefright_aidl_bufferpool2.so durch DirtyFrag. libstagefright_aidl_bufferpool2.so ist als vendor_file-Domäne gekennzeichnet, sodass es nicht vom Netzwerk-Stack-Prozess aus aufgerufen werden kann. Die Lösung besteht darin, zuerst /apex/com.android.runtime/bin/crash_dump64 zu patchen, es auszuführen, und sobald wir zur crash_dump-Domäne gewechselt sind, können wir libstagefright_aidl_bufferpool2.so öffnen.init-Prozess auszulösen. Da libc++.so gepatcht ist, wird unser Code als UID 0 mit der init-Domäne ausgeführt. Wir führen dann /vendor/bin/modprobe aus, um zur -Domäne zu wechseln.vendor_modprobemodprobe ausgeführt wird, wird unser Code, da auch libc.so gepatcht ist, unter der vendor_modprobe-Domäne ausgeführt. Wir können nun Kernel-Module laden, aber nur für Dateien, die bestimmte Labels haben. Wir laden libstagefright_aidl_bufferpool2.so, das das vendor_file-Label hat.libstagefright_aidl_bufferpool2.so gepatcht haben, wurde der eigentliche Inhalt dieser Datei durch unser eigenes Kernel-Modul ersetzt. Das Kernel-Modul wird geladen, und jetzt können wir alles tun, einschließlich der Anpassung der SELinux-Richtlinie oder dem Setzen von SELinux auf permissive.