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:
- Você não consegue interceptar o tráfego HTTPS com um proxy porque o aplicativo emprega SSL pinning.
- Você não consegue anexar um depurador ao aplicativo porque o flag
android:debuggablenã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-srcse 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:
- Descompacte o arquivo APK usando a ferramenta
apktool. - Copie o conteúdo do arquivo
assets/index.android.bundleem um arquivo temporário. - Use
JStillerypara embelezar e desofuscar o conteúdo do arquivo temporário. - Identifique onde o código deve ser corrigido no arquivo temporário e implemente as alterações.
- Coloque o código corrigido em uma única linha e copie-o no arquivo original
assets/index.android.bundle. - Reempacote o arquivo APK usando a ferramenta
apktoole assine-o antes de instalá-lo no dispositivo/emulador alvo.