INTRODUÇÃO
Dando continuidade aos artigos sobre pentesting mobile, vamos discorrer sobre engenharia reversa de aplicativos
. Existem infinitas formas de fazer engenharia reversa de um aplicativo, nenhuma delas é a mais correta ou definitiva, para cada caso, uma das formas funcionará melhor tendo como princípio a necessidade.
A engenharia reversa é uma fase importante em um teste, não só pela possibilidade de ver o código-fonte da aplicação, mas pela possibilidade de alterar seu código e conseguir efetuar o bypass de proteções. Além de fornecer entendimento de como e onde a aplicação armazena dados, como é sua comunicação com o back-end e vazar possíveis endereços como URLs e IPs.
ANÁLISE ESTÁTICA x DINÂMICA
Existem basicamente duas frentes no que diz respeito à engenharia reversa de aplicativos, a análise estática
e a análise dinâmica
.
Na análise estática, a aplicação é analisada sem a necessidade de estar em execução, como um code-review
analisando o manifest, as bibliotecas e a descompilação dos arquivos .dex
.
Na análise dinâmica, a aplicação é analisada durante sua execução, medindo e enumerando seus comportamentos e fluxos. São utilizadas técnicas de debugging, trace de chamadas de API e syscalls, análise de variáveis em tempo de execução, entre outros.
É importante salientar, que qualquer uma das técnicas usadas individualmente se torna incompleta, uma vez que são complementares.
CÓDIGO SMALI
O código smali
é o mais baixo nível de um aplicativo Android. Quando um aplicativo é desenvolvido em Java, uma linguagem de alto nível, este código precisa ser compilado de um apk de forma que a máquina virtual de Dalvik consiga o interpretar.
Este processo de compilação gera os códigos em baixo nível para ser interpretados pela máquina. A este código em baixo nível chamamos de smali.
O código abaixo, mostra um exemplo de smali que imprime um “Hello World!”.
class public h41stur;
.super Ljava/lang/Object;
.method public static main([Ljava/lang/String;)V
.registers 2
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
const-string v1, "Hello World!"
invoke-virtual{v0,v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
return-void
.end method
Para entendermos o processo que chegou a este código smali, podemos criar o script em Java e compilá-lo para se tornar um arquivo .dex
que possa ser executado por uma máquina de Dalvik.
O código abaixo, mostra a classe Java correspondente ao “Hello World”.
1
2
3
4
5
public class h41stur{
public static void main(String[] args){
System.out.println("Hello World!");
}
}
Para que este código seja interpretado pelo binário java
precisamos compilá-lo com o javac
.
Como podemos ver, após compilar a classe, com o javac, um arquivo “h41stur.class” foi criado, a partir disto, foi possível executar o script com o java imprimindo a mensagem.
Neste ponto, a classe java está compilada e executável pelo java em uma máquina Linux, porém, o sistema operacional Android não interpreta Java, conforme já vimos, e sim smali. Para que possamos gerar o código em baixo nível para o Android interpretar, podemos utilizar a ferramenta d8
que faz parte do build-tools
do Android Studio
.
Esta ferramenta compila o bytecode Java para o bytecode DEX que pode ser executado pelo Android.
Ao compilar o arquivo .class com o d8, temos como resultado o arquivo “classes.dex” que contém o bytecode interpretável pela máquina de Dalvik.
Para comprovarmos a eficácia, podemos transferir o arquivo .dex gerado para um dispositivo através do comando adb push
e executá-lo dentro do Android com o comando dalvikvm -cp classes.dex h41stur
, onde:
- dalvikvm: é a máquina virtual Dalvik, responsável por interpretar os arquivos .dex;
- -cp classes.dex: é o “class path” ou o caminho onde está armazenada a classe;
- h41stur: é o nome da classe criada para ser interpretada.
A imagem abaixo mostra o resultado da execução.
TÉCNICAS DE ENGENHARIA REVERSA
A partir deste momento, podemos começar a “desmontar” um aplicativo de várias formas. Como teste, vamos utilizar o aplicativo EVABS (Extremaly Vulnerable Android Labs).
Unzip e Backsmali
Todo aplicativo nada mais é que um pacote compactado de arquivos e bibliotecas. Sendo assim, é possível descompactá-lo com o próprio unzip
.
Descompactando desta forma, não se tem o aplicativo de forma legível para análise, mas é possível utilizar outras ferramentas para auxiliar em sua análise.
Após descompactar o APK, podemos ver que alguns arquivos e diretórios são exibidos, conforme mostrado na imagem abaixo.
Estes diretórios e arquivos contém todos os recursos do aplicativo, incluindo imagens, códigos, bibliotecas, incluindo o arquivo `classes.dex”, porém todos os arquivos estão em bytecode e ilegíveis para nós.
Para fazermos o reversing do arquivo classes.dex, podemos utilizar a ferramenta backsmali, bastando passar argumento de disassembly
e o path do arquivo .dex e um output conforme mostrado abaixo.
Conforme podemos ver, o diretório “reversing” foi criado, pois foi o nome passado para o output.
Se caminharmos para o diretório raiz do aplicativo, podemos ver todos os scripts smali, conforme mostrado abaixo.
E ao abrirmos qualquer um destes arquivos, teremos o código smali legível para interpretação e edição.
Muitas vezes, somente de observar o código smali, é possível determinar comportamentos importantes sobre a aplicação, credenciais hardcoded e diversas outras informações.
Como, por exemplo, no arquivo DBLeak.smali
na linha 63, podemos ver a criação de uma tabela de usuários, e logo abaixo o insert de usuários e senhas, conforme mostrado abaixo.
Para melhor entendimento sobre os operadores Dalvik, segue uma excelente referência.
Após a análise do aplicativo, caso alguma alteração tenha sido feita em um arquivo smali, é possível compilá-los novamente para empacotar em um APK novamente, para isso, basta apagar o arquivo classes.dex
original, e utilizar a ferramenta smali para compilar o diretório gerado pelo backsmali.
Após a compilação dos arquivos smali, é possível compactar os arquivos e diretórios novamente em formato de APK com a ferramenta zip
conforme mostrado abaixo.
E um novo aplicativo foi gerado, conforme mostrado abaixo.
É importante ressaltar, que o aplicativo recompactado ainda não está pronto para ser instalado, pois como vimos em posts anteriores, um aplicativo precisa estar assinado
para que o Android o reconheça como válido, mais a frente iremos explorar a assinatura de um APK.
Apktool
Como já vimos a descompilação de um aplicativo de forma manual, podemos utilizar uma ferramenta automatizada que agiliza o processo.
O apktool é uma junção de várias ferramentas em um set que executa comandos de forma sequencial para descompactar e descompilar e/ou compilar e compactar um aplicativo.
Conforme podemos ver, o apktool
já conta até mesmo com o smali e backsmali em seu kit.
A imagem abaixo mostra o processo de descompilação de um aplicativo.
E assim como da forma manual, todos os arquivos smali estão disponíveis para leitura e edição, conforme mostrado abaixo.
O processo de recompilação é tão simples quanto o anterior, bastando utilizar o parâmetro b
(build) para o apktool.
Assim como da forma manual, é importante ressaltar, que o aplicativo recompactado ainda não está pronto para ser instalado, pois como vimos em posts anteriores, um aplicativo precisa estar assinado
para que o Android o reconheça como válido, mais a frente iremos explorar a assinatura de um APK.
Assinando um APK
Até o momento vimos uma forma de descompactar e descompilar um aplicativo para ser possível obter seu código smali e alterá-lo, caso possível, assim como recompilá-lo e compactá-lo para gerar um novo APK adulterado.
Porém, este processo remove a assinatura original do aplicativo, e como vimos em posts anteriores, o Android só reconhece um aplicativo como válido, caso este esteja assinado com um certificado.
Para fins de teste de um aplicativo, é possível assinar o APK adulterado com um certificado autoassinado. É importante salientar, que um aplicativo assinado desta forma, não é reconhecido pela loja oficial, portanto, não é possível subir um APK autoassinado na loja oficial
O processo para tal, envolve uma série de passos e ferramentas. Não existe uma única forma de assinar um aplicativo, portanto uma delas será explorada no decorrer do post.
KeyStore
O primeiro passo no processo de assinatura é a criação de uma KeyStore
. O KeyStore, como o próprio nome sugere, é um arquivo que serve como um “banco de dados” que contém informações criptografadas e informações de como criptografar. Em resumo é uma área de armazenamento de chaves.
O processo de criação do KeyStore já envolve também a criação de um par de chaves. A própria suite ADB
contém a ferramenta keytool
que tem como função a administração de certificados. Abaixo o comando para criação de uma KeyStore:
1
keytool -genkey -v -keystore h41stur.keystore -alias h41stur -keyalg RSA -keysize 2048 -validity 10000
Onde:
- -genkey: é o argumento para gerar um par de chaves;
- -v: argumento para aumentar a verbosidade;
- -keystore: é o argumento para indicar o nome da KeyStore a ser gerada;
- -alias: é o argumento para dar um alias para o certificado;
- -keyalg: é o argumento para indicar qual algoritmo de criptografia será usado para o certificado;
- -keysize: é o argumento para passar o tamanho da chave em bits;
- -validity: é o argumento para informar a validade em dias do certificado.
Após executar o comando, a ferramenta vai pedir uma senha para o certificado e mais algumas informações (que não tem tanta importância num certificado autoassinado para testes), e gerará o KeyStore com o nome indicado, conforme mostrado abaixo.
É possível listar as chaves armazenadas na KeyStore com o argumento -list
conforme mostrado abaixo.
Caso seja necessário, também é possível extrair os certificados armazenados na KeyStore, bastando indicá-la juntamente com o alias do certificado, com o comando abaixo.
1
keytool -export -rfc -keystore h41stur.keystore -alias h41stur -file h41stur.cer
Onde:
- -export: é o argumento para exportar um certificado;
- -rfc: é o argumento para indicar que a saída será no formato RFC;
- -keystore: é o argumento para passar o caminho para a KeyStore;
- -alias: é o argumento para passar o alias do certificado a ser extraído de dentro da KeyStore;
- -file: é o argumento para informar o arquivo de saída do certificado exportado.
Ao executar o comando, a senha do certificado será solicitada e o arquivo será gerado, conforme mostrado abaixo.
JarSigner
Com a KeyStore e o par de chaves criados, é possível assinar o aplicativo adulterado com a ferramenta jarsigner
existente na build-tools
do Android Studio
. O comando abaixo mostra o processo de assinatura.
1
jarsigner -sigalg SHA1withRSA -digestalg SHA1 -keystore h41stur.keystore EVABSv4_rebuild.apk h41stur
Onde:
- -sigalg: é o argumento para informar o algoritmo utilizado na assinatura;
- -digestalg: é o argumento para informar o algoritmo utilizado na compilação do certificado;
- -keystore: é o argumento para informar o caminho da KeyStore;
Estes argumentos devem ser seguidos do caminho do APK a ser assinado e do alias do certificado.
Ao executar o comando, a senha do certificado será solicitada e o aplicativo será assinado com o certificado, conforme mostrado abaixo.
É possível verificar a assinatura do APK com o argumento -verify
conforme mostrado abaixo.
Após este processo, o aplicativo ainda não está pronto, ainda é necessário fazer o alinhamento do arquivo compactado.
ZipAlign
O zipalign
é uma ferramenta que pode ser encontrada na build-tools
do Android Studio
. De acordo com sua documentação, esta ferramenta certifica que todos os arquivos descompactados no arquivo sejam alinhados em relação ao seu início, permitindo que estes arquivos possam ser acessados via mmap, eliminando a necessidade de copiar esses dados na RAM e reduzindo o uso de memória do seu aplicativo.
O comando abaixo mostra o processo de alinhamento do arquivo.
1
zipalign -v 4 EVABSv4_rebuild.apk EVABSv4_alinhado.apk
Onde:
- -v: é o argumento para aumentar a verbosidade;
- 4: é o argumento para informar o alinhamento em bytes, no caso o
4
informa um alinhamento em32 bits
.
Após este processo, o aplicativo adulterado está pronto para ser instalado em um dispositivo físico ou emulado.
Jadx-gui
Nem sempre a engenharia reversa precisa seguir até o nível mais baixo de código e bibliotecas, em uma situação de code review, a conversão para o código-fonte Java se torna suficiente para entendimento do aplicativo.
O jadx-gui é um set de ferramentas de linha de comando e GUI que produzo códigos Java a partir de APKs e Android Dex.
Seu uso é simples, bastando chamar a ferramenta seguida do path do aplicativo a ser descompilado. A imagem abaixo mostra o aplicativo EVABS (Extremaly Vulnerable Android Labs) descompilado.
A partir desta fase, é possível fazer buscas no aplicativo por termos e funções-chave.
É importante ressaltar, que na própria página da ferramenta, é explícito que sua eficácia não é de 100%, pois alguns pontos do aplicativo podem não ser descompilados.
Dex2jar
O dex2jar faz parte de uma suite de ferramentas que tem por objetivo trabalhar com extensões .dex
os convertendo em classes Java.
Especificamente o dex2jar
converte um aplicativo ou um código DEX em classes Java compactadas em um único arquivo .jar
servindo com uma alternativa na descompilação de um aplicativo, uma vez que o resultado possa não ser 100% satisfatório entre uma ferramenta e outra.
Sua sintaxe é simples, bastando invocar a ferramenta seguida do path do aplicativo, após o processo, um arquivo com o sufixo -dex2jar.jar
é gerado, conforme mostrado abaixo.
Para leitura do arquivo gerado é preciso utilizar uma aplicação que leia as classes Java, nestes artigos, usaremos o jd-gui, uma ferramenta do Java Decompiler project
que tem por objetivo desenvolver ferramentas para descompilar e analisar o Java byte-code.
Para efetuar a leitura, basta invocar o jd-gui seguido do path da classe gerada com o dex2jar. A imagem abaixo mostra como a leitura fica visível.
Enjarify
O enjarify assim como o dex2jar, é uma ferramenta para traduzir arquivos .dex e APKs em código Java, o scritp em Python
serve como mais uma alternativa para descompilação de aplicativos.
Sua sintaxe também é simples, bastando invocá-lo seguido do path do arquivo .dex ou APK. A imagem abaixo mostra este processo.
Ao final do processo, o enjarify também gera as classes Javas compactadas em um arquivo .jar, que pode ser aberto com o jd-gui.
Mobile Security Framework (MobSF)
O Mobile Security Framework (MobSF) é uma ferramenta “all-in-one” que faz todos os processos descritos neste post, desde a descompilação a code review. Além de permitir a análise tanto estática quanto dinâmica de um aplicativo.
Assim como as demais ferramentas, pode não trazer 100% de acerto, porém sua interface amigável e detalhada facilita a interpretação.
O demonstrativo abaixo, retirado do próprio GitHub MobSF mostra o processo de engenharia reversa.
CONCLUSÃO
Neste artigo, foi possível obter um overview do processo de engenharia reversa e adulteração de um aplicativo Android, foram vistas várias técnicas que podem se complementar, uma vez que nenhuma delas pode ser elencada como a definitiva.
Nos próximos posts, entenderemos o processo de hooking
de um aplicativo.