Skip to content

MASTG-KNOW-0032: Verificação de Integridade em Runtime

Os controles nesta categoria verificam a integridade do espaço de memória do aplicativo para defendê-lo contra patches de memória aplicados durante a execução. Tais patches incluem alterações indesejadas ao código binário, bytecode, tabelas de ponteiros de função e estruturas de dados importantes, além de códigos maliciosos carregados na memória do processo. A integridade pode ser verificada por meio de:

  1. comparação do conteúdo da memória ou de uma soma de verificação (checksum) do conteúdo com valores válidos,
  2. busca na memória por assinaturas de modificações indesejadas.

Há alguma sobreposição com a categoria "detecção de ferramentas e frameworks de engenharia reversa" e, de fato, demonstramos a abordagem baseada em assinatura naquele capítulo, ao mostrarmos como buscar na memória do processo por strings relacionadas ao Frida. Abaixo estão mais alguns exemplos de vários tipos de monitoramento de integridade.

Detecção de Manipulação do Java Runtime

Frameworks de hooking, como Xposed, injetam-se no Android Runtime e deixam diferentes rastros ao fazê-lo. Esses rastros podem ser detectados, conforme mostrado neste trecho de código do projeto XPosedDetector.

static jclass findXposedBridge(C_JNIEnv *env, jobject classLoader) {
    return findLoadedClass(env, classLoader, "de/robv/android/xposed/XposedBridge"_iobfs.c_str());
}
void doAntiXposed(C_JNIEnv *env, jobject object, intptr_t hash) {
    if (!add(hash)) {
        debug(env, "checked classLoader %s", object);
        return;
    }
#ifdef DEBUG
    LOGI("doAntiXposed, classLoader: %p, hash: %zx", object, hash);
#endif
    jclass classXposedBridge = findXposedBridge(env, object);
    if (classXposedBridge == nullptr) {
        return;
    }
    if (xposed_status == NO_XPOSED) {
        xposed_status = FOUND_XPOSED;
    }
    disableXposedBridge(env, classXposedBridge);
    if (clearHooks(env, object)) {
#ifdef DEBUG
        LOGI("hooks cleared");
#endif
        if (xposed_status < ANTIED_XPOSED) {
            xposed_status = ANTIED_XPOSED;
        }
    }
}

Detecção de Hooks Nativos

Ao usar binários ELF, hooks de funções nativas podem ser instalados sobrescrevendo ponteiros de função na memória (por exemplo, hooking da Tabela Global de Offset - GOT ou PLT) ou modificando partes do código da função em si (hooking inline). Verificar a integridade das respectivas regiões de memória é uma forma de detectar esse tipo de hook.

A Tabela Global de Offset (GOT) é usada para resolver funções de bibliotecas. Durante a execução, o linker dinâmico aplica patches nesta tabela com os endereços absolutos dos símbolos globais. Hooks GOT sobrescrevem os endereços de função armazenados e redirecionam chamadas legítimas de função para códigos controlados por adversários. Esse tipo de hook pode ser detectado enumerando o mapa de memória do processo e verificando se cada entrada GOT aponta para uma biblioteca carregada legitimamente.

Em contraste com o GNU ld, que resolve endereços de símbolos apenas quando são necessários pela primeira vez (vinculação lazy - lazy binding), o linker do Android resolve todas as funções externas e escreve as respectivas entradas GOT imediatamente após uma biblioteca ser carregada (vinculação imediata - immediate binding). Portanto, pode-se esperar que todas as entradas GOT apontem para locais de memória válidos nas seções de código de suas respectivas bibliotecas durante a execução. Métodos de detecção de hooks GOT geralmente percorrem a GOT e verificam isso.

Hooks inline funcionam sobrescrevendo algumas instruções no início ou no final do código da função. Durante a execução, esse chamado trampolim redireciona a execução para o código injetado. É possível detectar hooks inline inspecionando os prólogos e epílogos de funções de biblioteca em busca de instruções suspeitas, como saltos longos (far jumps) para locais fora da biblioteca.