Patching Split APKs The Easy Way
tldr for experienced researchers: Just sign each apk in the split, even if you didn’t modify it, with the same signature.
0.0 What Are Split APKs
It’s not unusual when beginning to dive into a new sub-field of security to encounter some roadblocks. This inevitably leads to googling, pulling up blog posts, scouring github comments, etc to find the answer to your problem. Occasionally this search ends up being more bafflingly difficult than it seems it should be. Surely if this is a common beginner roadblock, then more experienced people ought to have an absolute litany of options to solve it right?
Well when I began to dabble into reversing and messing around with android mobile apps I ran into one such situation. It’s an incredibly common practice to need to modify apks to either alter behavior or inject things like the super powerful and useful frida. On typical monolithic apk’s this is often pretty straight forward, and the powerful tool objection can even automate this task for you. However monolithic apks aren’t the only kind out there, we also have the annoying-to-deal-with ‘split apks’. These are linked apks that divvy up some amount of functionality amongst its sub parts.
You can identify if you’re dealing with a split apk by its obvious packaging.
For this example I will be using the uber app
madf0x@pop-os:~/Documents/0xFA7E.github.io$ adb shell pm path com.ubercab
package:/data/app/~~xeLRlT59D5ZHLpGv_WDTlg==/com.ubercab-7q1ZWW0A4vGW3Um1gAlzvQ==/base.apk
package:/data/app/~~xeLRlT59D5ZHLpGv_WDTlg==/com.ubercab-7q1ZWW0A4vGW3Um1gAlzvQ==/split_BarcodeScanner.apk
package:/data/app/~~xeLRlT59D5ZHLpGv_WDTlg==/com.ubercab-7q1ZWW0A4vGW3Um1gAlzvQ==/split_BarcodeScanner.config.arm64_v8a.apk
package:/data/app/~~xeLRlT59D5ZHLpGv_WDTlg==/com.ubercab-7q1ZWW0A4vGW3Um1gAlzvQ==/split_BarcodeScanner.config.xxxhdpi.apk
package:/data/app/~~xeLRlT59D5ZHLpGv_WDTlg==/com.ubercab-7q1ZWW0A4vGW3Um1gAlzvQ==/split_GooglePaySdk.apk
package:/data/app/~~xeLRlT59D5ZHLpGv_WDTlg==/com.ubercab-7q1ZWW0A4vGW3Um1gAlzvQ==/split_GooglePaySdk.config.xxxhdpi.apk
package:/data/app/~~xeLRlT59D5ZHLpGv_WDTlg==/com.ubercab-7q1ZWW0A4vGW3Um1gAlzvQ==/split_VoipTwilio.apk
package:/data/app/~~xeLRlT59D5ZHLpGv_WDTlg==/com.ubercab-7q1ZWW0A4vGW3Um1gAlzvQ==/split_VoipTwilio.config.arm64_v8a.apk
package:/data/app/~~xeLRlT59D5ZHLpGv_WDTlg==/com.ubercab-7q1ZWW0A4vGW3Um1gAlzvQ==/split_VoipTwilio.config.xxxhdpi.apk
package:/data/app/~~xeLRlT59D5ZHLpGv_WDTlg==/com.ubercab-7q1ZWW0A4vGW3Um1gAlzvQ==/split_config.arm64_v8a.apk
package:/data/app/~~xeLRlT59D5ZHLpGv_WDTlg==/com.ubercab-7q1ZWW0A4vGW3Um1gAlzvQ==/split_config.en.apk
package:/data/app/~~xeLRlT59D5ZHLpGv_WDTlg==/com.ubercab-7q1ZWW0A4vGW3Um1gAlzvQ==/split_config.xxxhdpi.apk
As we can see there is a base.apk
following by several other apks starting with split_
1.0 Patching Them The Hard Way
The problem with split apks is that once we modify the apk we desire(typically the base.apk) and go to reinstall the application for testing it errors out.
patching
madf0x@pop-os:~/Documents/0xFA7E.github.io/example/com.ubercab-7q1ZWW0A4vGW3Um1gAlzvQ==$ objection patchapk -s base.apk
No architecture specified. Determining it using `adb`...
Detected target device architecture as: arm64-v8a
Using latest Github gadget version: 16.3.3
Patcher will be using Gadget version: 16.3.3
Detected apktool version as: 2.9.3
Running apktool empty-framework-dir...
Unpacking base.apk
App already has android.permission.INTERNET
Setting extractNativeLibs to true...
Target class not specified, searching for launchable activity instead...
Smali not found in smali directory. This might be a multidex APK. Searching...
Found smali at: /tmp/tmphc4xxbnn.apktemp/smali_classes22/com/ubercab/presidio/app/core/root/RootActivity.smali
Reading smali from: /tmp/tmphc4xxbnn.apktemp/smali_classes22/com/ubercab/presidio/app/core/root/RootActivity.smali
Injecting loadLibrary call at line: 76
Attempting to fix the constructors .locals count
Current locals value is 0, updating to 1:
Writing patched smali back to: /tmp/tmphc4xxbnn.apktemp/smali_classes22/com/ubercab/presidio/app/core/root/RootActivity.smali
Creating library path: /tmp/tmphc4xxbnn.apktemp/lib/arm64-v8a
Copying Frida gadget to libs path...
Rebuilding the APK with the frida-gadget loaded...
Built new APK with injected loadLibrary and frida-gadget
Performing zipalign
Zipalign completed
Signing new APK.
Signed the new APK
Copying final apk from /tmp/tmphc4xxbnn.apktemp.aligned.objection.apk to base.objection.apk in current directory...
Cleaning up temp files...
install
madf0x@pop-os:~/Documents/0xFA7E.github.io/example/com.ubercab-7q1ZWW0A4vGW3Um1gAlzvQ==$ adb install-multiple base.objection.apk split_*
adb: failed to finalize session
Failure [INSTALL_FAILED_INVALID_APK: /data/app/vmdl2076824915.tmp/base.objection.apk signatures are inconsistent]
Womp womp!!
Now at this point if a newbie starts hunting around they’ll land on one common advice for solving this problem: turn the split apks back into a monolithic one! The methods for doing so range from downloading shady apk extractors that will export a monolithic apk from your device directly or to use NickstaDB’s patch-apk.py script. This is even the recommended approach from the objection developers.
Now when this script works, it works nicely and even automates reinstalling the apk back onto your device for you. Unfortunately in order for it to recreate a monolithic apk it makes some pretty significant modifications. Some resources might not render properly or the apk can just break and not open after installation at all.
So what do we do if the main python script everyone recommends to use just crashes and burns? Cry and give up? Resort to pure static analysis?
2.0 Patching Split APKs the Easy Way
Well let’s revisit that error again shall we?
Failure [INSTALL_FAILED_INVALID_APK: /data/app/vmdl2076824915.tmp/base.objection.apk signatures are inconsistent]
The error is telling us that the signatures are inconsistent. Apps are signed with unique keys in order to identify who the author is. That way someone can’t just pass off a backdoored or malicious package as a legitimate one so easily(not without doing the usual social engineering and trickery routes at least). Of course as researchers we know we are the ’new authors’ when we modify an apk and we can just sign it with whatever and move on to the fun parts, and indeed tools like objection automate this step when it patches an apk. What this error is really telling us is that the signature used to sign the base.apk doesn’t match the signatures of the associated split apks.
So uhhh why don’t we just sign the split apks with the same signature?? Well we can! And objection already supports this beautifully!
mass signing
madf0x@pop-os:~/Documents/0xFA7E.github.io/example/com.ubercab-7q1ZWW0A4vGW3Um1gAlzvQ==$ objection signapk base.objection.apk split_*
Performing zipalign
Zipalign completed
Signing new APK.
Signed the new APK
Copying final apk from /tmp/tmpwctxbk66.apktemp.aligned.objection.apk to base.objection.objection.apk in current directory...
Cleaning up temp files...
Performing zipalign
.......<SNIP>.......
Copying final apk from /tmp/tmpy8hbukf7.apktemp.aligned.objection.apk to split_VoipTwilio.config.xxxhdpi.objection.apk in current directory...
Cleaning up temp files...
Now we can try installing again(make sure the original application is uninstalled first)
madf0x@pop-os:~/Documents/0xFA7E.github.io/example/com.ubercab-7q1ZWW0A4vGW3Um1gAlzvQ==$ adb install-multiple base.objection.objection.apk split_*.objection.apk
Success
launch the app on your device and then connect with objection
madf0x@pop-os:~/Documents/0xFA7E.github.io/example/com.ubercab-7q1ZWW0A4vGW3Um1gAlzvQ==$ objection explore
Using USB device `GM1915`
Agent injected and responds ok!
_ _ _ _
___| |_|_|___ ___| |_|_|___ ___
| . | . | | -_| _| _| | . | |
|___|___| |___|___|_| |_|___|_|_|
|___|(object)inject(ion) v1.11.0
Runtime Mobile Exploration
by: @leonjza from @sensepost
[tab] for command suggestions
com.ubercab on (OnePlus: 11) [usb] #
Tada! A successful split apk patch using nothing but objection. Honestly I’m not sure why this isn’t the most commonly recommended method. It’s far cleaner than completely rebuilding an apk and less error prone as well as it makes by far the minimal amount of required editing of the original application. I refuse to believe that my newbie fumbling was the first to discover how much easier this approach is but I couldn’t find any mention of this method no matter how hard I looked. I only figured it out because I was relentlessly searching up errors to try to grasp the full problem and experimenting with different solutions for hours on a little personal project I was working on. If anyone happens to know why this approach might not be viable or has better solutions feel free to shoot me a message