Tráfego HTTPS de um app Flutter chegando descriptografado ao Burp — sem certificado CA instalado no aparelho.
## Por que existe Apps feitos em Flutter não usam a pilha de TLS do Android: o engine embute o próprio **BoringSSL** e mantém a própria cadeia de confiança. Na prática, instalar o certificado CA do Burp no sistema **não intercepta nada** — o app ignora o repositório de certificados do Android por completo. E como o Flutter também não respeita o proxy configurado no Wi-Fi, o tráfego simplesmente sai pela rede sem passar pelo seu interceptador. A técnica para contornar isso é conhecida, mas normalmente exige rodar um script Frida com o `frida-server` ativo e o cabo conectado, a cada sessão. O FlutterTap transforma essa mesma abordagem em um módulo persistente: instala uma vez, escolhe os apps, e funciona sozinho a cada boot. ## Por que um módulo, e não Frida ou LSPosed **Frida** é insuperável para *descobrir* como um app funciona — você edita JavaScript e vê o efeito em segundos. Foi assim que a técnica nasceu. O problema é *operar* com ela: depende de `frida-server` rodando com sessão ativa (fechou o terminal, perdeu o hook), não sobrevive a um reboot, exige capturar o processo no nascimento (`-f`) para pegar hooks de inicialização, e — o mais relevante num teste sério — **abre uma porta em escuta e deixa rastros conhecidos** (nomes de thread, strings em memória, entradas em `/proc`). Detectar Frida é literalmente a primeira coisa que qualquer SDK de proteção mobile faz. **LSPosed/Xposed** não é questão de conveniência, é de camada: ele hooka **métodos Java/ART**. No Flutter, `verify_cert_chain` e `GetSockAddr` são funções **nativas e não exportadas dentro do `libflutter.so`** — não existe método Java para interceptar. Um módulo LSPosed teria de fazer exatamente o mesmo trabalho nativo que o FlutterTap faz, carregando por cima todo o runtime do Xposed sem ganhar nada. | | Frida | LSPosed | FlutterTap | |---|---|---|---| | Alcança o BoringSSL nativo | sim | **não diretamente** | sim | | Sobrevive a reboot | não | sim | sim | | Dispensa cabo/ferramenta externa | não | sim | sim | | Porta em escuta / processo externo | sim (detectável) | não | não | | Pega o app desde o primeiro instante | só com spawn manual | sim | sim | | Zero impacto em apps não selecionados | — | depende | sim (`DLCLOSE`) | | Velocidade de iteração no desenvolvimento | **excelente** | média | baixa | A última linha é uma desvantagem honesta: iterar num módulo nativo é bem mais lento que editar um `.js`. É por isso que a divisão de trabalho faz sentido — **Frida para descobrir, módulo para operar**. ### Por que foi criado A motivação vem de trabalho real de análise de tráfego mobile. Num teste de verdade o app precisa se comportar **naturalmente ao longo do tempo**: abrir e fechar várias vezes, receber notificação, sincronizar em segundo plano, ser usado por alguém que não sabe o que é Frida. E o ambiente precisa ser **reproduzível** — reconfigurável por qualquer pessoa marcando checkboxes, sem terminal e sem comando decorado. O FlutterTap transforma um procedimento manual e frágil em **infraestrutura**. A seleção de apps por pacote existe justamente para isso: não é um interceptador global — ele fica inerte em todo app que você não selecionou, se descarregando da memória imediatamente, o que reduz tanto risco de efeito colateral quanto superfície de detecção. ## Como funciona Nenhuma das funções envolvidas é exportada, então elas precisam ser localizadas em tempo de execução, a cada processo: 1. O módulo é carregado em cada processo de app via Zygisk e verifica se aquele pacote está na lista de alvos — se não estiver, se descarrega imediatamente (zero footprint em apps não selecionados). 2. Numa thread de monitoramento, espera o `libflutter.so` ser carregado e faz o parsing dos segmentos ELF. 3. Procura as strings `"ssl_client"` e `"Socket_CreateConnect"` na memória e, a partir delas, resolve os endereços reais de `verify_cert_chain` e `GetSockAddr` **desmontando o código com Capstone** — a mesma biblioteca que o Frida usa por baixo. 4. Instala três hooks com Dobby: captura o `sockaddr` de destino, reescreve IP/porta para o proxy, e força o resultado "certificado válido". Detalhes de implementação, incluindo as decisões que divergem da abordagem original e por quê, estão em [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md). ## Compatibilidade | Item | Suporte | |---|---| | Android | 10 (API 29) a 17 (API 37) | | Arquiteturas | `arm64-v8a`, `x86_64` | | Root | Magisk, KernelSU, SukiSu Ultra, APatch | | Zygisk | Zygisk nativo do Magisk, Zygisk Next (**inclusive com o Zygisk Next Linker ativo**) ou NeoZygisk | Validado em hardware em dois ambientes deliberadamente distintos: - **OnePlus 5** — Android 10, Kitsune Magisk v27.2 + NeoZygisk 2.3 - **Pixel 8a** — Android 17, SukiSu Ultra + Zygisk Next 1.4.3, com o **Zygisk Next Linker** ligado > **Zygisk é obrigatório.** Root sozinho não basta: todo o mecanismo vive nos callbacks do Zygisk. No > Magisk, ative a opção "Zygisk" nas configurações. No KernelSU/SukiSu Ultra, instale o Zygisk Next ou o > NeoZygisk. O APatch já traz uma implementação compatível. > > A compatibilidade com o **Zygisk Next Linker** exigiu correções específicas. Se você mantém um módulo > Zygisk que quebra com esse recurso, a seção "Zygisk Next Linker compatibility" do > [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) documenta as três armadilhas encontradas (e uma pista > falsa que custou tempo), de forma reaproveitável. ## Instalação **Recomendado — via MMRL:** no [MMRL](https://mmrl.dev/), abra *Repositories → Add* e cole a URL do repositório abaixo. O FlutterTap instala em um toque e recebe notificação de atualização automaticamente: ``` https://raw.githubusercontent.com/script-or-script/FlutterTap-mmrl/main/json/modules.json ``` **Ou manualmente:** baixe os dois arquivos da [última release](../../releases/latest). 1. **Instale o módulo**: instale o `FlutterTap-