It seems like almost and age since I last published anything on here, I’d like to say its due to a global pandemic causing issues, but mostly its just due to amount of work and a lack of interesting problems to solve for a little while. Luckily this all changed yesterday……
Background
One of my colleagues approached me with a database that he believed included deleted fields. This related to the “Mega.nz” android application and many forensic investigators will be familiar with this as being an online storage application and I’ve had a number of Indecent Images of Children cases that revolve around this storage facility.
My colleague said they looked like there were base64 encoded, however when he decoded them he didn’t get any intelligible data. The database is named “megapreferences” and a copy was provided to myself.
Preliminary work
To start off with, I opened the database file with an SQLite viewer. I quite like SQLite spy but some of this work was done on a Linux machine and I used DB Browser for SQLite on there. It shouldn’t make any real difference to this which one you use
A quick look at this copy of the database showed that a number of tables were empty, but the “completedtransfers” table had some data and looked like this:

A quick look at the data suggests that it is Base64 encoded due to the prevalence of “=” characters at the end of the data. Although my colleague had already told me they had unsuccessfully tried to decode the data, I needed to verify this myself.
So I use CyberChef for my data decoding needs (https://gchq.github.io/cyberchef) and I took a sample string out of the database for testing purposes (“IdAI0AprbXDgp5RH4ByJz/hxDJjUGPi8fLOI2lVMJgs=”).

Running Base64 decoding over this produced data that appeared to have no meaning. So this clearly wasn’t the end of the process. Next step was to check it wasn’t something simple like it had been XOR’d using a single character. Luckily Cyberchef has an option for this under XOR Bruteforce

However reviewing these quickly showed no promising results and I wasn’t going to start running bigger key lengths due to the number of permutations involved.
There are so many possible encryption methods that could have been used, in varying complexities, that bruteforcing the data would not be feasible in any sensible time frame. Therefore I needed to figure out how the data had been encrypted, time for some reverse engineering!
Firsty I needed the APK of the application to start the reverse engineering process. I grabbed a copy of the APK for the Mega app from https://mega.en.uptodown.com/android/download.
Next I needed a decomplier, and preferably one that would work on Linux as I was still working off this machine.
The Jadx decompiler and more specifically the jadx-gui (github.com/skylot/jadx) proved an excellent choice. Imported the apk file into the jadx-gui and let it decode the sourcecode (takes a little while).
The Jadx software is a decompiler for Android APKs that reverts it to its original Java code, as opposed to those generate Byte code (such as Dalvik) which is very close to Assembly language.
Once the Jadx-Gui had been opened, it was a simple case of opening the APK file that had been previously downloaded. This started the decompilation process which takes a little time.
Once the source code has been loaded, we can run text searches over the data in order to find possible encryption routines. So started with “encrypt” and quickly noticed a lot of hits, so added a “(“ to make “encrypt(“. This gives us a number of results and we can see a number of SQL statements that appear to be encrypting the data as it is added to the database. This is consistent with what we have so far as the database itself is not encrypted but the individual fields are.

Further down the results, we can see a declaration for a function named “encrypt” which takes a string as an argument. This is likely to be the function that we are interested in

In Java, functions are either Public or Private, for reasons I won’t explore in this post, and can be defined as Static. The “String” text just before the function name tells us (and the compiler) what type of data the function will return. The text within the brackets are the arguments for the function including its type. This function has the text “String str” and this tells us that the function takes a single parameter that is a type String and is referred to as “str” within this function.
When we are in the searches view of Jadx-Gui, and indeed in many other section, double clicking on a result of function name (in the other parts of the program) will take us to the search hit (or function) in the source code. Double clicking on the hit static “Public Static String encrypt”

Running through this function shows us a number of things. The majority of the code is error checking. The first “if” statement is checking that the parameter “str” exists (i.e. == null) or if it is, to return a null value. This would stop the program crashing if a null string was passed.
The next section is a “try catch” section. This again is mostly error checking, the program will try and execute any code inbetween the “{” and “}” brackets after the “try” keyword. If an error occurs during this, the code within the “{” and “}” brackets after the “catch” keyword is executed. This prevents a program crashing on an error.
The only “useful” code within this function is the line starting “return”. The return keyword is used to pass a value back to the function that called it. So in this case, it will return a String (we know this from the function declaration) that contains the result of a function that Base64 encodes the result of the Util.aes_encrypt function.
The aes_encrypt function is passed three parameters:
- The result of the “getAesKey” function
- str.getBytes – this is a method of converting a String (text) into a binary object (Hex)
- Literal value of 0
Now lets run another search for this and go to the function and we can see the aes_encrypt and aes_decrypt functions are next to each other.

So we can see both the aes_encrypt and aes_decrypt functions, but they don’t give too much further information on the key or other details. Earlier we saw that the result of the “getAesKey” function is passed to the aes_encrypt (and aes_decrypt) functions. Lets examine this function:

This gives a string of text and a function that appears to return the first 32 bytes of the string (for some reason as opposed to using whole string).
So now, we have a key, we can jump back into CyberChef and use the AES decrypt function
Unfortunately we also need an “IV” value and a mode to be able to decrypt it via this method and further examination of the source code didn’t seem to show that these values were passed to any encrypt function.

However, we have the encryption, key generation functions and the aes_decrypt, now lets find the calling decryption function. Given the naming convention, a keyword search for “decrypt(“ seems like a good starting point. Sure enough , this gives us the function we are looking for

Again it shows us that the string is decoded from Base64 and then decrypted to give a result. Still no further with these values, but we do have the functions required to decrypt the data. We can copy these functions out of the source code and create a small program to see if we can decode some text. Let create a small java program to test this:

As we can see, putting a string literal into this and decoding it using the functions provided (slightly modified to make it work) gives us a string of text that looks a lot like a file that would be downloaded using MEGA.
Details of a couple of the changes made:
- The decrypt function was part of the class “Utils” in the original program, as this is now in our only class, it is called without a preceding Utils.
- The base64 function comes from “android.util.Base64”. The java.util.Base64 version is slightly different and the code has been amended to reflect this.
Now we have working code, we need to try and decrypt all the values within the database. So the “simplest” way is to add a function into the Java program and then decrypt the values and output them in some way. However, Java is a pain of a language and if I could have ported this code to anything else (short of assembly) I would have.
My (slightly hacky) workaround was to make the program take a variable number of string arguments and decode them all and print them to the console, separated by “,” characters.
I then created a python script (Download Here ) that connected to the database, got the values and sent these to the java program (Download Here) and scooped up the results.In order to make the python script work, please put the java program (with .jar extension) in the same location as the script and note that a number of command window boxes will open during this process (one per record in table).
The usage of the script is megaDBDecrypt.py {path to database, including name}
So in order to make my life easier, I put the database file in the same location as the python script and the java file and ran “python megaDBDecrypt.py megapreferences”
Once the python script has finished, it creates a “completedtransferDec” table and then writes the decrypted results back into the database. This gives us the following results (some details hidden):

Et voila! We now have a decrypted table from the database. The Python script used the Java compiled code along with source code can be found in the downloads section or Here.
Note: If the database table is full of “null” values, please ensure your Java Runtime Environment is up to date https://java.com/en/download/manual.jsp
Excellent work! Your persisted where many would have given up on this, very impressive.
Let me know if you ever need technical help on stuff like this, we love these sorts of challenges. Will happily help for free for this crime type.
Cheers, Matt (from CameraForensics)
LikeLike
You can do the decryption in python as well:
“`
>>> from Crypto.Cipher import AES
>>> key = ‘android_idfkvn8 w4y*(NC$G*(G($*GR*(#)*huio4h389$G’
>>> len(key)
49
>>> d = ‘IdAI0AprbXDgp5RH4ByJz/hxDJjUGPi8fLOI2lVMJgs=’
>>> import base64
>>> dd = base64.b64decode(d)
>>> dd
‘!\xd0\x08\xd0\nkmp\xe0\xa7\x94G\xe0\x1c\x89\xcf\xf8q\x0c\x98\xd4\x18\xf8\xbc|\xb3\x88\xdaUL&\x0b’
>>> aes = AES.new(key[:32])
>>> aes.decrypt(dd)
‘1_5080474925622362420.MOV\x07\x07\x07\x07\x07\x07\x07’
“`
Then just strip the pad bytes.
LikeLike
Hi Jason,
You are correct, I’ve put together a second post explaining the steps to move the entire process to python as well as making the code a little more than a POC.
Chris
LikeLike
This is amazing work! Have you done it for a Windows mega client? Do you know what key is used?
LikeLike
Hi William,
I tried the same key against the windows client and that didn’t work.
Unfortunately I haven’t managed to get any further with the windows client.
LikeLike