Enter The Realm
During my time as a penetration tester who enjoys mobile testing I have come across a number of different databases. These include SQLite, SQLCipher, Firebase and Realm.
In one particular instance I noticed that the application I was testing used a Realm database which was encrypted.
From prior experience testing I knew it’s quite trivial to hook the SQLCipher function and obtain the encryption password to the database Additionally, it’s quite simple to call rawExecSQL
using #FRIDA and write out the contents of the database without encryption as demonstrated in the script below:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Java.perform(function () {
Java.choose("net.sqlcipher.database.SQLiteDatabase", {
onMatch: function(instance) {
var path = instance.mPath.value
console.log("Path: " + path)
if(path.includes("<DB>.sqlite")) {
var File = Java.use("java.io.File");
var file = File.$new(path + ".plain");
file.delete();
instance.rawExecSQL(
"ATTACH DATABASE '" + path + ".plain' AS plaintext KEY '';");
instance.rawExecSQL("SELECT sqlcipher_export('plaintext');");
instance.rawExecSQL("DETACH DATABASE plaintext;");
console.log("[+] Done ")
}
}
});
});
However, there was no information on doing this with Realm databases. This increased my curiosity to see if it was possible to somehow decrypt an encrypted realm database without the source code of the application.
According to the Realm documentation encrypted realms databases will be encrypted using 64 byte encryption key.
The first 32 bytes are used for encryption the next 24 bytes are used for the signature and the remaining 8 bytes are not currently used then each 4KB block of data is encrypted using AES-256 CBC and one time use IV key. Attacking the database in terms of breaking the encryption doesn’t appear to be impossible from this information provided.
However, while using #FRIDA I was able to hook into the io.realm.RealmConfiguration
function. This function appeared to be the most ideal place to hook as its setups the database name, location, encryption key and various other information.
My theory: Can I obtain the encryption key from this function?
So after a few hours of fiddling around with an GitHub project using realm databases and dealing with Gradle build issues and just Android compiling issues.
I was able to compile a test app to test this theory I then added a few Log.d("getSecretKey")
as well as Log.d(JSON.stringify(Key)
statements in the android studio project to ensure i knew the encryption key value.
I then began writing a Frida script to see if it was possible to obtain the encryption key as decrypting a realm database is done by using a 128 character hex-encoded encryption key as the decryption key.
To my surprise the values from the Logcat and the Frida script were the exact same.
I then modified my script to print the bytes from the application into hex which is demonstrated in the script below:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
'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;
}
if (Java.available) {
Java.perform(function() {
var RealmConfig = Java.use("io.realm.RealmConfiguration")
Java.choose("io.realm.Realm", {
onMatch: function(instance)
{
console.log("[=====================] Opened Database")
},
onComplete: function(instance)
{
RealmConfig.$init.overload('java.io.File', 'java.lang.String', 'java.lang.String', 'java.lang.String', '[B', 'long', 'io.realm.RealmMigration', 'boolean', 'io.realm.internal.OsRealmConfig$Durability', 'io.realm.internal.RealmProxyMediator', 'io.realm.rx.RxObservableFactory', 'io.realm.Realm$Transaction', 'boolean', 'io.realm.CompactOnLaunchCallback', 'boolean').implementation = function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15) {
console.log("Location: ", arg1)
console.log("Realm Database: ", arg2)
if(arg5.length == null){
console.log("")
} else {
console.log("Encryption Key - No Bytes", JSON.stringify(arg5))
}
console.log("Length of key: ", arg5.length + "\nKey(Decrypt): ", bytesToHex(arg5))
console.log("Build", arg6)
var returnvalue = this.$init(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
return returnvalue
}
console.log("Closed Database")
}
})
}
)}
which then returned the encryption key. This appears to hook every time the database query that value. So, I pulled the application database using adb pull
then loaded it into Realm Studio and pasted the key which decrypted the database allowing me to view its contents.
Then to ensure that I didn’t just get lucky on one application I tested this on a real world application Element - Secure Messager and was able to obtain the encryption key for all the databases and decrypt them.
Hopefully this discourages developers from including sensitive information such as passphrases or encryption keys in the device storage. Obfuscation, encryption and even hooks protection are valid options to increase security posture of the applications but ultimately cannot completely protect against secrets stored in plain text in the application.
If you would like to play with this yourself, I have uploaded the source code of the application, a compiled APK and the frida script at the repo below: Realm Android
Also #FRIDA = MAGIC.