Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated Decrypting Realm Databases for Android (0x05d) and iOS (0x06d) #2570

Merged
merged 15 commits into from
May 2, 2024
62 changes: 62 additions & 0 deletions Document/0x05d-Testing-Data-Storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,68 @@ Realm realm = Realm.getInstance(config);

cpholguera marked this conversation as resolved.
Show resolved Hide resolved
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.

However its quite important to be aware that if the database _is_ encrypted, its often possible to obtain the decryption key at runtime. This is because the encryption and decryption keys are identical and are invoked at runtime to facilitate access to the Realm file. The frida script below is demonstrating how to intercept the specific Realm key utilized by the Realm database, allowing for the decryption of encrypted database.
cpholguera marked this conversation as resolved.
Show resolved Hide resolved

```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

You can save files to the device's [internal storage](https://developer.android.com/training/data-storage#filesInternal "Using Internal Storage"). Files saved to internal storage are containerized by default and cannot be accessed by other apps on the device. When the user uninstalls your app, these files are removed.
Expand Down
42 changes: 42 additions & 0 deletions Document/0x06d-Testing-Data-Storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,49 @@
// If the encryption key is wrong, `error` will say that it's an invalid database
fatalError("Error opening realm: \(error)")
}
```

Check failure on line 96 in Document/0x06d-Testing-Data-Storage.md

View workflow job for this annotation

GitHub Actions / markdown-lint-check

Fenced code blocks should be surrounded by blank lines [Context: "```"]
One security concern that warrants attention involves the potential interception or compromise of the encryption key when accessing the Realm database. This arises due to the necessity of supplying the decryption key at runtime, which introduces a window to capture the decryption key. The frida script demonstrated below targets the RLMRealmConfiguration class within the Realm database framework, leveraging its functionality to extract the decryption key. By hooking into this class, the script retrieves the key directly from memory, converting it into a hexadecimal string which then can be used to decrypt the database.
cpholguera marked this conversation as resolved.
Show resolved Hide resolved

```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

Expand Down
Loading