Segurança em Aplicações Android
O ficheiro de uma aplicação Android é chamado de Android Package (apk), e não é mais que um ficheiro Zone Information Protocol (ZIP) comprimido.
Começamos com algumas breves questões:
- É possível descomprimir um apk?
Sim.- Então, também é possível ler o código-fonte de um apk?
Sim.- Os apks são reversíveis através de engenharia reversa?
Sim.- Isso quer dizer que, é possível encontrar dados sensíveis como, por exemplo, palavras-passe e Application Programming Interface (API) keys, ao longo do código?
Sim.- É possível construir um apk totalmente seguro — à prova de bala?
Este artigo tem o objetivo de passar alguns procedimentos de forma a que qualquer developer, ou fulano com conhecimentos básicos sobre Android, consiga auditar sua própria aplicação antes que esta seja publicada e maliciosamente explorada.
E respondendo à última questão: ”—Nim”.
Caça aos Dados Sensíveis
Não existe uma aplicação, sistema, infraestrutura ou até ecossistema à prova de bala e totalmente seguro. Isso deve-se a muitos fatores, p.ex., um desenho deficiente da arquitetura, bugs cometidos em sede de desenvolvimento, software desatualizado, vulnerabilidades zero-day attack, fuga de informação, entre outros. Quantos mais parâmetros são enumerados maior se torna o horizonte de ataque. Dito de uma forma mais jocosa, o objetivo da segurança da informação é então dificultar o processo de obtenção de determinado valor no seu estado mais cru. Nesse caso, qualquer fulano que tente explorar e obter informação do sistema, em vez de demorar 30 minutos vai demorar, dias, semanas, talvez anos, extingue-se o sol e o bisneto do bisneto do terceiro filho tenta alcançar o objetivo do tataravô. Confuso? É o objetivo!
O principal segredo para a segurança nasce durante o desenho da arquitetura do sistema. É necessário clarificar a arquitetura, as assunções do sistema, os intervenientes, e depois, começar o puzzle. No caso das aplicações Android existem alguns tópicos que podem e devem ser levantados logo no ínicio do desenvolvimento, p.ex:
- A aplicação irá ser distribuída por centenas de utilizadores, logo o código-fonte vai estar disponível nos smartphones dos utilizadores.
- No caso de comunicação com serviços third-party, é importante pensar como e onde irão ser guardadas as palavras-passe, secret keys e tokens de acesso a esses serviços.
- Consoante o tipo de aplicação, nativa ou não-nativa, é importante incrementar a segurança na forma como o código da aplicação é distribuído na store.
Antes de avançar é importante abordar uns breves conceitos sobre a forma como uma aplicação Android opera e qual o ciclo de desenvolvimento até à geração do apk final.
APK e o Android
Depois do download do apk da store, a aplicação é instalada no smartphone. A forma como o sistema foi desenhado permite:
- Cada apk correr numa Virtual Machine (VM) totalmente isolada;
- Isolamento de processo (UID), i.e., cada processo tem um único ID. Cada processo tem o seu endereçamento de memória, e apenas o processo com o ID XPTO tem acesso à memória XPTO (shared preferences);
- Não há recursos partilhados porque cada shared preference só pode ser acedida pelo processo com o ID XPTO (o owner); e
- Maior proteção do kernel.
Dalvik Virtual Machine Vs. Java Virtual Machine
A forma como um apk é interpretado pelo sistema Android é muito semelhante à forma como uma Java Virtual Machine (JVM) interpreta um ficheiro .jar. A VM do Android chama-se Dalvik Virtual Machine (DVM).
(Existe também uma outra runtime denominada ART e que é bastante mais rápida que a DVM. Pode ser consultada mais informação aqui.)
Existem ferramentas de compilação como o Gradle e o Apache Ant que automatizam o processo de geração da aplicação final, o apk, no caso do Android. De notar que, foram gerados pelo caminho os ficheiros .class, que são os ficheiros com o código Java já compilado. Adicionalmente para que um apk rode numa DVM, é necessário também codificar esses ficheiros em .dex files.
O processo é um pouco mais elaborado do que aquele que foi apresentado acima (ver abaixo).
Os ficheiros Resources, Assets e Manifest não são sequer codificados pelo compilador Java (javac) nem pelo dx. Estes ficheiros são apenas compactados. Está a lembrar-se do ZIP e do UnZIP?
Neste ponto é importante reter o seguinte:
- Os Resources e Assets armazenam outros ficheiros como imagens, fonts, HTML, CSS e Javascript (este último é muito importante).
- Eles não são compilados (codificados em byte code), apenas são comprimidos junto dos ficheiros .class e .dex.
- A grosso modo, um apk é mesmo um conjunto de ficheiros do tipo: Resources, Assets, Manifest + ficheiros compilados (.class e .dex).
Aplicações Nativas vs. Não-Nativas
Nativa
De uma maneira breve, uma aplicação nativa comunica diretamente com o SO através de um API. O SO é quem gere a comunicação com os componentes de hardware.
Não-Nativa (Híbrida)
Uma aplicação não-nativa, por exemplo, híbrida, é uma aplicação web (por exemplo, um website) a rodar num wrapper Java Android. De uma forma ainda mais simples: — Consiste numa aplicação Android, com um web-browser, a invocar uma página HTML. É claro que existe aqui um abuso de linguagem, mas é para o leitor perceber a big picture de uma forma mais clara e leve. Para que essa página HTML concretize chamadas ao SO, é necessário, via Javascript, invocar rotinas codificadas algures no código Java.
Neste caso, chamadas a funções de APIs nativas são executadas através de Javascript. Ou seja, a maior parte da lógica computacional está presente nos ficheiros Javascript e que não são compilados, apenas comprimidos.
Normalmente este tipo de aplicações híbridas são desenvolvidas com recurso a ferramentas disponíveis no mercado que automatizam e aceleram o seu desenvolvimento, Cross-platform Tools (CPTs), como o Apache Cordova ou o Phonegap, e Cross-platforms to Mobile Development (CPMDs), frameworks web-based que permitem criar uma aplicação através de ponta e clique em minutos.
Engenharia Reversa (ER)
Podemos desenhar dois caminhos distintos antes iniciar o trabalho:
- Para aplicações híbridas não é necessário decompilar os ficheiros (.dex e .class), eles apenas vão conter os plugins e libs dos CPTs. É necessário fazer somente o unzip do apk porque o código está nos Assets.
- Para aplicações nativas é necessário percorrer todo o fluxo de Engenharia Reversa (ER).
Segue abaixo o fluxo de ER para aplicações Android.
O processo de ER de aplicações Android começa sempre da mesma forma:
- Download do apk do telemóvel ou da store;
- unzip do apk
- Normalmente, para aplicações híbridas, basta o unzip.
- Para aplicações nativas é necessário:
- Decompilar o ficheiro classes.dex
- Decompilar os ficheiros *.class para source-code
- Analisar o source-code.
1. Download de uma Aplicação Aleatória da Store
De forma a comprovar o que foi dito ao longo do artigo, foi descarregada uma aplicação aleatória da Play Store. Para isso, basta entrar na página da Play Store e selecionar uma aplicação para descarregar.
https://play.google.com/store/apps/details?id=ID
Através do SDK do Android, é possível puxar o apk diretamente do smartphone.
adb connect xxx.xxx.xxx.xxx:5555 adb shell pm list packages adb pull /data/app/exemplo.app.pt.apk
Para os mais impacientes, podem ser usadas algumas ferramentas online, p.ex.:
2. Unzip do apk
Em seguida segue o unzip do apk e a respetiva estrutura dos ficheiros.
unzip apk_name.apk
É possível observar dois ficheiros importantes:
- assets, onde vai estar o código de aplicações híbridas (código Javascript); e
- classes.dex, o ficheiro com o source-code em Java.
3. Diretoria Assets
Em aplicações híbridas, como é o caso de apps construídas com o Apache Cordova ou Phonegap, dentro desta diretoria está a estrutura tipicamente de um website.
Analisando o ficheiro m_index.html é possível visualizar uma chamada a um ficheiro Javascript.
<script src="js/app.js"></script>
Analisando o ficheiroapp.js, na diretoria js, é possível encontrar dados sensíveis, secret keys para comunicar com third party applications, como é o exemplo da Amazon Web Services, Linkedin, ou outro tipo de APIs públicas.
linkedIn: { callbackUri: "https://####", clientId: "####rqbt###rr", clientSecret: "####M85####X9" }
Como uma API não pública necessita de uma API key e uma secret key para que uma comunicação entre o cliente e o servidor seja estabelecida, esses dados são guardados, preferencialmente, em ficheiros do tipo Javascript em aplicações híbridas. Como observado, isto é um problema grave de segurança, uma vez que um simples unzip do apk fez-nos chegar aos dados sensíveis.
É muito provável que existam bots na Internet a efetuar este tipo de processos de uma forma autónoma.
4. Analisar o Source-code Java da Aplicação – ER
Para uma análise in-depth, é necessário seguir o processo de ER mencionado no diagrama mais acima. Para tal, é necessário usar algumas ferramentas disponíveis na Internet, p.ex., dex2jar e Java Decompiler, e efetuar os seguintes passos:
- d2j-dex2jar apk_name.apk (reverter .dex em .jar)
- Abrir o ficheiros com um Java Decompiler
Depois de aberto, é possível visualizar o source-code da aplicação.
Como se trata de uma aplicação não-nativa, apenas é feita a invocação do web wrapper, tal como foi mencionado mais acima. O wrapper invoca simplesmente os resources (HTML).
Em aplicações totalmente nativas, seria possível encontrar todas as atividades da app, e procurar recursivamente por palavras-chave dentro do projeto. Dessa maneira, é viável identificar dados sensíveis.
Vantagens de Desvantagens de Aplicações Híbridas
As aplicações híbridas são excelente do ponto de vista da rapidez de desenvolvimento. Torna-se rápido o seu desenvolvimento porque os CPTs já fornecem um conjunto de plugins muito variados. Outra grande vantagem é que, normalmente, o desenvolvimento é cruzado, i.e., multiplataforma, Android, iOS, web, etc. E sobre tudo, o custo de desenvolvimento é muito mais barato que aplicações nativas.
Nem sempre os cuidados de segurança são os melhores. É necessário ofuscar todo o conteúdo, nomeadamente ficheiros Javascript onde vão estar guardados dados sensíveis, e sobretudo, repensar a forma como a app deve ser desenhada.
Relembrar que, o desempenho de uma aplicação híbrida não pode ser comparado ao desempenho de uma aplicação nativa, pois a aplicação não-nativa não fala diretamente com o SO.
Em suma, como vantagens podem ser enumeradas as seguintes:
- Boa experiência de utilização;
- Portabilidade (multiplataforma);
- Baixo custo de desenvolvimento; e
- Rápido desenvolvimento.
Como desvantagens:
- Fraca segurança (sem ofuscação e criptografia, etc);
- Baixa performance (é necessário uma ponte via Javascript); e
- A aplicação é mais pesada que uma aplicação nativa porque agrega um conjunto de plugins do CTP.
Melhorar a Segurança de Aplicações Android
A segurança de qualquer que seja o sistema deve ser pensada desde o seu início. Neste caso em específico, seja uma aplicação Android nativa ou não-nativa, o problema é exatamente o mesmo: — dados sensíveis estão codificados no código e espalhados por centenas de utilizadores.
A ofuscação é uma técnica que pode ajudar a incrementar o tempo de processamento para obter os dados sensíveis, no entanto, eles estão ali (apenas baralhados). Nesse sentido, ofuscação dificulta o processo mas não resolve o problema dos dados codificados no código.
Algumas soluções de ofuscação para aplicações Android são as seguintes:
- Google Code Enclosure (para apps híbridas);
- ProGuard (apps nativas).
É preciso pensar em outras alternativas para além da ofuscação, p.ex., o Android Key Store System, em que os dados sensíveis são guardados diretamente no processador (hardware).
É uma solução à prova de bala? Não, mas complica em muito a vida de um cracker, e obter dados deste nível de proteção é muito mais complexo do que apenas através de um simples “unzip”.
Existem também outras soluções baseadas em third parties services, como é o exemplo da AWS e do Meteor. Aqui a computação fica do lado de quem mantém o serviço e a aplicação não precisa sequer de manter registos sensíveis no client-side.
Fica em baixo um caso de uso da AWS para soluções móveis.
Nestes casos, a computação fica do lado do provedor de serviço (a AWS, neste exemplo em específico). A gestão de utilizadores é gerida através do serviço cognito, e as callbacks a third party applications são geridas dentro do ecossistema do provedor. Esta é uma forma elegante e segura de conceber aplicações móveis.
Tem desvantagens? Sim, a aplicação móvel distribuída por centenas de utilizadores é considerada segura (uma vez que não existem dados sensíveis partilhados do lado dos clientes) mas mantem-se “agarrada” a um provedor de serviços (o preço a pagar pela medida de segurança implementada).
Conclusão
Não existem soluções perfeitas para a segurança de um sistema ou de uma aplicação. O segredo está na forma como é conseguido fazer andar o relógio a seu favor. Como foi observado, com uma solução de ofuscação, um indivíduo ao analisar a aplicação, não iria demorar 5 minutos, mas talvez fosse demorar algumas horas até encontrar matéria sensível. Existem muitas outras maneiras que não foram discutidas neste artigo, uma vez que o objetivo principal foi demonstrar e consciencializar a importância do desenho de infraestruturas, sistemas e aplicações de forma a que estes sejam implementados com os mínimos requisitos de segurança.
Estima-se que uma grande percentagem de aplicações distribuídas na Play Store não detenham grandes mecanismos de proteção. Em Portugal, o paradigma não é muito diferente. É importante consciencializar os arquitetos de soluções da importância do uso de mecanismos de criptografia e de segurança. É importante perceber que nos últimos anos os prejuízos por fuga de informação têm sido catastróficos. Basta debruçar um olhar atento sobre um dos últimos grandes leaks do verão de 2017, Equifax.
É importante também que as empresas adotem cada vez mais uma mentalidade defensiva, porque já a partir de maio de 2018, existem procedimentos a cumprir, normas europeias que visam penalizar aquelas empresas que não o fizerem (RGPD).
Bom artigo 🙂 Keep going
Muito obrigado Rafael!
Bom artigo 🙂 Keep going!