MASTG-KNOW-0040: Realm Databases

O Realm Database for Java está se tornando cada vez mais popular entre os desenvolvedores. O banco de dados e seu conteúdo podem ser criptografados com uma chave armazenada no arquivo de configuração.

//o método getKey() obtém a chave do servidor, de um KeyStore ou é derivada de uma senha.
RealmConfiguration config = new RealmConfiguration.Builder()
  .encryptionKey(getKey())
  .build();

Realm realm = Realm.getInstance(config);

O acesso aos dados depende da criptografia: bancos de dados não criptografados são facilmente acessíveis, enquanto os criptografados exigem investigação sobre como a chave é gerenciada — se está embutida no código (hardcoded), armazenada sem criptografia em um local inseguro, como shared preferences, ou de forma segura no KeyStore da plataforma (que é a melhor prática).

No entanto, se um invasor tiver acesso suficiente ao dispositivo (por exemplo, acesso root) ou puder reempacotar o aplicativo, ainda será possível recuperar as chaves de criptografia em tempo de execução usando ferramentas como o Frida. O seguinte script em Frida demonstra como interceptar a chave de criptografia do Realm e acessar o conteúdo do banco de dados criptografado.

'use strict';

function modulus(x, n){
    return ((x % n) + n) % n;
}

function bytesToHex(bytes) {
    for (var hex = [], i = 0; i < bytes.length; i++) { hex.push(((bytes[i] >>> 4) & 0xF).toString(16).toUpperCase());
        hex.push((bytes[i] & 0xF).toString(16).toUpperCase());
    }
    return hex.join("");
}

function b2s(array) {
    var result = "";
    for (var i = 0; i < array.length; i++) {
        result += String.fromCharCode(modulus(array[i], 256));
    }
    return result;
}

// Módulo e função principal.

if(Java.available){
    console.log("Java está disponível");
    console.log("[+] Dispositivo Android.. Conectando à Configuração do Realm.");

    Java.perform(function(){
        var RealmConfiguration = Java.use('io.realm.RealmConfiguration');
        if(RealmConfiguration){
            console.log("[++] A Configuração do Realm está disponível");
            Java.choose("io.realm.Realm", {
                onMatch: function(instance)
                {
                    console.log("[==] Banco de Dados Realm Aberto... Obtendo a chave...")
                    console.log(instance);
                    console.log(instance.getPath());
                    console.log(instance.getVersion());
                    var encryption_key = instance.getConfiguration().getEncryptionKey();
                    console.log(encryption_key);
                    console.log("Tamanho da chave: " + encryption_key.length); 
                    console.log("Chave de descriptografia:", bytesToHex(encryption_key));

                }, 
                onComplete: function(instance){
                    RealmConfiguration.$init.overload('java.io.File', 'java.lang.String', '[B', 'long', 'io.realm.RealmMigration', 'boolean', 'io.realm.internal.OsRealmConfig$Durability', 'io.realm.internal.RealmProxyMediator', 'io.realm.rx.RxObservableFactory', 'io.realm.coroutines.FlowFactory', 'io.realm.Realm$Transaction', 'boolean', 'io.realm.CompactOnLaunchCallback', 'boolean', 'long', 'boolean', 'boolean').implementation = function(arg1)
                    {
                        console.log("[==] Realm onComplete Finalizado..")

                    }
                }

            });
        }
    });
}