Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
TLPE — CVE-2026-49881, um problema de lógica na classe InCallController do serviço Telecom do Android 17 que permite que um aplicativo sem privilégios obtenha execução arbitrária de código como UID 1000 system_server | Kitploit
Ferramentas/GitHubGitHub/supersonic/tlpe
Segurança AndroidEscalada de PrivilégiosMecanismos de PersistênciaAnálise de VulnerabilidadesExploraçãoSegurança MóvelExploração de Binários
GitHubsupersonic/tlpe

TLPE

CVE-2026-49881, um problema de lógica na classe InCallController do serviço Telecom do Android 17 que permite que um aplicativo sem privilégios obtenha execução arbitrária de código como UID 1000 system_server

Ver Repositório
111há 9h 1mAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

Este é um PoC e um writeup para o CVE-2026-49881, um problema de lógica na classe InCallController no serviço Telecom do Android 17 que permite que um aplicativo sem privilégios obtenha execução arbitrária de código como UID 1000 system_server sem nenhuma interação extra do usuário. Também mostramos aqui que a execução de código em system_server ainda pode ser facilmente adaptada para obter persistência, mesmo em versões modernas do Android.

Reportei esta vulnerabilidade à Equipe de Segurança do Android em 2026-04-10, ela foi confirmada em 2026-05-06 e foi corrigida no Boletim de Segurança do Android de setembro de 2026. (veja aqui o patch)

No momento do relato, ela afetava ativamente apenas builds Pixel a partir do Android 16 QPR3 (junto com builds Beta 17), até onde sei, mas depois chegou ao lançamento estável do AOSP do Android 17.

Para se proteger deste problema, certifique-se de instalar a atualização do sistema Google Play, bem como o patch de segurança do sistema. (Telecom é um componente mainline desde o Android 17)

TLPE

Notas sobre o PoC

  • O PoC demonstra a obtenção de execução de código em system_server usando a vulnerabilidade, registra id e stack trace no logcat e se reinstala como um componente de system_server.
  • Uma vez instalado o PoC, tocar no botão Start Exploit ou uma chamada sendo feita através da pilha telecom o acionará.
  • Compile o PoC executando o build.sh incluído. Caso contrário, você pode executar manualmente ./gradlew assembleSystemRelease, mover o app-system-release.apk resultante para app/src/poc/assets/system.apk e então executar ./gradlew assemblePocRelease.
  • O PoC registrará seu próprio certificado como um certificado ancestral para o UID 1000 após a exploração bem-sucedida. Este estado persiste através de OTAs, incluindo o patch da própria vulnerabilidade. Para limpar seu dispositivo após uma execução do PoC, você deve pressionar o botão "Uninstall" no PoC (que limpa o certificado injetado) — no entanto, recomendo fortemente usar seu próprio keystore de release para assinar um APK do PoC compilado durante os testes. (em vez daquele que o PoC usa por padrão em TLPE/app/teststore.jks)
  • Observe que o PoC, depois de entrar em system_server, também desativa o Play Protect definindo package_verifier_user_consent como -1 em Settings.Global, porque ele às vezes pode interceptar a transação de reinstalação devido a assinaturas desconhecidas. Você deve reativar isso em Configurações após os testes.
  • Foi validado no Pixel Android e no AOSP, mas o estágio de execução de código deve funcionar em versões do Android 17 personalizadas por OEMs. O estágio de reinstalação como system_server pode precisar de alguma personalização por OEM, pois depende de percorrer a estrutura do PMS usando símbolos que os OEMs às vezes alteram.

A saída esperada do logcat do 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

Esta é uma vulnerabilidade excepcionalmente direta. Sempre que certas ações relacionadas ao Telecom acontecem, o InCallController tenta descobrir serviços disponíveis através de getInCallServiceComponents. Isso ocorre naturalmente quando uma chamada é registrada no sistema, mas um aplicativo pode, na verdade, acioná-la sob demanda também graças à API de chamadas transacionais TelecomManager.addCall. (nota: é isso que o botão Start Exploit no PoC usa) Esta API precisa de MANAGE_OWN_CALLS, mas é uma permissão normal e invisível ao usuário, concedida automaticamente na instalação.

Esta enumeração é implementada nas versões vulneráveis assim:

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

Observe como serviceClassExists é executado contra qualquer componente que declare uma intent InCallService junto com o valor de metadados android.telecom.CLASS_EXISTENCE_CHECK, não apenas InCallServices válidos/habilitados. (essa verificação é implementada mais abaixo no ramo com getInCallServiceType e isServiceEnabled)

serviceClassExists é implementado como:

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

Os perigos de createPackageContext com CONTEXT_IGNORE_SECURITY são bem documentados, e neste caso é o próprio system_server executando-o contra um componente não confiável apenas para verificar se uma classe existe no DEX desse aplicativo. À primeira vista, no entanto, isso pode parecer seguro porque o contexto obtido é usado em Class.forName com initialize=false — o desenvolvedor provavelmente estava ciente desse risco e especificou esse argumento para garantir que a classe estrangeira não fosse inicializada em system_server.

Infelizmente, essa cautela chega tarde demais — o dano já está feito por getClassLoader no contexto estrangeiro. Se o aplicativo atacante define um AppComponentFactory em seu manifesto como android:appComponentFactory, o método getClassLoader em um LoadedApk correspondente a um contexto criado com CONTEXT_INCLUDE_CODE e CONTEXT_IGNORE_SECURITY primeiro recupera o class loader padrão do aplicativo estrangeiro do disco e então executa tanto o construtor da fábrica quanto seu método instantiateClassLoader antes de retornar o class loader.

Como essa classe é controlável pelo aplicativo atacante, isso leva imediatamente à execução arbitrária de código no contexto do Telecom.

Pós-exploração

Para mostrar as consequências da execução bem-sucedida de código em system_server, o PoC demonstra a reinstalação de si mesmo como um componente persistente de system_server. (a técnica é adaptada da técnica publicada no PoC AbxOverflow / CVE-2024-34740 por Michał Bednarski)

Fazemos isso:

  • Recuperando o objeto Signature do nosso aplicativo PoC refletindo dinamicamente em PackageManagerService.mSettings diretamente quando estamos em system_server.
  • Recuperando o SharedUserSetting correspondente a "android.uid.system" e injetando o Signature do PoC duas vezes em seu getSigningDetails().mPastSigningCertificates com CertCapabilities.SHARED_USER_ID.
  • Forçando a desinstalação do aplicativo PoC e reinstalando uma variante dele com android:sharedUserId="android.uid.system" e android:process="system" adicionados ao manifesto. (o que também libera a modificação volátil da etapa anterior para packages.xml)

Isso faz com que canJoinSharedUserId() em PackageSignatures passe devido à correspondência do histórico de rotação e produz privilégios persistentes de system_server.

Nota final

Uma pergunta que você pode ter é por que essa vulnerabilidade existe — em particular, por que há uma verificação de existência de classe opt-in protegida por um sinalizador de metadados não documentado?

É impossível dizer com certeza, mas a resposta provável para esse mistério pode ser encontrada cavando um pouco mais fundo no AOSP: tanto a verificação de classe quanto a comparação de metadados provavelmente foram adicionadas para considerar android.net.ConnectivityCallListenerService, que estava sendo implementado na mesma época.

Este serviço foi inicialmente definido no manifesto do framework, mas não foi implementado em nenhum lugar. (e quando foi adicionado, estava atrás do sinalizador de recurso Flags.FLAG_ENABLE_INCALL_SERVICE_API) Uma vez que as falhas foram observadas nos testes, alguém provavelmente decidiu implementar a correção como uma verificação reutilizável de "defesa em profundidade" em vez de uma exceção codificada — a definição do manifesto deste serviço diz que ele define "android.telecom.CLASS_EXISTENCE_CHECK" para "indicar que a classe deste serviço pode não estar presente em todos os builds" e "instruir o Telecom a verificar a existência da classe antes de tentar vincular". (e é por isso que estamos aqui agora)

Baixar ferramenta