Skip to content

MASTG-TECH-0112: Engenharia Reversa de Aplicações Flutter

O Flutter é um SDK de UI de código aberto do Google para a criação de aplicativos compilados nativamente para dispositivos móveis, web e desktop a partir de uma única base de código. Dart, a linguagem de programação utilizada no Flutter, é fundamental para seu funcionamento, oferecendo recursos de linguagem e otimizações de desempenho que possibilitam o desenvolvimento eficiente de aplicativos multiplataforma de alta qualidade.

Um snapshot do Dart é uma representação pré-compilada de um programa Dart que permite tempos de inicialização mais rápidos e execução eficiente. O desenvolvimento de aplicativos Flutter concentra-se no snapshot AOT (Ahead-of-Time), utilizado em todos os aplicativos móveis Flutter.

Existem desafios significativos na engenharia reversa de snapshots AOT do Dart devido a vários fatores:

  1. Código Assembly Distintivo: O código assembly gerado utiliza registradores, convenções de chamada e codificação de inteiros únicos, complicando a análise.
  2. Informações Sequenciais de Classes: As informações sobre cada classe no snapshot AOT do Dart devem ser lidas sequencialmente, impedindo acesso aleatório e tornando demorada a localização de classes específicas.
  3. Falta de Documentação: O formato do snapshot Dart carece de documentação abrangente e evoluiu ao longo do tempo, aumentando a complexidade.
  4. Ofuscação e Otimização: O processo de build do Flutter pode incluir técnicas de ofuscação e otimização que dificultam os esforços de engenharia reversa.

Devido a esses desafios, a análise eficaz de aplicativos Flutter requer ferramentas e métodos especializados.

Usando o Blutter

Para usar Blutter, você precisa:

  1. Extrair o APK: Descompacte o arquivo APK e localize o arquivo libapp.so.
  2. Executar o Blutter: Execute o Blutter com o caminho para o arquivo libapp.so e especifique um diretório de saída.
python3 blutter.py caminho/para/app/lib/arm64-v8a diretorio_saida

O Blutter gera vários arquivos:

  • asm/*: Arquivos assembly com símbolos.
  • blutter_frida.js: Um template de script Frida para instrumentar o aplicativo.
  • objs.txt: Um dump completo e aninhado de objetos do pool de objetos.
  • pp.txt: Todos os objetos Dart no pool de objetos.

Os arquivos assembly em asm/* contêm funções reconstruídas com nomes, facilitando o rastreamento da lógica do aplicativo. Aqui está um trecho de uma função main:

  static _ main(/* Sem informações */) async {
    // ** endereço: 0x5961e0, tamanho: 0x230
    // 0x5961e0: EnterFrame
    //     0x5961e0: stp             fp, lr, [SP, #-0x10]!
    //     0x5961e4: mov             fp, SP
    // 0x5961e8: AllocStack(0x28)
    //     0x5961e8: sub             SP, SP, #0x28
    // 0x5961ec: SetupParameters()
    //     0x5961ec: stur            NULL, [fp, #-8]
    // 0x5961f0: CheckStackOverflow
    //     0x5961f0: ldr             x16, [THR, #0x38]  ; THR::stack_limit
    //     0x5961f4: cmp             SP, x16
    //     0x5961f8: b.ls            #0x596400
    // 0x5961fc: InitAsync() -> Future<void?>
    //     0x5961fc: ldr             x0, [PP, #0x80]  ; [pp+0x80] TypeArguments: <void?>
    //     0x596200: bl              #0x3a5d48
    // 0x596204: r0 = ensureInitialized()
    //     0x596204: bl              #0x570d8c  ; [package:flutter/src/widgets/binding.dart] WidgetsFlutterBinding::ensureInitialized
    // 0x596208: r0 = init()
    //     0x596208: bl              #0x59a98c  ; [package:get_secure_storage/src/storage_impl.dart] GetSecureStorage::init
    // 0x59620c: mov             x1, x0
    // 0x596210: stur            x1, [fp, #-0x10]
    // 0x596214: r0 = Await()

Embora esse código não seja tão fácil de entender quanto o código Java descompilado típico, muitas informações ainda estão disponíveis. No topo, podemos ver o nome da função (main), bem como a localização da função no binário original libapp.so. As diferentes instruções de salto (bl) são acompanhadas por informações de símbolos, facilitando a compreensão do que o código está fazendo. Por exemplo, podemos ver que o aplicativo primeiro garante que as vinculações do Flutter estejam corretamente inicializadas (WidgetsFlutterBinding::ensureInitialized), seguido pela inicialização do plugin get_secure_storage (GetSecureStorage::init).