Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
TLPE — CVE-2026-49881, un problème de logique dans la classe InCallController du service Telecom d'Android 17 qui permet à une application non privilégiée d'obtenir une exécution de code arbitraire en tant que UID 1000 system_server | Kitploit
Outils/GitHubGitHub/supersonic/tlpe
Sécurité AndroidEscalade de PrivilègesMécanismes de PersistanceAnalyse des VulnérabilitésExploitationSécurité MobileExploitation de Binaires
GitHubsupersonic/tlpe

TLPE

CVE-2026-49881, un problème de logique dans la classe InCallController du service Telecom d'Android 17 qui permet à une application non privilégiée d'obtenir une exécution de code arbitraire en tant que UID 1000 system_server

Voir le dépôt
111il y a 11h 48mPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

Ceci est un PoC et un writeup pour CVE-2026-49881, un problème logique dans la classe InCallController du service Telecom d'Android 17 qui permet à une application non privilégiée d'obtenir une exécution de code arbitraire en tant que UID 1000 system_server sans aucune interaction supplémentaire de l'utilisateur. Nous montrons également ici que l'exécution de code dans system_server peut encore être facilement adaptée pour obtenir une persistance, même sur les versions modernes d'Android.

J'ai signalé cette vulnérabilité à l'équipe de sécurité Android le 2026-04-10, elle a été confirmée le 2026-05-06, et elle a été corrigée dans le bulletin de sécurité Android de septembre 2026. (voir ici pour le patch)

Au moment du signalement, elle n'affectait activement que les builds Pixel à partir d'Android 16 QPR3 (ainsi que les builds bêta 17) à ma connaissance, mais elle s'est ensuite propagée à la version stable AOSP d'Android 17.

Pour vous protéger de ce problème, assurez-vous d'installer la mise à jour du système Google Play ainsi que le correctif de sécurité système. (Telecom est un composant mainline depuis Android 17)

TLPE

Notes sur le PoC

  • Le PoC démontre l'obtention d'une exécution de code dans system_server en utilisant la vulnérabilité, journalise id et la trace de pile dans logcat, et se réinstalle en tant que composant system_server.
  • Une fois le PoC installé, appuyer sur le bouton Start Exploit ou passer un appel via la pile telecom déclenchera l'exploit.
  • Compilez le PoC en exécutant le build.sh inclus. Sinon, vous pouvez exécuter manuellement ./gradlew assembleSystemRelease, déplacer le app-system-release.apk résultant vers app/src/poc/assets/system.apk, puis exécuter ./gradlew assemblePocRelease.
  • Le PoC enregistrera son propre certificat comme certificat ancêtre pour l'UID 1000 après une exploitation réussie. Cet état persiste à travers les OTA, y compris le correctif de la vulnérabilité elle-même. Pour nettoyer votre appareil après une exécution du PoC, vous devez appuyer sur le bouton « Uninstall » dans le PoC (qui nettoie le certificat injecté) — néanmoins, je recommande fortement d'utiliser votre propre keystore de release pour signer un APK PoC compilé pendant les tests. (plutôt que celui que le PoC utilise par défaut à TLPE/app/teststore.jks)
  • Notez que le PoC après être entré dans system_server désactive également Play Protect en définissant package_verifier_user_consent à -1 dans Settings.Global car il peut parfois intercepter la transaction de réinstallation en raison de signatures inconnues. Vous devez le réactiver dans les Paramètres après les tests.
  • Il a été validé sur Pixel Android et AOSP, mais l'étape d'exécution de code devrait fonctionner sur les versions Android 17 personnalisées par les OEM. L'étape de réinstallation en tant que system_server pourrait nécessiter une personnalisation par OEM car elle repose sur la traversée de la structure PMS en utilisant des symboles que les OEM modifient parfois.

La sortie logcat attendue du PoC est :

root@kitploit:~
04-15 03:02:47.911  1558 12775 E TLPE    : ===================================
04-15 03:02:47.911  1558 12775 E TLPE    : [+] Exploit successful!
04-15 03:02:47.911  1558 12775 E TLPE    : [+] Running as: [uid=1000(system) gid=1000(system) groups=1000(system),1001(radio),1002(bluetooth),1003(graphics),1004(input),1005(audio),1006(camera),1007(log),1008(compass),1009(mount),1010(wifi),1018(usb),1021(gps),1023(media_rw),1024(mtp),1032(package_info),1065(reserved_disk),3001(net_bt_admin),3002(net_bt),3003(inet),3005(net_admin),3006(net_bw_stats),3007(net_bw_acct),3009(readproc),3010(wakelock),3011(uhid),3012(readtracefs) context=u:r:system_server:s0]
04-15 03:02:47.911  1558 12775 E TLPE    : [+] Current stack trace:
04-15 03:02:47.911  1558 12775 E TLPE    : [dalvik.system.VMStack.getThreadStackTrace(Native Method), java.lang.Thread.getStackTrace(Thread.java:2842), poc.sithi.tlpe.EvilFactory.instantiateClassLoader(EvilFactory.kt:31), android.app.LoadedApk.createOrUpdateClassLoaderLocked(LoadedApk.java:1215), android.app.LoadedApk.getClassLoader(LoadedApk.java:1267), android.app.ContextImpl.getClassLoader(ContextImpl.java:542), com.android.server.telecom.InCallController.serviceClassExists(InCallController.java:2561), com.android.server.telecom.InCallController.getInCallServiceComponents(InCallController.java:2606), com.android.server.telecom.InCallController.getInCallServiceComponents(InCallController.java:2515), com.android.server.telecom.InCallController.getInCallServiceComponents(InCallController.java:2499), com.android.server.telecom.InCallController.bindToBTService(InCallController.java:2247), com.android.server.telecom.InCallController.onCallAdded(InCallController.java:1437), com.android.server.telecom.CallsManager.addCall(CallsManager.java:5457), com.android.server.telecom.CallsManager.processIncomingCallIntent(CallsManager.java:1970), com.android.server.telecom.callsequencing.voip.IncomingCallTransaction.processTransaction(IncomingCallTransaction.java:76), com.android.server.telecom.callsequencing.CallTransaction$$ExternalSyntheticLambda3.apply(R8$$SyntheticClass:0), java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1126), java.util.concurrent.CompletableFuture$Completion.run(CompletableFuture.java:458), com.android.server.telecom.LoggedHandlerExecutor$1.loggedRun(LoggedHandlerExecutor.java:41), android.telecom.Logging.Runnable$1.run(Runnable.java:37), android.os.Handler.handleCallback(Handler.java:1095), android.os.Handler.dispatchMessageImpl(Handler.java:135), android.os.Handler.dispatchMessage(Handler.java:125), android.os.Looper.loopOnce(Looper.java:269), android.os.Looper.loop(Looper.java:367), android.os.HandlerThread.run(HandlerThread.java:139)]
04-15 03:02:47.911  1558 12775 E TLPE    : ===================================
04-15 03:02:47.934  1558 12785 E TLPE    : [+] Retrieved system APK, attempting persistence...
04-15 03:02:47.935  1558 12785 E TLPE    : [+] Injection successful, forcing packages.xml flush
04-15 03:02:47.957  1558 12785 E TLPE    : [+] Persistence successful, reinstalling...

Writeup

C'est une vulnérabilité inhabituellement directe. Chaque fois que certaines actions liées à Telecom se produisent, InCallController tente de découvrir les services disponibles via getInCallServiceComponents. Cela se déclenche naturellement lorsqu'un appel est enregistré auprès du système, mais une application peut en réalité le déclencher à la demande également grâce à l'API d'appels transactionnels TelecomManager.addCall. (note : c'est ce que le bouton Start Exploit du PoC utilise) Cette API nécessite MANAGE_OWN_CALLS, mais c'est une permission normale et invisible pour l'utilisateur, accordée automatiquement lors de l'installation.

Cette énumération est implémentée sur les versions vulnérables comme :

root@kitploit:~
private List<InCallServiceInfo> getInCallServiceComponents(UserHandle userHandle,
        String packageName, ComponentName componentName,
        int requestedType, boolean ignoreDisabled) {
        ...
        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;
                }
                ...
            }
        }
}

Notez comment serviceClassExists s'exécute contre n'importe quel composant qui déclare une intention InCallService avec la valeur de métadonnées android.telecom.CLASS_EXISTENCE_CHECK, pas seulement les InCallService valides/activés. (cette vérification est implémentée plus loin dans la branche avec getInCallServiceType et isServiceEnabled)

serviceClassExists est implémenté comme :

root@kitploit:~
/**
 * 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;
    }
}

Les dangers de createPackageContext avec CONTEXT_IGNORE_SECURITY sont bien documentés, et dans ce cas c'est system_server lui-même qui l'exécute contre un composant non fiable juste pour vérifier si une classe existe dans le DEX de cette application. À première vue, cependant, cela peut sembler sûr car le contexte obtenu est utilisé dans Class.forName avec initialize=false — le développeur était très probablement conscient de ce risque et a spécifié cet argument pour garantir que la classe étrangère n'est pas initialisée dans system_server.

Malheureusement, cette précaution arrive trop tard — le mal est déjà fait par getClassLoader sur le contexte étranger. Si l'application attaquante définit un AppComponentFactory dans son manifeste comme android:appComponentFactory, la méthode getClassLoader sur un LoadedApk correspondant à un contexte créé avec CONTEXT_INCLUDE_CODE et CONTEXT_IGNORE_SECURITY récupère d'abord le chargeur de classe par défaut de l'application étrangère depuis le disque puis exécute à la fois le constructeur de la factory et sa méthode instantiateClassLoader avant de retourner le chargeur de classe.

Comme cette classe est contrôlable par l'application attaquante, cela mène immédiatement à une exécution de code arbitraire dans le contexte de Telecom.

Post-exploitation

Pour montrer les conséquences d'une exécution de code réussie dans system_server, le PoC démontre la réinstallation de lui-même en tant que composant system_server persistant. (la technique est adaptée de la technique publiée dans le PoC AbxOverflow / CVE-2024-34740 par Michał Bednarski)

Nous faisons cela en :

  • Récupérant l'objet Signature de notre application PoC en réfléchissant dynamiquement dans PackageManagerService.mSettings directement une fois que nous sommes dans system_server.
  • Récupérant le SharedUserSetting correspondant à "android.uid.system" et injectant la Signature du PoC deux fois dans son getSigningDetails().mPastSigningCertificates avec CertCapabilities.SHARED_USER_ID.
  • Désinstallant de force l'application PoC et réinstallant une variante de celle-ci avec android:sharedUserId="android.uid.system" et android:process="system" ajoutés au manifeste. (ce qui vide également la modification volatile de l'étape précédente vers packages.xml)

Cela fait passer canJoinSharedUserId() dans PackageSignatures grâce à la correspondance de l'historique de rotation, et donne des privilèges system_server persistants.

Note de conclusion

Une question que vous pourriez vous poser est pourquoi cette vulnérabilité existe du tout — en particulier, pourquoi y a-t-il une vérification d'existence de classe opt-in protégée derrière un indicateur de métadonnées non documenté ?

Il est impossible de le dire avec certitude, mais la réponse probable à ce mystère peut être trouvée en creusant un peu plus dans AOSP : la vérification de classe et la comparaison de métadonnées ont probablement été ajoutées pour tenir compte de android.net.ConnectivityCallListenerService, qui était en cours d'implémentation à peu près à la même époque.

Ce service était initialement défini dans le manifeste du framework mais n'était implémenté nulle part. (et quand il a été ajouté, c'était derrière l'indicateur de fonctionnalité Flags.FLAG_ENABLE_INCALL_SERVICE_API) Une fois les crashs observés lors des tests, quelqu'un a probablement décidé d'implémenter le correctif comme une vérification réutilisable de « défense en profondeur » plutôt qu'une exception codée en dur — la définition du manifeste de ce service indique qu'il définit "android.telecom.CLASS_EXISTENCE_CHECK" pour « indiquer que la classe de ce service peut ne pas être présente dans toutes les builds » et « demander à Telecom de vérifier l'existence de la classe avant de tenter de se lier ». (et c'est pourquoi nous en sommes là maintenant)

Télécharger l’outil