Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
TLPE — CVE-2026-49881, un problema logico nella classe InCallController del servizio Telecom di Android 17 che consente a un'app senza privilegi di ottenere l'esecuzione arbitraria di codice come UID 1000 system_server. | Kitploit
Strumenti/GitHubGitHub/supersonic/tlpe
Sicurezza AndroidEscalation di PrivilegiMeccanismi di PersistenzaAnalisi delle VulnerabilitàExploitSicurezza MobileBinary Exploitation
GitHubsupersonic/tlpe

TLPE

CVE-2026-49881, un problema logico nella classe InCallController del servizio Telecom di Android 17 che consente a un'app senza privilegi di ottenere l'esecuzione arbitraria di codice come UID 1000 system_server.

Vedi Repository
11111h 46m faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

Questo è un PoC e un writeup per CVE-2026-49881, un problema logico nella classe InCallController del servizio Telecom di Android 17 che consente a un'app senza privilegi di ottenere l'esecuzione arbitraria di codice come UID 1000 system_server senza alcuna interazione aggiuntiva da parte dell'utente. Mostriamo anche qui che l'esecuzione di codice in system_server può essere ancora facilmente adattata per ottenere la persistenza, anche nelle versioni moderne di Android.

Ho segnalato questa vulnerabilità all'Android Security Team il 2026-04-10, è stata confermata il 2026-05-06 ed è stata corretta nel bollettino sulla sicurezza Android di settembre 2026. (vedi qui per la patch)

Al momento della segnalazione, per quanto ne so, colpiva attivamente solo le build Pixel successive e successive ad Android 16 QPR3 (insieme alle build Beta 17), ma in seguito è arrivata alla release stabile AOSP di Android 17.

Per proteggerti da questo problema, assicurati di installare l'aggiornamento di sistema Google Play e la patch di sicurezza di sistema. (Telecom è un componente mainline da Android 17)

TLPE

Note sul PoC

  • Il PoC dimostra l'ottenimento dell'esecuzione di codice in system_server utilizzando la vulnerabilità, registra id e stack trace su logcat e si re-installa come componente di system_server.
  • Una volta installato il PoC, toccando il pulsante Start Exploit o effettuando una chiamata tramite lo stack telecom, questo verrà attivato.
  • Compila il PoC eseguendo il build.sh incluso. In alternativa, puoi eseguire manualmente ./gradlew assembleSystemRelease, spostare l'app-system-release.apk risultante in app/src/poc/assets/system.apk, quindi eseguire ./gradlew assemblePocRelease.
  • Il PoC registrerà il proprio certificato come certificato antenato per l'UID 1000 dopo lo sfruttamento riuscito. Questo stato persiste attraverso gli OTA inclusa la patch della vulnerabilità stessa. Per pulire il dispositivo dopo un'esecuzione del PoC, dovresti premere il pulsante "Uninstall" nel PoC (che pulisce il certificato iniettato) - ciononostante, consiglio vivamente di utilizzare il proprio keystore di release per firmare un APK PoC compilato durante i test. (piuttosto che quello che il PoC usa di default in TLPE/app/teststore.jks)
  • Nota che il PoC dopo essere entrato in system_server disattiva anche forzatamente Play Protect impostando package_verifier_user_consent su -1 in Settings.Global perché a volte può intercettare la transazione di re-installazione a causa di firme sconosciute. Dovresti riattivarlo in Impostazioni dopo il test.
  • È stato validato su Pixel Android e AOSP, ma la fase di esecuzione del codice dovrebbe funzionare su versioni Android 17 personalizzate dagli OEM. La fase di re-installazione come system_server potrebbe richiedere una personalizzazione per ogni OEM perché si basa sull'attraversamento della struttura PMS utilizzando simboli che gli OEM a volte cambiano.

L'output logcat previsto del PoC è:

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

Questa è una vulnerabilità insolitamente diretta. Ogni volta che si verificano determinate azioni relative a Telecom, InCallController tenta di scoprire i servizi disponibili tramite getInCallServiceComponents. Questo si attiva naturalmente quando una chiamata viene registrata con il sistema, ma un'app può effettivamente attivarlo anche su richiesta grazie all'API di chiamate transazionali TelecomManager.addCall. (nota: questo è ciò che usa il pulsante Start Exploit nel PoC) Questa API richiede MANAGE_OWN_CALLS, ma è un'autorizzazione normale e invisibile all'utente concessa automaticamente al momento dell'installazione.

Questa enumerazione è implementata nelle versioni vulnerabili in questo modo:

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;
                }
                ...
            }
        }
}

Nota come serviceClassExists venga eseguito su qualsiasi componente che dichiari un intent InCallService insieme al valore dei metadati android.telecom.CLASS_EXISTENCE_CHECK, non solo su InCallService validi/abilitati. (quel controllo è implementato più avanti nel ramo con getInCallServiceType e isServiceEnabled)

serviceClassExists è implementato come:

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;
    }
}

I pericoli di createPackageContext con CONTEXT_IGNORE_SECURITY sono ben documentati, e in questo caso è system_server stesso a eseguirlo su un componente non attendibile solo per verificare se una classe esiste nel DEX di quell'app. A prima vista, tuttavia, questo può sembrare sicuro perché il contesto ottenuto viene utilizzato in Class.forName con initialize=false - lo sviluppatore era molto probabilmente consapevole di questo rischio e ha specificato quell'argomento per garantire che la classe esterna non venga inizializzata in system_server.

Sfortunatamente, questa cautela arriva troppo tardi - il danno è già fatto da getClassLoader sul contesto esterno. Se l'app dell'attaccante definisce un AppComponentFactory nel suo manifest come android:appComponentFactory, il metodo getClassLoader su un LoadedApk corrispondente a un contesto creato con CONTEXT_INCLUDE_CODE e CONTEXT_IGNORE_SECURITY recupera prima il class loader predefinito dell'app esterna dal disco e poi esegue sia il costruttore della factory che il suo metodo instantiateClassLoader prima di restituire il class loader.

Poiché questa classe è controllabile dall'app dell'attaccante, questo porta immediatamente all'esecuzione arbitraria di codice nel contesto di Telecom.

Post sfruttamento

Per mostrare le conseguenze dell'esecuzione riuscita di codice in system_server, il PoC dimostra la re-installazione di se stesso come componente persistente di system_server. (la tecnica è adattata dalla tecnica pubblicata nel PoC AbxOverflow / CVE-2024-34740 di Michał Bednarski)

Lo facciamo:

  • Recuperando l'oggetto Signature della nostra app PoC riflettendo dinamicamente in PackageManagerService.mSettings direttamente una volta che siamo in system_server.
  • Recuperando il SharedUserSetting corrispondente a "android.uid.system" e iniettando la Signature del PoC due volte nel suo getSigningDetails().mPastSigningCertificates con CertCapabilities.SHARED_USER_ID.
  • Forzando la disinstallazione dell'app PoC e re-installando una sua variante con android:sharedUserId="android.uid.system" e android:process="system" aggiunti al manifest. (che scarica anche la modifica volatile del passaggio precedente su packages.xml)

Questo fa sì che canJoinSharedUserId() in PackageSignatures venga superato grazie alla corrispondenza della cronologia di rotazione e produce privilegi persistenti di system_server.

Nota conclusiva

Una domanda che potresti avere è perché questa vulnerabilità esista affatto - in particolare, perché c'è un controllo di esistenza della classe opt-in protetto da un flag di metadati non documentato?

È impossibile dirlo con certezza, ma la risposta probabile a questo mistero può essere trovata scavando un po' più a fondo in AOSP: sia il controllo della classe che il confronto dei metadati sono stati probabilmente aggiunti per tenere conto di android.net.ConnectivityCallListenerService, che veniva implementato più o meno nello stesso periodo.

Questo servizio inizialmente era definito nel manifest del framework ma non era implementato da nessuna parte. (e quando è stato aggiunto, era dietro il flag di funzionalità Flags.FLAG_ENABLE_INCALL_SERVICE_API) Una volta osservati i crash nei test, qualcuno ha probabilmente deciso di implementare la correzione come controllo riutilizzabile di "difesa in profondità" piuttosto che come eccezione hardcoded - la definizione del manifest di questo servizio dice che definisce "android.telecom.CLASS_EXISTENCE_CHECK" per "indicare che la classe di questo servizio potrebbe non essere presente in tutte le build" e "dire a Telecom di verificare l'esistenza della classe prima di tentare il bind". (ed è per questo che siamo qui ora)

Scarica lo strumento