Análise estática no Android – parte 1: abrindo o APK
Como descompactar um APK em formatos diferentes, o que cada um revela e o que muda quando o app é feito em React Native.
Série · Parte 4 de 11 Segurança Mobile
- 01 O mapa da segurança mobile: OWASP MASVS, MASWE e MASTG 6 min
- 02 Arquitetura do Android: do kernel ao app 10 min
- 03 Arquitetura do iOS: do boot ao Secure Enclave 7 min
- 04 Análise estática no Android – parte 1: abrindo o APK 6 min
- 05 Análise estática no Android – parte 2: segredos no binário 4 min
- 06 Análise estática no Android – parte 3: manifesto e superfície de ataque 5 min
- 07 Análise estática no Android – parte 4: TLS, pinning e armazenamento 5 min
- 08 Scanners automáticos: o que eles pegam e o que passa batido 4 min
- 09 Análise estática no iOS: binário, Info.plist e entitlements 5 min
- 10 Análise dinâmica: o app rodando sob instrumentação 5 min
- 11 Do achado ao relatório: confirmar, classificar e registrar 4 min
Neste artigo4 seções
Um APK é, na prática, um arquivo ZIP com uma extensão diferente e algumas regras a mais. Isso significa que boa parte do trabalho de análise estática, olhar o app sem executá-lo, começa simplesmente descompactando esse arquivo e escolhendo a ferramenta certa para cada pergunta.
Este artigo cobre a primeira fase prática da série: como abrir um APK em formatos diferentes, por que cada um serve para um tipo de busca, e o que muda quando o app é feito em React Native.
Três formatos, três perguntas
Um APK sozinho não é muito legível: o AndroidManifest.xml vem em binário, o código Kotlin/Java vem em bytecode DEX, e os recursos (strings.xml, layouts) também vêm compilados. Por isso o primeiro passo de qualquer análise é converter o pacote em pelo menos três formas diferentes, cada uma útil para um tipo de busca:
| Formato | Como obter | Para que serve |
|---|---|---|
| Arquivos crus | unzip |
Ver o assets/, bibliotecas nativas (.so), bundle JavaScript |
| Java decompilado | jadx | Ler código Kotlin/Java quase original, BuildConfig, lógica de negócio |
| Manifest e recursos em XML | apktool | AndroidManifest.xml e strings.xml legíveis, smali |
O jadx converte bytecode DEX de volta para algo muito próximo do código-fonte original, porque esse bytecode preserva nomes de classes, métodos e variáveis (ao contrário de um binário nativo compilado, que perde quase tudo isso). É esse detalhe que torna a engenharia reversa de um APK relativamente acessível, e por isso ferramentas de ofuscação como o R8 existem: elas embaralham esses nomes antes de publicar o app.
O apktool já olha para outro lado do pacote: ele desmonta o DEX em smali (uma representação textual do bytecode, mais próxima de assembly do que de Java) e, principalmente, decodifica o manifesto e os recursos compilados de volta para XML legível. É ali que ficam as permissões, os componentes exportados e a configuração de rede do app, assunto do próximo artigo.
Na prática, os três formatos saem de três comandos, rodados a partir do APK (o jadx e o apktool se instalam com um gerenciador de pacotes comum, como o Homebrew no macOS: brew install jadx apktool):
mkdir -p pentest && cd pentest
unzip -q -o example.apk -d apk-out
jadx -q -d jadx-out example.apk
apktool d -f -q example.apk -o apktool-out
| Pasta | O que tem | Para que serve |
|---|---|---|
apk-out/ |
Arquivos crus (dex, assets, libs nativas) | Bundle JS, arquivos soltos em assets/ |
jadx-out/ |
Java decompilado | BuildConfig, código nativo do app |
apktool-out/ |
Manifest e res/ em XML legível |
Manifest, strings.xml, configuração de rede |
O jadx costuma ser o mais lento dos três (minutos, não segundos, em apps grandes); nada impede seguir explorando as outras duas pastas enquanto ele termina.
Se o pacote for um AAB
Hoje a Google Play exige o envio no formato Android App Bundle (.aab), não mais o APK direto. O AAB não é instalável sozinho: ele contém os recursos separados por configuração (idioma, densidade de tela, arquitetura de CPU) e é a própria Play Store quem gera, na hora da instalação, o APK otimizado para cada aparelho.
Para analisar um AAB como analisaria um APK, a ferramenta oficial do Google, o bundletool, gera um APK universal, que contém todas as configurações juntas:
java -jar bundletool.jar build-apks --bundle=example.aab --output=example.apks --mode=universal --overwrite
unzip -p example.apks universal.apk > universal.apk
A partir desse universal.apk, bastam as mesmas técnicas de descompactação, jadx e apktool descritas acima.
O caso React Native: o bundle JavaScript
Apps feitos com React Native acrescentam uma camada ao que o jadx e o apktool mostram. A lógica de produto não fica (só) em Kotlin: ela fica num bundle JavaScript, um único arquivo que concentra todo o código JS da aplicação, normalmente em assets/index.android.bundle dentro do APK.
Esse bundle existe em duas formas possíveis, dependendo de como o projeto foi configurado:
- Texto puro (minificado): ainda é JavaScript, só que compactado numa linha. Dá para ler direto ou formatar com uma ferramenta como o Prettier.
- Bytecode Hermes: desde que o Hermes, o motor JavaScript criado pela Meta para React Native, se tornou o padrão do framework (a partir da versão 0.70), o bundle passou a ser compilado para um bytecode próprio, não mais texto. Ferramentas como o hermes-dec fazem o caminho inverso: extraem a tabela de strings embutida no bytecode e, com limitações, produzem um pseudo-JavaScript aproximado do código original.
Dá para confirmar qual dos dois formatos um bundle usa com o utilitário file, do Unix, e localizar o próprio bundle dentro do pacote descompactado:
find apk-out -name "*.bundle" -o -name "*.jsbundle" -o -name "*.hbc"
file apk-out/assets/index.android.bundle
find apk-out -iname "libhermes*"
Resposta do file |
O que fazer |
|---|---|
Hermes JavaScript bytecode, version <n> |
Extrair as strings do bytecode (próximo passo) |
ASCII text / UTF-8 text |
É JS minificado. Dá para ler direto ou formatar com o Prettier |
A presença de uma biblioteca libhermes.so no APK também é um indício forte de que o app usa Hermes, mesmo antes de olhar o bundle.
Com um bundle em bytecode Hermes, o primeiro passo é extrair a tabela de strings embutida, a fonte mais confiável de texto legível dentro do binário (uv tool install hermes-dec instala o pacote):
hbc-file-parser apk-out/assets/index.android.bundle > hbc-strings.txt
strings -n 5 apk-out/assets/index.android.bundle > bundle.txt
Para ir além da tabela de strings e tentar reconstruir a lógica em si, o próprio hermes-dec traz um desassemblador e um decompilador (pseudo-JS, que não compila: ele não reconstrói laços nem if):
hbc-disassembler apk-out/assets/index.android.bundle saida.hasm
hbc-decompiler apk-out/assets/index.android.bundle saida.js
O strings funciona em qualquer um dos dois formatos (bytecode ou texto) e é mais rápido, mas corta trechos de texto muito longos; o hbc-strings.txt, gerado pelo hermes-dec, é a leitura completa.
Dentro desse texto, o que se procura é sempre parecido: endereços de servidor que não deveriam estar na versão pública (ambientes de desenvolvimento ou homologação), nomes de variáveis de segredo, tokens no formato JWT, chaves de provedores de nuvem e qualquer lógica de validação que um atacante possa decifrar lendo o próprio código:
grep -oE 'https?://[A-Za-z0-9._-]+' bundle.txt | sort -u
grep -oE '[^ |]*(dev|develop|hml|homolog|staging|test)[^ |]*' hbc-strings.txt | sort -u
grep -oE '[^ |]*(secret|SECRET|password|token|TOKEN)[^ |]*' hbc-strings.txt | sort -u
grep -oE 'eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*' hbc-strings.txt | sort -u
grep -oE "AIza[0-9A-Za-z_-]{35}" hbc-strings.txt
grep -oE "AKIA[0-9A-Z]{16}" hbc-strings.txt
Na ordem: todos os hosts chamados pelo app; endereços de ambientes que não são de produção; nomes de variável que sugerem segredo; tokens JWT; e dois padrões conhecidos de chave (API do Google e credencial da AWS).
Encontrar uma dessas strings é só metade do trabalho: o passo seguinte, quando se tem acesso ao repositório do app, é localizar a origem exata no código-fonte com uma busca pelo trecho encontrado (git grep -n -F '<trecho>' -- src), para entender se aquilo é intencional (um valor de exemplo) ou um vazamento real.
Limites das ferramentas
Nem toda ferramenta de decompilação acompanha a evolução do Hermes. O hbctool, por exemplo, só sabe ler as versões 59 e 62 do bytecode, o que o deixa defasado em apps mais novos. Vale sempre conferir, antes de montar um fluxo de análise em cima de uma ferramenta de terceiros, se ela cobre a versão do Hermes usada pelo app analisado.
No próximo artigo da série: o que dá para aprender olhando só para as variáveis de ambiente que acabam compiladas dentro do binário, e onde cada plataforma esconde esse dado.






