Skip to content

MASTG-TECH-0038: Correção

Fazer pequenas alterações no Android Manifest ou no bytecode geralmente é a maneira mais rápida de corrigir pequenos incômodos que impedem você de testar ou fazer engenharia reversa de um aplicativo. No Android, dois problemas em particular ocorrem com frequência:

  1. Você não consegue interceptar o tráfego HTTPS com um proxy porque o aplicativo emprega SSL pinning.
  2. Você não consegue anexar um depurador ao aplicativo porque o flag android:debuggable não está definido como "true" no Android Manifest.

Na maioria dos casos, ambos os problemas podem ser resolvidos fazendo pequenas alterações no aplicativo (também conhecido como patching) e, em seguida, reassinando e reempacotando-o. Aplicativos que executam verificações de integridade adicionais além da assinatura de código padrão do Android são uma exceção. Nesses casos, você também precisa corrigir as verificações adicionais.

O primeiro passo é descompactar e desmontar o APK com apktool:

apktool d target_apk.apk

Nota: Para economizar tempo, você pode usar a flag --no-src se quiser apenas descompactar o APK, mas não desmontar o código. Por exemplo, quando você só deseja modificar o Android Manifest e reempacotar imediatamente.

Exemplo de Patching: Desativando Certificate Pinning

O certificate pinning é um problema para testadores de segurança que querem interceptar a comunicação HTTPS por motivos legítimos. Fazer patching no bytecode para desativar o SSL pinning pode ajudar nisso. Para demonstrar a bypass do certificate pinning, percorreremos uma implementação em um aplicativo de exemplo.

Depois de descompactar e desmontar o APK, é hora de encontrar as verificações de certificate pinning no código-fonte Smali. Pesquisar no código por palavras-chave como "X509TrustManager" deve direcioná-lo para o caminho certo.

Em nosso exemplo, uma busca por "X509TrustManager" retorna uma classe que implementa um TrustManager personalizado. A classe derivada implementa os métodos checkClientTrusted, checkServerTrusted e getAcceptedIssuers.

Para bypass da verificação de pinning, adicione o opcode return-void à primeira linha de cada método. Esse opcode faz com que as verificações retornem imediatamente. Com essa modificação, nenhuma verificação de certificado é realizada e o aplicativo aceita todos os certificados.

.method public checkServerTrusted([LJava/security/cert/X509Certificate;Ljava/lang/String;)V
  .locals 3
  .param p1, "chain"  # [Ljava/security/cert/X509Certificate;
  .param p2, "authType"   # Ljava/lang/String;

  .prologue
  return-void      # <-- NOSSO OPCODE INSERIDO!
  .line 102
  iget-object v1, p0, Lasdf/t$a;->a:Ljava/util/ArrayList;

  invoke-virtual {v1}, Ljava/util/ArrayList;->iterator()Ljava/util/Iterator;

  move-result-object v1

  :goto_0
  invoke-interface {v1}, Ljava/util/Iterator;->hasNext()Z

Essa modificação quebrará a assinatura do APK, então você também terá que reassinar o arquivo APK alterado após reempacotá-lo.

Exemplo de Patching: Tornando um Aplicativo Depurável

Todo processo com depurador habilitado executa uma thread extra para lidar com pacotes do protocolo JDWP. Essa thread é iniciada apenas para aplicativos que têm o flag android:debuggable="true" definido no elemento <application> do arquivo de manifesto. Essa é a configuração típica de dispositivos Android enviados para usuários finais.

Ao fazer engenharia reversa de aplicativos, muitas vezes você terá acesso apenas à build de release do aplicativo alvo. Builds de release não são feitas para serem depuradas, esse é o propósito das debug builds. Se a propriedade do sistema ro.debuggable estiver definida como "0", o Android não permite JDWP e depuração nativa de builds de release. Embora isso seja fácil de contornar, você ainda provavelmente encontrará limitações, como a falta de pontos de interrupção de linha. No entanto, mesmo um depurador imperfeito ainda é uma ferramenta inestimável, pois poder inspecionar o estado de tempo de execução de um programa torna a compreensão do programa muito mais fácil.

Para converter uma build de release em uma build depurável, você precisa modificar um flag no arquivo Android Manifest (AndroidManifest.xml). Depois de descompactar o aplicativo (por exemplo, apktool d --no-src UnCrackable-Level1.apk) e decodificar o Android Manifest, adicione android:debuggable="true" a ele usando um editor de texto:

<application android:allowBackup="true" android:debuggable="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:name="com.xxx.xxx.xxx" android:theme="@style/AppTheme">

Mesmo que não tenhamos alterado o código-fonte, essa modificação também quebra a assinatura do APK, então você também terá que reassinar o arquivo APK alterado.

Aplicações de Patching em React Native

Se o framework React Native foi usado para o desenvolvimento, o código principal do aplicativo está localizado no arquivo assets/index.android.bundle. Este arquivo contém o código JavaScript. Na maioria das vezes, o código JavaScript neste arquivo é minificado. Usando a ferramenta JStillery, uma versão legível por humanos do arquivo pode ser obtida, permitindo a análise de código. A versão CLI do JStillery ou o servidor local deve ser preferido em vez de usar a versão online, caso contrário, o código-fonte é enviado e divulgado a terceiros.

A seguinte abordagem pode ser usada para corrigir o arquivo JavaScript:

  1. Descompacte o arquivo APK usando a ferramenta apktool.
  2. Copie o conteúdo do arquivo assets/index.android.bundle em um arquivo temporário.
  3. Use JStillery para embelezar e desofuscar o conteúdo do arquivo temporário.
  4. Identifique onde o código deve ser corrigido no arquivo temporário e implemente as alterações.
  5. Coloque o código corrigido em uma única linha e copie-o no arquivo original assets/index.android.bundle.
  6. Reempacote o arquivo APK usando a ferramenta apktool e assine-o antes de instalá-lo no dispositivo/emulador alvo.