Segurança moderna para desenvolvedores Android (parte 5)
Uma das coisas que costumamos comentar sobre o Android e que surpreende outros desenvolvedores é que podemos escrever código em C e C ++ e geralmente todos pensam que é incrível porque o código em Java e Kotlin pode ser descompilado de uma forma simples (se um aplicativo tem ofuscação de código, isso provavelmente tornará a tarefa um pouco mais complicada), mas em C e C ++ não há descompilação, pelo menos não como em Java, porém o código em C pode ser desmontado
No android, temos uma parte interessante que lida com o runtime chamado ART (Android Runtime) que é o antecessor do Dalvik, Art usa uma forma executável de bytecode Dalvik e Dex, isso é suportado por JNI (Java Native interface) que é a parte que deixe o código Java interagir com o código escrito em C / C ++, lembre-se de que o Android é um sistema operacional baseado em Linux, então o código nativo será empacotado e compilado em bibliotecas dinâmicas com ELF (* .so) que será carregado em Aplicativos Android em tempo de execução usando o sistema System.load
No exemplo abaixo:
init { try { System.loadLibrary("someKeyWeWantSafe") } catch (e: UnsatisfiedLinkError) { Log.e(TAG, "Error loading library " + e.toString()); } }
Este carregamento acontece usando System.loadLibrary, que é a biblioteca que está escrita em C / C ++. Se navegarmos um pouco dentro do código loadLibrary, perceberemos que uma exceção de segurança será lançada se o gerenciador de segurança indicar que houve uma violação de segurança, sem No entanto, essa exceção não é lançada ao dissolver o código.
Podemos criar um módulo e adicionar uma chave que desejamos manter segura dentro de um módulo C usando algo semelhante a este:
#include <jni.h> JNIEXPORT jstring JNICALL Java_com_secure_project_keymodule_NdkKeys_getKey(JNIEnv *env, jobject instance) { return (*env)->NewStringUTF(env, "someCoolKey"); }
No entanto, quando a engenharia reversa é feita dentro do aplicativo que contém o código nativo, precisamos entender algumas estruturas de dados relacionadas à ponte JNI chamada JavaVM e JNIEnv. Essas estruturas são ponteiros para apontar funções em nosso código:
- JavaVM Fornece uma interface para invocar as funções, o Android só permite um JavaVM por processo, e não é muito relevante, neste caso, mas é preciso saber.
- JNIEnv Fornece um ponto de acesso para quase todas as funções em JNI que são acessíveis por meio do deslocamento fixo de um ponteiro JNIEnv. Este ponteiro é o primeiro parâmetro passado em cada função JNI, ele também é usado como thread local para armazenamento, por este motivo, não podemos compartilhar JNIEnv dentro de threads.
Mesmo quando em código, poderíamos acessar o deslocamento, é importante entender que se você quiser ter uma chave “segura” usando essa abordagem, pode ser contraproducente, o código pode ser desmontado e se esse ataque vier de alguém que pode ser interno e sabe o que está procurando, você pode encontrar facilmente, mesmo quando é possível manter as chaves armazenadas no NDK e isso torna mais difícil para invasores iniciantes, pode se tornar um processo preocupante ter chaves de texto simples dentro de um aplicativo, você precisa estar bem ciente desses processos, minha recomendação é que não temos nenhuma chave em texto simples
Isso é tudo por esta postagem, se você precisar de ajuda: