Análise dinâmica: o app rodando sob instrumentação
Frida, objection, interceptação de tráfego e por que uma função suspeita só vira achado confirmado quando o app está rodando de verdade.
Série · Parte 10 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 artigo8 seções
Os artigos anteriores desta série cobriram a análise estática: ler o código, o manifesto e o binário de um app sem executá-lo. Ela mostra o que um app pode fazer. A análise dinâmica responde a outra pergunta: o que ele faz de fato, quando está rodando, conectado à rede, manipulando dados reais.
Essa mudança de modo exige um ambiente diferente e uma categoria de ferramenta própria: a instrumentação em tempo de execução.
Por que um ambiente de teste, não o app de produção de alguém
Testar a análise dinâmica de um app exige observar e, em muitos casos, alterar seu comportamento enquanto ele roda, o que inclui interceptar o tráfego de rede e inspecionar dados em memória. Fazer isso contra um app de terceiros, ou contra um backend de produção que não é seu, sem autorização, pode configurar crime na legislação brasileira, além de violar os termos de uso da maioria dos serviços. Por isso esse tipo de teste só deve ser feito em ambiente próprio, com autorização explícita, ou contra os apps vulneráveis de propósito que a própria OWASP mantém para treino.
Preparando um emulador com root
No Android, a análise dinâmica profunda (interceptar chamadas de função, driblar verificações locais) exige um aparelho com root ou, mais comumente para fins de teste, um emulador criado com uma imagem de sistema Google APIs (que tem root habilitado), em vez da imagem Google Play (sem root), usada normalmente por consumidores finais:
sdkmanager "system-images;android-34;google_apis;arm64-v8a"
avdmanager create avd -n pentest -k "system-images;android-34;google_apis;arm64-v8a"
emulator -avd pentest -writable-system &
adb root && adb remount
Com o emulador em modo de leitura e escrita (último comando acima), é possível remontar o sistema de arquivos e instalar as ferramentas de instrumentação direto no dispositivo.
Frida: a base da instrumentação
O Frida é um framework de instrumentação dinâmica de código aberto que permite injetar scripts em JavaScript dentro de um processo em execução, para Android, iOS e várias outras plataformas. Na prática, isso significa interceptar a chamada de qualquer método (em Java, Objective-C ou código nativo) e substituir seu comportamento: por exemplo, fazer uma função que normalmente retorna false passar a retornar sempre true.
uv tool install frida-tools
uv tool install objection
Com o emulador de root pronto (passo anterior), falta subir o frida-server dentro dele, na mesma versão instalada na máquina:
frida --version
# baixe frida-server-<mesma versão>-android-arm64 em github.com/frida/frida/releases
adb push frida-server /data/local/tmp/
adb shell "chmod 755 /data/local/tmp/frida-server && /data/local/tmp/frida-server &"
frida-ps -U | head
O objection é uma camada construída em cima do Frida que empacota as tarefas mais comuns de um pentest mobile em comandos prontos, sem exigir que o testador escreva o próprio script de instrumentação para cada uma:
adb install example.apk
objection -g com.exemplo.app explore
Dentro do objection, alguns comandos úteis logo de início:
android root disable
android sslpinning disable
android hooking list activities
android keystore list
env
Um app com proteções anti-instrumentação (detectadas, por exemplo, pelo APKiD) pode se recusar a abrir quando percebe a presença do Frida. Nesse caso, uma alternativa é reempacotar o próprio APK com o Frida Gadget embutido, uma biblioteca que roda a instrumentação sem depender de um processo externo com privilégio elevado:
objection patchapk -s example.apk
Interceptando o tráfego de rede
Para observar as requisições que o app faz, a técnica padrão é colocar um proxy de interceptação no meio da conexão, usando ferramentas como o mitmproxy, o HTTP Toolkit ou o Burp Suite:
brew install --cask http-toolkit
brew install --cask mitmproxy
O proxy instala sua própria autoridade certificadora (CA) no dispositivo de teste, o que permite decifrar o tráfego HTTPS para leitura, desde que o app aceite essa CA: abra o HTTP Toolkit e escolha "Android device via ADB", ou rode mitmweb --listen-port 8080 e instale a CA gerada no emulador manualmente.
É exatamente aí que entra o certificate pinning, descrito no artigo anterior: um app com pinning corretamente implementado rejeita a CA do proxy, mesmo que ela esteja instalada como confiável pelo sistema. Já com o objection isso costuma bastar (android sslpinning disable, visto acima), mas para testar o comportamento da aplicação com mais controle, scripts de "unpinning" (como os do projeto frida-interception-and-unpinning, mantido pela equipe do HTTP Toolkit) usam o Frida para interceptar e neutralizar a checagem de pinning em tempo de execução:
git clone https://github.com/httptoolkit/frida-interception-and-unpinning
cd frida-interception-and-unpinning
frida -U -l config.js -l android/android-certificate-unpinning.js -f com.exemplo.app
Com o tráfego visível, os pontos clássicos de revisão são: token de autenticação exposto numa URL (em vez de num header, por exemplo) ou num log; respostas da API que vazam dado de outro usuário quando um identificador é trocado na requisição (IDOR, uma das categorias mais comuns de falha de autorização); e se o backend aceita, fora do app, uma credencial de cliente que só deveria funcionar vindo do app legítimo.
Componentes expostos, de verdade
A fase estática de manifesto identifica quais componentes estão declarados como exportados. A fase dinâmica confirma o que acontece quando eles de fato são chamados:
adb shell am start -n com.exemplo.app/.MainActivity
adb shell am start -a android.intent.action.VIEW -d "https://exemplo.com/rota?param=teste"
O primeiro comando abre uma activity específica como se fosse outro app fazendo a chamada; o segundo testa um deep link diretamente. Para service, receiver e content provider, que não têm um atalho tão simples quanto uma activity, ferramentas especializadas como o drozer automatizam o teste:
uv tool install drozer
O que fica gravado no aparelho
Com acesso root ao sistema de arquivos, dá para inspecionar diretamente o que o app grava: preferências compartilhadas, bancos de dados locais (incluindo o que uma camada como o AsyncStorage, comum em React Native, usa por baixo) e os logs do sistema:
adb shell run-as com.exemplo.app ls -R /data/data/com.exemplo.app/
adb shell "su -c 'ls -R /data/data/com.exemplo.app/shared_prefs /data/data/com.exemplo.app/databases'"
adb logcat --pid=$(adb shell pidof com.exemplo.app)
É nesse momento que um dado sensível salvo "temporariamente" em texto puro, token, informação pessoal, aparece de forma concreta, em vez de apenas suspeitado pela leitura do código.
iOS: jailbreak ou Gadget
No iOS, o mesmo nível de instrumentação normalmente exige um dispositivo com jailbreak, ou, como alternativa sem jailbreak, reempacotar o IPA com o Frida Gadget, de forma equivalente ao que acontece no Android:
objection patchipa --source example.ipa --codesign-signature "<identidade>"
objection -g com.exemplo.app explore
A partir daí, o objection oferece os mesmos atalhos adaptados à plataforma:
ios sslpinning disable
ios keychain dump
ios nsuserdefaults get
ios plist cat <arquivo>
Estático e dinâmico, lado a lado
Nenhuma das duas análises substitui a outra. Uma função suspeita encontrada lendo o código (estática) só vira um achado confirmado quando reproduzida com o app rodando (dinâmica); e um comportamento estranho observado em tempo de execução só se transforma numa correção acionável quando alguém encontra a linha exata de código que o origina. É justamente esse fechamento de ciclo, do candidato a achado até o registro formal, o assunto do último artigo desta série.






