Olá, caro leitor, estou estudando aplicativos para dispositivos móveis há algum tempo. A maioria dos aplicativos não tenta, de alguma forma, esconder sua funcionalidade "secreta" de mim. E estou feliz neste momento, porque não tenho que estudar o código ofuscado de alguém.

Neste artigo, gostaria de compartilhar minha visão de ofuscação, além de falar sobre um método interessante para ocultar a lógica de negócios em aplicativos com NDK, que encontrei relativamente recentemente. Então, se você estiver interessado em exemplos ao vivo de código ofuscado no Android - peço gato.
Sob a ofuscação na estrutura deste artigo, entendemos a redução do código executável de um aplicativo Android para um formulário difícil de analisar. Há vários motivos para dificultar a análise de código:
- Nenhuma empresa quer ser bisbilhotada em seu "interior".
- Mesmo se você tiver um aplicativo fictício, sempre poderá encontrar coisas interessantes (um exemplo no instagram ).
Muitos desenvolvedores resolvem o problema simplesmente alterando a configuração do ProGuard. Esta não é a melhor maneira de proteger os dados (se é a primeira vez que você ouve isso, consulte o wiki ).
Quero dar um exemplo, por que a suposta "proteção" com o ProGuard não funciona. Veja qualquer exemplo simples do Google Samples.

Depois de conectar o ProGuard com uma configuração padrão, obtemos um código descompilado:

"Ohhh, nada está claro", dizemos e nos acalmamos. Mas, após alguns minutos de alternância entre arquivos, encontramos partes semelhantes de código:

Neste exemplo, o código do aplicativo parece bastante fraco (registro de dados, criação de captura de vídeo); portanto, alguns dos métodos usados no código original são facilmente entendidos, mesmo após o processamento com a configuração do ProGuard.
Além disso, dê uma olhada nas classes de dados no Kotlin. A classe de dados padrão cria o método "toString", que contém os nomes das variáveis da instância e o nome da própria classe.
Classe de dados de origem:

Pode se transformar em um petisco para um reverso:

(geração automática do método toString no Kotlin)
Acontece que o ProGuard se esconde longe de todo o código-fonte do projeto.
Se eu ainda não o convenci da inconveniência de proteger o código dessa maneira, vamos tentar deixar o atributo ".source" em nosso projeto.
-keepattributes SourceFile
Essa linha está em muitos projetos de código aberto. Ele permite que você visualize o StackTrace quando o aplicativo falha. No entanto, ao extrair “.source” do código smali, obtemos toda a hierarquia do projeto com nomes completos de classe.
Por definição, ofuscação é “trazer o código-fonte para uma forma ilegível, a fim de neutralizar diferentes tipos de recepção”. No entanto, o ProGuard (quando usado com uma configuração padrão) não torna o código ilegível - ele funciona como um minificador, compactando nomes e expulsando classes extras do projeto.
Esse uso do ProGuard é fácil, mas não totalmente adequado para uma boa solução de ofuscação. Um bom desenvolvedor precisa fazer com que o revendedor (ou o atacante) fique com medo de "caracteres chineses", difíceis de ofuscar.
Se você estiver interessado em aprender mais sobre o ProGuard, proponho o seguinte artigo informativo .
O que estamos escondendo
Agora vamos ver o que geralmente está oculto nos aplicativos.

- Lógica de aplicação específica:

Algo mais inesperado pode frequentemente estar oculto no código (observações da experiência pessoal), por exemplo:
- Nomes dos desenvolvedores do projeto
- Caminho completo para o projeto
- Client_secret para protocolo Oauth2
- Livro em PDF "Como desenvolver para Android" (provavelmente sempre disponível)
Agora sabemos o que pode ocultar nos aplicativos Android e podemos avançar para a principal coisa, a saber, maneiras de ocultar esses dados.
Maneiras de ocultar dados
Opção 1: não esconda nada, deixe tudo à vista
Neste caso, eu apenas mostro esta imagem :)
“Ajude o Dasha a encontrar a lógica de negócios”

Esta solução econômica e totalmente gratuita é adequada para:
- Aplicativos simples que não interagem com a rede e não armazenam informações confidenciais do usuário;
- Aplicativos que usam apenas a API pública.
Opção 2: use o ProGuard com as configurações corretas
Essa decisão ainda tem direito à vida, porque, antes de tudo, é simples e gratuita. Apesar das desvantagens acima mencionadas, há uma vantagem significativa: se as regras do ProGuard estiverem configuradas corretamente, o aplicativo poderá realmente ficar ofuscado.
No entanto, você precisa entender que essa solução após cada montagem exige que o desenvolvedor descompile e verifique se está tudo bem. Depois de passar alguns minutos estudando o arquivo APK, o desenvolvedor (e sua empresa) pode se tornar mais confiante na segurança de seu produto.
Como estudar o arquivo APKVerificar o pedido de ofuscação é bastante simples.
Para obter o arquivo APK do projeto, existem várias maneiras:
- tire do diretório do projeto (no Android Studio, geralmente o nome da pasta é "build");
- instale o aplicativo no seu smartphone e obtenha o APK usando o aplicativo "Apk Extractor".
Depois disso, usando o utilitário Apktool, obtemos o código Smali (instruções para chegar aqui https://ibotpeaches.imtqy.com/Apktool/documentation ) e tentamos encontrar algo suspeito de ser lido nas linhas do projeto. A propósito, para procurar códigos legíveis, você pode estocar os comandos bash pré-fabricados.
Esta solução é adequada para:
- Aplicações de brinquedos, aplicações de lojas online, etc;
- Aplicativos realmente thin clients e todos os dados chegam exclusivamente do lado do servidor;
- Aplicativos que não gravam em todos os seus banners "Aplicativo Seguro Nº 1".
Opção 3: Use o Ofuscador de Código Aberto
Infelizmente, não conheço realmente bons ofuscadores gratuitos para aplicativos móveis. E os ofuscadores que podem ser encontrados na rede podem causar muita dor de cabeça, pois será muito difícil montar esse projeto para novas versões da API.
Historicamente, os ofuscadores legais existentes são fabricados sob código de máquina (para C / C ++). Bons exemplos:
Por exemplo, o Movfuscator substitui todos os opcodes por movs, torna o código linear, removendo todos os ramos. No entanto, é altamente desencorajado usar esse método de ofuscação em um projeto de combate, porque o código corre o risco de se tornar muito lento e pesado.
Esta solução é adequada para aplicativos em que a parte principal do código é NDK.
Opção 4: use uma solução proprietária
Essa é a escolha mais competente para aplicativos sérios, como software proprietário:
a) suportado;
b) sempre será relevante.
Um exemplo de código ofuscado ao usar essas soluções:

Neste trecho de código, você pode ver:
- Os nomes mais incompreensíveis das variáveis (com a presença de letras russas);
- Caracteres chineses nas linhas que não deixam claro o que realmente está acontecendo no projeto;
- Existem muitas traps adicionadas ao projeto ("switch", "goto"), que alteram bastante o fluxo de código do aplicativo.
Esta solução é adequada para:
- Bancos
- Companhias de seguros;
- Operadoras móveis, aplicativos de armazenamento de senhas etc.
Opção 5: use React-Native
Decidi destacar esse ponto, já que escrever aplicativos em várias plataformas agora se tornou uma atividade muito popular.
Além de uma comunidade muito grande, o JS possui um número muito grande de ofuscadores abertos. Por exemplo, eles podem transformar seu aplicativo em emoticons:

Eu realmente gostaria de aconselhá-lo sobre essa solução, mas seu projeto funcionará muito pouco mais rápido que uma tartaruga.
Mas, reduzindo o requisito de ofuscação de código, podemos criar um projeto realmente bem protegido. Então, google "js obfuscator" e ofusque nosso arquivo do pacote de saída.
Esta solução é adequada para aqueles que estão prontos para escrever um aplicativo de plataforma cruzada no React Native.
XamarinSeria muito interessante saber sobre os ofuscadores no Xamarin; se você tiver experiência em usá-los, conte-nos nos comentários.
Opção 6: use NDK
Eu próprio frequentemente tive que usar NDK no meu código. E sei que alguns desenvolvedores acreditam que o uso do NDK salva seu aplicativo dos reversores. Isto não é inteiramente verdade. Primeiro, você precisa entender como ocultar funciona com o NDK.

Acontece muito simples. Há alguma convenção JNI no código que, quando você chama o código C / C ++ em um projeto, ele é convertido da seguinte maneira.
NativeSummator Classe nativa:

Implementação do método de soma nativa:

Implementação do método de soma estática nativa:

Torna-se claro que, para chamar o método nativo, você usa a Java_<package name>_<Static?><class>_<method>
da função Java_<package name>_<Static?><class>_<method>
na biblioteca dinâmica.
Se você olhar o código Dalvik / ART, encontraremos as seguintes linhas:

( fonte )
Primeiro, geraremos a seguinte linha Java_<package name>_<class>_<method>
partir do objeto Java e tentaremos resolver o método na biblioteca dinâmica usando a chamada "dlsym", que tentará encontrar a função que precisamos no NDK.
É assim que o JNI funciona. Seu principal problema é que, ao descompilar a biblioteca dinâmica, veremos todos os métodos em exibição completa:

Portanto, precisamos encontrar uma solução para que o endereço da função seja ofuscado.
No começo, tentei gravar dados diretamente na nossa tabela JNI, mas percebi que os mecanismos ASLR e diferentes versões do Android simplesmente não me permitiam fazer esse método funcionar em todos os dispositivos. Decidi descobrir quais métodos o NDK fornece aos desenvolvedores.
E eis que havia um método "RegisterNatives" que faz exatamente o que precisamos (chama a função interna dvmRegisterJNIMethod ).
Definimos uma matriz que descreve nosso método nativo:

E registramos nosso método declarado na função JNI_OnLoad (o método é chamado depois que a biblioteca dinâmica é inicializada, mil ):

Hooray, nós mesmos escondemos a função "hideFunc". Agora aplique nosso ofuscador llvm favorito e aproveite a segurança do código em sua forma final.
Essa solução é adequada para aplicativos que já usam NDK (conectar o NDK traz muitas dificuldades ao projeto, portanto, essa solução não é tão relevante para aplicativos que não são do NDK).
Conclusão
De fato, o aplicativo não deve armazenar dados confidenciais ou deve estar acessível somente após a autenticação do usuário. No entanto, acontece que a lógica de negócios força os desenvolvedores a armazenar tokens, chaves e elementos específicos da lógica de código dentro do aplicativo. Espero que este artigo o ajude se você não deseja compartilhar esses dados confidenciais e seja um "livro aberto" para escribas.
Acredito que a ofuscação é uma parte estrutural importante de qualquer aplicação moderna.
Pense cuidadosamente sobre problemas de ocultação de código e não procure maneiras fáceis! :)
A propósito, obrigado ao usuário miproblema pela ajuda em alguns problemas. Assine o canal de telegrama dela, é interessante por lá.
E também muito obrigado aos usuários do sverkunchik e SCaptainCAP por sua ajuda na edição do artigo.