Segurança moderna no Android (parte 5)
Uma das coisas que costumo dizer sobre o Android que surpreende as pessoas é que você pode escrever código em C e C ++ e geralmente elas acham que é legal, porque o código Java e Kotlin pode ser descompilado facilmente (se um aplicativo móvel tiver ofuscação, provavelmente a tarefa será um pouco mais difícil), mas C e C ++ não, pelo menos não da mesma forma que o código Java, C não pode ser descompilado, mas pode ser desmontado
No Android, temos uma coisa especial que gerencia o tempo de execução chamado ART (Android Runtime) é o predecessor do Dalvik, ART usa um formato executável de bytecode Dalvik e Dex, isso suporta o JNI (Java Native Interface) que é a parte mágica que permite O código Java opera e interage com o código escrito em C / C ++, vamos lembrar que o Android é um código nativo do sistema operacional baseado em Linux que é empacotado (compilado) em bibliotecas dinâmicas ELF (* .so), que o aplicativo Android carrega em tempo de execução por meio do Sistema. carga
No próximo exemplo:
init { try { System.loadLibrary("someKeyWeWantSafe") } catch (e: UnsatisfiedLinkError) { Log.e(TAG, "Error loading library " + e.toString()); } }
Este carregamento acontece usando System.loadLibrary que é uma biblioteca escrita em C / C ++ se você navegar dentro do código de loadLibrary, você verá uma Exceção de Segurança, lançada pelo gerenciador de segurança para indicar uma violação de segurança, seja qual for a violação não é chamada quando dissimular o código.
Você pode criar um módulo e adicionar sua chave privada segura aqui, dentro do Módulo C, usando algo como isto:
#include <jni.h> JNIEXPORT jstring JNICALL Java_com_secure_project_keymodule_NdkKeys_getKey(JNIEnv *env, jobject instance) { return (*env)->NewStringUTF(env, "someCoolKey"); }
Ao reverter um aplicativo Android que contém código nativo, precisamos entender algumas estruturas de dados relacionadas à ponte JNI entre Java e código nativo. Da perspectiva inversa, precisamos estar cientes de duas estruturas de dados principais: JavaVM e JNIEnv. Ambos são ponteiros para tabelas de funções:
- JavaVM fornece uma interface para chamar funções para criar e destruir um JavaVM. O Android permite apenas um JavaVM por processo e não é realmente relevante para nossos fins de reversão
- JNIEnv fornece acesso à maioria das funções JNI que são acessíveis em um deslocamento fixo por meio do ponteiro JNIEnv. Este JNIEnv a ponteiro é o primeiro parâmetro passado para cada função JNI. Também é usado para armazenamento local de thread. Por esse motivo, você não pode compartilhar um JNIEnv entre threads. Se um trecho de código não tem outra maneira de obter seu JNIEnv, você deve compartilhar o JavaVM e usar GetEnv para descobrir o JNIEnv do encadeamento.
É importante ressaltar que nunca é uma boa ideia deixar suas chaves de API espalhadas por sua base de código de uma forma que seja facilmente decodificável, analisar código C/C++ desmontado é muito mais difícil do que código Java desmontado.
Embora seja possível armazenar qualquer chave com o NDK, fica mais difícil para os iniciantes obterem as informações que procuram dentro do seu aplicativo, seja o que for, é mais fácil para os Vazamentos internos obterem o que desejam fazendo alguma pesquisa básica, não é uma opção segura para chaves importantes, mas tornará o truque para algo mais simples.
Isso é tudo para esta parte da postagem, se você precisar de ajuda: