Ofuscando código no Android

Marco Aurelio Silva
CoreShield
Published in
7 min readOct 15, 2020

A IDE Android Studio oferece ao desenvolvedor a possibilidade de ofuscar o código android, mas o que o desenvolvedor pode ganhar com isso? Porque o desenvolvedor deve ofuscar o código?

“Este artigo irá focar na relação entre o ofuscamento do código e o aumento do nível de segurança do seu aplicativo.”

Engenharia reversa

Quando você publica seu aplicativo na loja Play Store ou disponibiliza o arquivo .apk para instalação, existem aplicativos que fazem a engenharia reversa ou descompilação do seu aplicativo. Desta forma o seu código fonte fica exposto, facilitando a vida do invasor que a partir do código fonte ou parte dele.

Ofuscamento do código

O código ofuscado dificulta a engenharia reversa, removendo instruções não utilizadas, minificando classes, métodos e campos. Segue abaixo parte do código de um aplicativo compilado com ofuscamento e resultado da engenharia reversa deste aplicativo:

Resultado da engenharia reversa de uma classe ofuscada

“Mantenha em mente que o R8 foi feito para trabalhar com as regras existentes do ProGuard, então provavelmente você não precisará de tomar nenhuma ação diferente para se beneficiar do R8”

A partir o Plug-in Do Android para Gradle 3.4.0, o ProGuard não é mais usado para otimizar o código em tempo de compilação, o R8 passa a executar este papel por default, executando algumas tarefas como:

  • Redução do código (ou tree shaking): neste passo as classes, campos, métodos, e atributos não utilizados no código, são detectados e removidos com segurança, além de remover partes não utilizadas no código, o R8 também remove as dependências de biblioteca do que não esta sendo usado pelo App. Isto torna esta ferramenta valiosa para evitar que o App supere o limite de 64K de referências.

Mas vamos nos concentrar na ofuscação do código, que encurta o nome de classes e membros, o que resulta em arquivos DEX menores.

DEX???

Os programas Android são compilados em arquivos (Dalvik Executable), que por sua vez são compactados em um único arquivo apk no dispositivo. Arquivos DEX podem ser criados traduzindo automaticamente aplicativos compilados escritos na linguagem de programação Java. Se quiser saber mais sobre o formato DEX siga este link: https://source.android.com/devices/tech/dalvik/dex-format.html

Colocando a mão na massa

Ativando redução, ofuscação e otimização

Por padrão a ofuscação, redução e otimização do código não são ativadas na criação de um novo projeto no Android Studio. Isso ocorre porque essas otimizações em tempo de compilação aumentam o tempo de compilação do projeto e podem introduzir bugs se você não personalizar suficientemente o código a ser mantido.

Portanto deixe para ativar a estas tarefas na versão final do App antes de publica-lo. Para ativar a redução, ofuscação e otimização, inclua o seguinte no seu arquivo build.gradle no projeto

android {
buildTypes {
release {
// Habilita ofuscar, otimizar e reduzir o
//código apenas na versão de produção do App
minifyEnabled true
// Habilita o recurso de redução do código
// Android Gradle plugin.
shrinkResources true
// Inclui as regras default do proguard
proguardFiles getDefaultProguardFile(
'proguard-android-optimize.txt'),
'proguard-rules.pro'
}
}
...
}

Personalizando o código a ser mantido

O arquivo de regras padrão proguard-android-optimize.txt orienta o R8 no sentido da remoção de código não utilizados pelo App, contudo em algumas situações o R8 pode remover códigos que seu App precisa. Alguns exemplos de código qu epodem ser removidos incorretamente incluem:

  • Quando seu app chama um método da Java Native Interface (JNI)
  • Quando seu aplicativo procura código no momento da execução (por exemplo, com reflexão)

Os testes no App geralmente revelam os erros causados por remoção indevida de código, mas você pode gerar um relatório de código removido também, não vou entrar neste tópico neste artigo, caso tenha interesse por favor acesse o gerando um relatório do código removido na documentação oficial do Android.

Para corrigir erros e forçar o R8 a manter determinados códigos, basta adicionar uma linha -keep no arquivo de regras do ProGuard.

-keep public class MyClass

Como alternativa, a anotação @keep pode ser adicionada no seu código para manter uma classe intacta, se você adicionar a anotação @keep a um método ou um campo manterá os nomes intactos. Esta anotação estará disponíve quando você estiver usando a biblioteca AndroidX Annotations.

Arquivos de configuração do R8

O R8 usa arquivos de regras do ProGuard para modificar o comportamento padrão e entender melhor a estrutura do app, como as classes que servem como pontos de entrada para o código do app. Embora você possa modificar alguns arquivos de regras, algumas regras podem ser geradas automaticamente por ferramentas de tempo de compilação, como AAPT2, ou herdadas das dependências da biblioteca do app. A tabela abaixo descreve as fontes dos arquivos de regras do ProGuard que o R8 usa.

  • Biblioteca AAR(Android Archive), a estrutura de uma biblioteca android é a mesma de um módulo de aplicativo para Android. Uma biblioteca AAR pode conter tudo que é necessário para criar um app, como código fonte, manifesto do Android e arquivos de recursos. A diferença é que ao ser compilada ela não gera um arquivo APK e sim um arquivo AAR que pode ser usado como dependência de um módulo de aplicativo Android.

Depuração de erros em um código ofuscado

Como foi descrito no início deste artigo, a ofuscação é reduz o tamanho do app encurtando o tamanho dos nomes das classes, métodos e campos. Abaixo um exemplo de código ofuscado usando o R8.

androidx.appcompat.app.ActionBarDrawerToggle$Delegate Provider -> a.a.a.b:
androidx.appcompat.app.AlertController -> androidx.appcompat.app.AlertController:
android.content.Context mContext -> a
int mListItemLayout -> O
int mViewSpacingRight -> l
android.widget.Button mButtonNeutral -> w
int mMultiChoiceItemLayout -> M
boolean mShowTitle -> P
int mViewSpacingLeft -> j
int mButtonPanelSideLayout -> K

A ofuscação renomeia diferentes partes do seu código, isso pode impossibilitar a visualização de stack traces (Um stack trace é gerado sempre que seu app falha devido a um erro ou uma exceção e pode ser visualizado no Logcat do Android Studio), porque os nomes das classes e métodos podem ter sido alterados. Alem de renomear, o R8 pode trocar número de linhas presentes nos stack traces. Para solucionar esta questão, o R8 cria um arquivo chamado mapping.txt sempre que é executado, este arquivo contém os nomes originais das classes, métodos e campos, além de informações para mapear os números de linha de volta para os números originais. O R8 salva este arquivo no diretório <module- name>/build/outputs/mapping/<build-type>/.

Cuidado: o arquivo mapping.txt gerado pelo R8 é substituído sempre que você cria seu projeto. Por esse motivo, salve uma cópia sempre que publicar uma nova versão. Manter uma cópia do arquivo mapping.txt para cada build de lançamento permitirá depurar problemas caso um usuário envie um stack trace ofuscado de uma versão anterior do app.

Para converter um stack trace ofuscado em um que você possa entender, use o script ReTrace que faz parte do pacote do ProGuard

Otimizações mais agressivas

O r8 possui outras otimizações que não estão ativadas por default. Para ativar outras otimizações , inclua o seguinte no arquivo gradle.properties:

android.enableR8.fullMode=true

Otimizações extras fazem o R8 se comportar de maneira diferente do ProGuard, isso pode exigir que você inclua mais regras do ProGuard para evitar problemas em tempo de execução. Por exemplo se estiver usando Reflexão (Reflexão é um recurso da API Java que possibilita aos aplicativos o acesso e a modificação do comportamento de aplicações que estão rodando na Java Virtual Machine. Uma classe pode acessar outras classes em tempo de execução, sem conhecer sua definição no momento da compilação.). Por padrão, o R8 considera que você pretende examinar e manipular objetos dessa classe no tempo de execução, mesmo que o código não faça isso, e mantém automaticamente a classe e o inicializador estático correspondente.

No entanto, ao usar o “modo completo”, o R8 não faz essa suposição, e, se o R8 declarar que seu código nunca usa a classe durante o tempo de execução, ele removerá a classe do DEX final do app. Ou seja, se você quiser manter a classe e o inicializador estático correspondente, precisará incluir uma regra keep no arquivo de regras.

Referência: documentação oficial Android.

--

--