Skip to content
KitploitKITPLOIT
FerramentasExploitsBlog
Log in
Enviar
FerramentasExploitsBlog
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.

FeedsContatoPrivacidade© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
TheLastBundleMismatch — Writeup e exploit para CVE-2023-45777, bypass para validação de Intent dentro do AccountManagerService no Android 13 apesar da mitigação "Lazy Bundle" | Kitploit
Ferramentas/GitHubGitHub/michalbednarski/thelastbundlemismatch
Segurança AndroidAnálise de VulnerabilidadesExploraçãoAnálise de BináriosPapers e Pesquisa
GitHubmichalbednarski/thelastbundlemismatch

TheLastBundleMismatch

Writeup e exploit para CVE-2023-45777, bypass para validação de Intent dentro do AccountManagerService no Android 13 apesar da mitigação "Lazy Bundle"

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
Ver Repositório
1011467há 2 anosRevisado pelo Kitploit

Patch misterioso

Vamos começar desta vez com o patch que apareceu como correção para o CVE-2023-45777 no Android Security Bulletin:```diff diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 7a19d034c2c8..5238595fe2a2 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -4923,7 +4923,7 @@ public class AccountManagerService p.setDataPosition(0); Bundle simulateBundle = p.readBundle(); p.recycle();

  •        Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT);
    
  •        Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT, Intent.class);
           if (intent != null && intent.getClass() != Intent.class) {
               return false;
           }
    
Poucas pessoas ficaram curiosas o suficiente para me perguntar; anteriormente respondi a elas com algumas dicas e agora estou publicando o writeup completo para este problema

Mas primeiro vamos fornecer algum contexto sobre o que está acontecendo neste patch

Esta é uma alteração no [método `checkKeyIntent()`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/accounts/AccountManagerService.java;l=4938-4954;drc=47de64a38aa1799cb41f41b2ea0c539ee61de64d). Este método realiza várias verificações para garantir que o `Intent` fornecido pelo aplicativo seja seguro para o sistema lançar (usando privilégios do sistema)

Primeiro, este método usa `checkKeyIntentParceledCorrectly()`, que serializa e desserializa novamente o `Bundle` que estamos verificando e checa se o `Intent` obtido do `Bundle` antes disso corresponde ao `Intent` do `Bundle` após tal ciclo. Como o lançamento do `Intent` acontece em outros processos de aplicativos do sistema, diferentes daquele que realiza a validação, anteriormente era [possível construir `Bundle`-s que pareciam seguros durante a validação dentro do `AccountManagerService`, mas continham um `Intent` diferente após serem enviados para o próximo processo](https://github.com/michalbednarski/IntentsLab/issues/2#issuecomment-344365482). Isso simula o envio do `Bundle` para o próximo processo, a fim de detectar tais situações.

Após `checkKeyIntentParceledCorrectly()`, temos a chamada `bundle.getParcelable()`, que este patch altera da versão obsoleta que podia construir qualquer objeto para uma que valida que o objeto prestes a ser desserializado é do tipo especificado no segundo parâmetro

Essa versão com parâmetro de tipo foi introduzida no Android 13, como parte de um endurecimento maior do `Parcel`/`Bundle`. Em particular, antes do Android 13, quando um `Bundle` era enviado entre processos, ele mantinha uma cópia bruta de todos os dados serializados até que qualquer item fosse acessado, momento em que cada valor era desserializado. Agora, quando qualquer valor é acessado pela primeira vez após o `Bundle` ter sido recebido, apenas as chaves `String` e os valores de tipos primitivos são desserializados, enquanto valores não primitivos são deixados como `LazyValue`-s, que têm seu comprimento armazenado como parte dos dados serializados para garantir que, mesmo quando a lógica de serialização/desserialização estiver incompatível, tais incompatibilidades não afetem outras entradas

Antes de mergulharmos, vamos dar uma olhada no `LazyValue`: em seu código-fonte [temos um ótimo comentário explicando sua estrutura de dados](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=4392-4399;drc=03c34f57c05feecfb090de3917787f049cb5f804)```
                     |   4B   |   4B   |
mSource = Parcel{... |  type  | length | object | ...}
                     a        b        c        d
length = d - c
mPosition = a
mLength = d - a

mPosition e mLength descrevem a localização de todos os dados de LazyValue no Parcel original, incluindo type e length. "length" (sem o "m" no início) refere-se ao valor de comprimento como escrito no Parcel e exclui o cabeçalho (type e length)

Se um Bundle contendo LazyValue estiver sendo encaminhado para outro processo, todo o LazyValue, incluindo os campos type e length, é copiado literalmente de Bundle.mParcelledData para o Parcel de destino

Quando o item do Bundle representado por LazyValue é acessado, o Parcel é rebobinado para mPosition e readValue() é chamado. Se um argumento de tipo for passado para bundle.getParcelable(), ele é propagado para readValue(), que garantirá tanto que o tipo prestes a ser desparcelado seja o esperado quanto verificará após o desparcelamento que o tipo do valor desparcelado é o esperado. Após o desparcelamento, LazyValue é substituído, de modo que na próxima vez que o Bundle for escrito no Parcel, o valor será serializado novamente por meio de writeValue()

O uso do parâmetro tipado de Bundle.get*()/Parcel.read*() é mais relevante para métodos como Parcel.readParcelableList(), que retorna um ArrayList e, devido ao Apagamento de Tipo (Type Erasure) do Java, mesmo que você fizesse algo como List<SomeParcelableType> field = parcel.readParcelableList();, a parte <SomeParcelableType> não era aplicada em tempo de execução, e tal List poderia conter qualquer classe Parcelable disponível no sistema; portanto, todos os createFromParcel/writeToParcel disponíveis no sistema poderiam ser usados como parte da serialização/desserialização do tipo que continha tal List

Você também pode querer conferir a apresentação da equipe de Segurança e Privacidade do Android sobre a introdução desses mecanismos (slides, vídeo)

Aqui, no entanto, o uso da versão tipada parece redundante, pois também verificamos explicitamente o tipo do objeto retornado. Então, o que está acontecendo e qual vulnerabilidade está sendo corrigida aqui?

Efeitos colaterais

Dê outra olhada no patch desde o início

  • Se o valor desserializado sob a chave "intent" for um Intent
    • Ele será validado para apontar para um componente que seja seguro para o sistema iniciar
    • Se uma incompatibilidade pudesse ser acionada a partir do objeto Intent, teríamos um problema muito maior
  • Se o valor sendo desserializado não for um Intent
    • Para fazer algo ruim, precisaríamos ter um Intent dentro do Bundle depois que ele for enviado para outro processo, mas o tipo do Parcelable é salvo em um offset anterior a qualquer possível incompatibilidade, e o prefixo de comprimento do LazyValue nos impede de modificar os próximos pares chave-valor em caso de incompatibilidade de writeToParcel/createFromParcel
Baixar ferramenta