# 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.

- Autor: Marcelo Silva
- Publicado em: 2026-10-07
- Tema: Segurança
- Série: Segurança Mobile, parte 2 de 2 (https://marcelxsilva.dev/series/seguranca-mobile/)
- URL: https://marcelxsilva.dev/artigos/arquitetura-do-android/

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:

```text
┌──────────────────────────────────────────────┐
│ 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:

1. **Bootloader**: o primeiro código que roda. Ele verifica a imagem do sistema (Verified Boot) e carrega o kernel.
2. **Kernel**: inicializa hardware e memória e inicia o primeiro processo de usuário, o `init`.
3. **init**: lê os arquivos `init.rc`, monta partições, aplica as políticas de SELinux e sobe os serviços nativos.
4. **Zygote**: um processo que carrega o ART e pré-carrega as classes e recursos mais usados do framework. Depois disso ele fica esperando.
5. **system_server**: o primeiro processo criado a partir do Zygote. É onde nascem os serviços do sistema.
6. **Apps**: quando o usuário toca num ícone, o Activity Manager pede ao Zygote um processo novo. O Zygote faz um `fork` de 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:

```text
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:

```text
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:

```text
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.
