Arquitetura do Android: do kernel ao app
As camadas do Android, o boot até o Zygote, o sandbox com UID e SELinux, o Binder, permissões e assinatura, vistos por quem vai testar a segurança de um app.
Série · Parte 2 de 2 Segurança Mobile
- 01 O mapa da segurança mobile: OWASP MASVS, MASWE e MASTG 7 min
- 02 Arquitetura do Android: do kernel ao app 10 min
Neste artigo15 seções
Quase toda vulnerabilidade de app Android é, no fundo, uma quebra de alguma garantia da plataforma. Dados lidos por quem não devia, um componente chamado por outro app, um certificado aceito sem verificação. Para entender por que essas coisas acontecem, e por que algumas técnicas de pentest funcionam, primeiro é preciso entender como o Android é montado.
Este artigo percorre o sistema do kernel até o app: as camadas, o caminho do boot até o app abrir, como um app fica isolado dos outros e como eles conversam entre si.
As camadas do Android
O Android é um sistema em camadas. Cada uma esconde a complexidade da de baixo e oferece uma interface mais simples para a de cima:
┌──────────────────────────────────────────────┐
│ Apps (sistema e de terceiros) │
├──────────────────────────────────────────────┤
│ Java API Framework │
│ Activity Manager, Package Manager, Window... │
├───────────────────────┬──────────────────────┤
│ Bibliotecas nativas │ Android Runtime │
│ libc (Bionic), SSL, │ (ART) │
│ SQLite, WebView... │ │
├───────────────────────┴──────────────────────┤
│ HAL (Hardware Abstraction Layer) │
├──────────────────────────────────────────────┤
│ Kernel Linux (drivers, Binder, SELinux) │
└──────────────────────────────────────────────┘
Kernel Linux
A base é um kernel Linux, com algumas adições específicas do Android. É ele que cuida de processos, memória, sistema de arquivos, rede e drivers de hardware.
Para segurança, o kernel é a peça mais importante, porque é ele que garante o isolamento entre apps. Usuários Linux, permissões de arquivo, SELinux e seccomp são todos mecanismos do kernel. Uma das adições do Android é o driver Binder, o canal de comunicação entre processos que aparece em praticamente tudo no sistema.
HAL
A Hardware Abstraction Layer é uma camada de interfaces padronizadas para o hardware: câmera, sensores, áudio, Bluetooth, leitor de digital. O fabricante implementa essas interfaces para o hardware dele, e o resto do sistema não precisa saber qual chip está por baixo.
Desde o Android 8 (Project Treble), essa parte do fabricante fica separada do restante do sistema, o que permite atualizar o Android sem reescrever os drivers. Para pentest de app ela raramente aparece direto, mas é por ela que passa, por exemplo, o hardware que guarda as chaves criptográficas do Keystore.
Bibliotecas nativas e ART
Acima da HAL ficam as bibliotecas em C e C++: a libc do Android (Bionic), SQLite, BoringSSL, o motor da WebView e outras. Apps também podem trazer as próprias bibliotecas nativas (arquivos .so), e é o caso de todo app React Native: o Hermes, o motor JavaScript, é uma biblioteca nativa empacotada dentro do APK.
Ao lado delas está o Android Runtime (ART), que executa o código dos apps. Um app escrito em Kotlin ou Java não vira código de máquina na compilação: ele vira bytecode DEX (Dalvik Executable), guardado nos arquivos classes.dex dentro do APK.
O ART pega esse bytecode e roda de três formas combinadas:
- Interpretado, instrução por instrução.
- JIT (just-in-time), compilando para código nativo os trechos mais usados enquanto o app roda.
- AOT (ahead-of-time), compilando antes, com a ferramenta
dex2oat, normalmente guiado por um perfil de quais métodos são mais usados.
O ART substituiu a máquina virtual Dalvik no Android 5. O nome "DEX" ficou.
Para pentest isso importa por um motivo simples: bytecode DEX guarda muita informação (nomes de classes, métodos, tipos), então ele é fácil de decompilar de volta para algo próximo do código original. É por isso que ferramentas como o jadx conseguem mostrar um app Kotlin quase legível, e por isso a ofuscação com R8/ProGuard existe.
Java API Framework
É a camada com que o desenvolvedor Android conversa: as classes de android.*. Por trás delas existem os serviços do sistema, que rodam num processo privilegiado chamado system_server:
| Serviço | Responsabilidade |
|---|---|
| Activity Manager | Ciclo de vida de processos e componentes, intents |
| Package Manager | Apps instalados, permissões, assinaturas |
| Window Manager | Janelas e o que aparece na tela |
| Location, Telephony, Notification... | Cada recurso do aparelho tem o seu |
Quando um app chama startActivity() ou pede a localização, ele não faz isso sozinho. Ele pede ao serviço do sistema, que roda em outro processo, e esse serviço decide se o pedido é permitido. Essa separação é a base do modelo de segurança.
Apps
No topo ficam os apps, tanto os do sistema (discador, configurações) quanto os instalados pelo usuário. Na visão do sistema, eles são todos iguais: processos isolados que pedem coisas ao framework. A diferença está nas permissões e na assinatura que cada um tem.
Do boot até o app abrir
Entender a ordem do boot ajuda a entender várias técnicas de ataque, principalmente as de root e de instrumentação:
- Bootloader: o primeiro código que roda. Ele verifica a imagem do sistema (Verified Boot) e carrega o kernel.
- Kernel: inicializa hardware e memória e inicia o primeiro processo de usuário, o
init. - init: lê os arquivos
init.rc, monta partições, aplica as políticas de SELinux e sobe os serviços nativos. - Zygote: um processo que carrega o ART e pré-carrega as classes e recursos mais usados do framework. Depois disso ele fica esperando.
- system_server: o primeiro processo criado a partir do Zygote. É onde nascem os serviços do sistema.
- Apps: quando o usuário toca num ícone, o Activity Manager pede ao Zygote um processo novo. O Zygote faz um
forkde si mesmo, o processo filho recebe a identidade do app e carrega o código dele.
O Zygote existe por desempenho: em vez de cada app iniciar um runtime do zero, ele herda uma cópia já pronta. Um efeito colateral é que todo app começa como cópia do mesmo processo, e ferramentas de instrumentação como o Frida aproveitam esse ponto para se injetar no app logo no início.
Já o bootloader é a primeira linha de defesa contra root. Num aparelho com bootloader bloqueado, uma imagem modificada não passa na verificação. Para ter root de verdade é preciso desbloquear o bootloader, o que apaga os dados do aparelho justamente para proteger o dono. Ferramentas como o Magisk funcionam modificando a imagem de boot para ganhar privilégio antes do resto do sistema subir.
O sandbox de aplicativos
Aqui está o coração da segurança do Android: cada app é um usuário Linux diferente.
Na instalação, o sistema dá a cada app um UID próprio, a partir de 10000. É por isso que, na lista de processos do aparelho, os apps aparecem com nomes como u0_a123: usuário 0 do aparelho (o dono, no caso de multiusuário), app de número 123, ou seja, UID 10123.
Como cada app é um usuário diferente, todas as regras normais de permissão do Linux passam a valer entre eles:
- Cada app tem um diretório privado em
/data/data/<pacote>(atalho para/data/user/0/<pacote>), acessível só pelo UID dele. - Um app não consegue ler a memória nem os arquivos do outro.
- Um processo de app não tem privilégio para mexer no sistema.
O diretório de um app, visto de dentro do aparelho:
drwx------ 5 u0_a123 u0_a123 4096 ... /data/data/com.exemplo.app
Isso é chamado de controle de acesso discricionário (DAC): o dono do arquivo decide quem acessa. Sozinho, ele tem uma fraqueza: se um app gravar um arquivo com permissão aberta, ou se um processo com mais privilégio for comprometido, o isolamento cai.
SELinux
Por isso o Android também usa SELinux em modo enforcing desde o Android 5. É um controle de acesso obrigatório (MAC): uma política central define o que cada tipo de processo pode fazer, independente de quem é o dono do arquivo. Apps de terceiros rodam num domínio restrito (untrusted_app), e mesmo um processo rodando como root fica limitado ao que a política do domínio dele permite.
Cada processo carrega um contexto do SELinux. O de um app de terceiros tem esta forma:
u:r:untrusted_app:s0:c123,c256,c512,c768 u0_a123 ... com.exemplo.app
As categorias no fim (c123,c256...) separam ainda mais um app do outro dentro do mesmo domínio.
seccomp
Desde o Android 8, os processos de app também têm um filtro seccomp, que bloqueia chamadas de sistema do kernel que um app nunca deveria precisar. Isso reduz a superfície de ataque do kernel caso o app seja comprometido.
O que o root quebra
Root significa ter um processo rodando como UID 0 e, geralmente, com a política do SELinux modificada para liberá-lo. Com isso as três camadas (DAC, SELinux e o próprio Verified Boot) deixam de proteger os dados de um app. Num aparelho com root, o diretório privado de qualquer app pode ser lido.
Esse é o ponto que muita gente que desenvolve não percebe: o sandbox protege um app dos outros apps, não do dono do aparelho. Num pentest, o testador é o dono do aparelho. Por isso dados sensíveis gravados em texto puro dentro do diretório privado continuam sendo um problema: o sandbox não foi feito para escondê-los de quem controla o aparelho, nem de um malware que consiga escalar privilégio.
Binder: como os processos conversam
Se cada app está isolado, como ele pede algo ao sistema ou a outro app? Pelo Binder, o mecanismo de comunicação entre processos (IPC) do Android.
O fluxo é este:
App (UID 10123) system_server
│ ▲
│ getSystemService(LOCATION) │ getCallingUid() == 10123
│ requestLocationUpdates(...) │ esse UID tem a permissão?
▼ │
┌──────────────── driver Binder (kernel) ───────────┐
│ anexa à transação o UID e o PID de quem chamou │
└───────────────────────────────────────────────────┘
O detalhe importante é que quem preenche o UID de quem chamou é o kernel, não o app. O app não tem como mentir sobre a própria identidade. O serviço do outro lado olha esse UID e decide se atende o pedido.
Quase tudo no Android passa por Binder: intents, chamadas aos serviços do sistema, content providers, serviços de outros apps. Quando um app declara um componente como exportado no AndroidManifest.xml, ele está dizendo "outros processos podem me chamar por Binder". Se esse componente não conferir quem está chamando, qualquer app instalado consegue usá-lo. Isso é uma das famílias de vulnerabilidade mais comuns do grupo MASVS-PLATFORM, e é exatamente o que ferramentas como o drozer exploram.
Permissões
As permissões do Android são a forma de um app ganhar acesso a algo fora do sandbox. Elas são declaradas no manifesto e conferidas pelos serviços do sistema, através do UID de quem chama. Os principais níveis de proteção:
| Nível | Como é concedida | Exemplos |
|---|---|---|
normal |
Automaticamente na instalação | Internet, vibrar |
dangerous |
Pelo usuário, em tempo de execução (desde o Android 6) | Câmera, localização, contatos |
signature |
Só para apps assinados com o mesmo certificado de quem definiu a permissão | Permissões internas do sistema ou entre apps da mesma empresa |
Apps também podem definir as próprias permissões para proteger seus componentes. Um erro comum é definir uma permissão como normal quando a intenção era limitar o acesso aos apps da própria empresa: qualquer app pode declará-la e recebê-la. O nível que restringe o acesso aos apps do mesmo autor é signature.
Assinatura de apps
Todo APK precisa ser assinado para ser instalado. A assinatura serve a dois propósitos:
- Integridade: garante que o APK não foi alterado depois de assinado.
- Identidade: atualizações de um app só são aceitas se vierem assinadas com o mesmo certificado. É assim que o sistema sabe que a atualização vem do mesmo autor.
Existem várias versões do esquema de assinatura: v1 (o formato JAR antigo), v2 (Android 7, assina o arquivo inteiro), v3 (Android 9, permite rotacionar a chave) e v4 (Android 11, para instalação incremental).
O ponto para pentest é que a assinatura não impede ninguém de modificar um app. Ela só garante que o app modificado vai ter outra assinatura. Um APK pode ser desmontado, alterado (uma verificação desligada, o modo de depuração ativado, o Frida embutido), remontado e assinado com outra chave. O app modificado instala normalmente, só não substitui o original. É por isso que verificações de integridade feitas dentro do próprio app fazem parte do grupo MASVS-RESILIENCE, e por isso elas também podem ser contornadas.
Juntando as peças
Olhando de cima, o modelo de segurança do Android se apoia em poucas ideias:
| Garantia | Mecanismo | O que quebra |
|---|---|---|
| Um app não lê os dados do outro | UID por app, DAC e SELinux | Root, arquivo com permissão aberta, backup |
| Um app só faz o que pode | Permissões conferidas pelo serviço via Binder | Componente exportado sem verificação |
| A identidade de quem chama é confiável | UID anexado pelo kernel no Binder | Serviço que confia em dado enviado pelo app |
| O sistema não foi adulterado | Verified Boot e assinatura | Bootloader desbloqueado, APK reassinado |
Boa parte dos testes do MASTG é, no fundo, verificar se o app usa essas garantias do jeito certo ou se depende de algo que não está garantido.






