Tools

Patching Android Split APKs

I recently came up against my first split APK during an Android app security assessment. My usual toolkit doesn’t support split APKs, so I hacked together a solution to allow me to instrument and analyse an Android app that was split over multiple APKs. I’ve published patch-apk.py to Github, but read on for the gory details!

Split APKs

It turns out Android has supported split APKs since version 5/Lollipop (June 2014, API level 21), but this was the first time I’d come across one in many years of mobile app pentesting. APK splitting allows an app to be split across multiple APK files, for example one might contain the main code while another contains visual resources that are optimised for a given screen resolution, or native libraries that are compiled for a given architecture.

We can identify whether an app uses split APKs by checking if the adb shell pm path command returns multiple APK paths. Using a COVID Symptom Tracker app as an example we get the following output:

[crayon-65734b5d818ed819860168/]

This app was chosen purely for using split APKs and being high on the top apps list on Google Play at the time of writing!

Patching with Objection

One of my main go-to tools for mobile app testing is objection. Unfortunately we get an error if we attempt to use objection patchapk to patch base.apk from an App Bundle and install it back on the device.

[crayon-65734b5d818f4745499119/]

Looking at the APKs it wasn’t entirely clear what the signature inconsistency might be. It was possibly related to the resources.asrc file, but I’m not familiar with the format. Either way, the contents of the split APK files looked simple enough to smash them together into one.

Contents of one split APK file.
Contents of one split APK file.

Simply copying files from the split APKs into the base APK directory and rebuilding didn’t work. A quick look at AndroidManifest.xml revealed that the <application> element had the attribute android:isSplitRequired set to true. I set this to false, then removed the following <meta-data> elements:

[crayon-65734b5d818f6515530051/]

This still didn’t work, but the error hinted that the android:extractNativeLibs attribute of the <application> element was the problem. After setting this attribute to true I managed to build a single APK, patch it using objection patchapk, and get it up and running on my Android device.

Broken Resources

After patching the app in this way it appeared to work correctly, but there were various icons missing from the UI compared to the corresponding iOS app I’d been working on. While this didn’t affect the functionality of the app, it was the result of me hacking the APKs together.

Looking more closely at the resources from the extracted APKs I noticed that the file res/values/public.xml was very different across the different APKs. In particular, some of the resource names from the split APKs weren’t present in base.apk, although the corresponding resource IDs were.

Using the same symptom tracker app from earlier as an example, if we look inside res/values/public.xml of the APK split_config.xxhdpi.apk we can see an entry with the name exo_icon_stop and the id 0x7f0800bf. The same file in base.apk did not include an entry with this name, but it did have an entry with the same ID as follows:

[crayon-65734b5d818fc073401143/]

The entry with the matching id in base.apk had been given a dummy name by apktool. Presumably because the actual resource name was defined in the split APK and therefore could not be resolved by apktool.

Fixing Resource IDs

The dummy resource names generated by apktool aren’t a problem in many cases because Android apps tend to refer to resources by their ID. In this case, however, certain images were loaded by name.

With hundreds of resource identifiers in the app, I started hacking together some Python to automate fixing of resource IDs. The first step was to parse res/values/public.xml from base.apk and get the name and id of all entries where the name was prefixed with APKTOOL_DUMMY_.

Next, I needed to map those IDs to the correct names from the split APKs. This was done by parsing the res/values/public.xml of each split APK and retrieving the name of each entry with an id matching one of those loaded from base.apk.

With the mappings of dummy names to IDs to real names I could update res/values/public.xml within base.apk to give all entries with dummy names their correct names.

The final step was to process all XML files in the res directory of base.apk and replace all references to one of the dummy resource names with the real resource name.

Dummy Android resource references.
Dummy Android resource references.

With the resource identifiers fixed, the patched APK worked great.

Wrapping It Up

I’ve been automating some of my mobile app pentesting tasks recently. One of the main tasks I’ve automated is that of getting an app setup for proxying and instrumentation:

  1. Working out the package name and APK path on the device.
  2. Pulling the APK.
  3. Patching the APK to support user-installed CA certificates.
  4. Injecting a frida/objection instrumentation gadget.
  5. Uninstalling the original app.
  6. Installing the patched app.

Originally I had no intention of releasing this as it was largely just a dirty wrapper script automating several other tools.

Then I came across a split APK.

I’m not aware of any other easy method of patching a split APK and it’s definitely not something I’d want to be doing manually, so I’ve incorporated that into my original “pull-patch-push” script, tidied things up a touch, and released it on Github: patch-apk.py.

Automating patching of split APKs using patch-apk.py.
Automating patching of split APKs using patch-apk.py.
Tags:

Discussion

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.