This post is the result of research presented at Recon Montreal 2022. Two slide decks are provided along with this research . One is the presentation showing the whole process and how to do it on Google Play Protect services. The other one is a workshop on how to do it on an emulator targeting MtpService.
Performing reverse engineering on Android applications and/or malware is often about doing static analysis that is complemented by dynamic analysis. This is an approach that works perfectly with applications that run at the user level and have a user interface. However, the Android security model allows for the existence of system applications, which can be operating system applications, operator applications or vendor applications. This does not mean that the applications require special permissions. They may be regular applications or service provider applications that are pre-installed either by a vendor or by the telecom service provider.
One of the features associated with these applications is that they are uninstalable by the user - The best the user can do is uninstall the latest updates and suspend or deactivate the applications, but never uninstall them.
There are also system applications that are part of the Android framework which, are closed source, thus require reverse engineering to be analyzed.
A popular tool to perform dynamic analysis is the Frida Framework. To use it a researcher needs to inject the Frida library into the target application’s address space. This can be done either by running the Frida daemon with root level access on the device or by patching the target application and making it load the shared library version of Frida. If the target application is headless, i.e. without a user interface, the instrumentation can only be done by forcing the load of the shared library and patching the target application code. Unless the researcher has root level access on the device and the is no requirement to inject the application at startup.
Doing such analysis should be easy by using an OEM version of the Android operating system; however, these images may not contain the target of our research which created the need to enable the instrumentation of system applications on Android stock images.
This, however, presented a bigger challenge than originally expected due to the protection mechanisms in place in the Android Operating system. Maddie Stone’s presentation at BlackHat 2019 describes both the limitations and vulnerabilities found on some system applications.
There are several limitations imposed by the Android security architecture, but also limitations imposed by the very nature of the availability of the target applications. This section will enumerate the limitations and solutions to those limitations.
Operating system limitations
It would be easy to obtain root level access on the operating system if one was to reflash the device with an open operating system like GrapheneOS however this presents two challenges:
1) Applications that are installed by vendors would not be present. Even though it is possible to install them, they may have checks regarding the operating system version they are running on. Eventually a static analysis along with patching could bypass such checks. But at this point the analysis environment would be tainted which could lead to biased results. Additionally there is also the risk of missing some checks.
2) Some vendors add protections to the kernel which won’t be present on this different operating system which tend to be more generic. As an example, Samsung ships most of their kernels with the SELinux permissions set to restricted without the possibility of change from user level.
System applications are often headless, meaning that they don’t have a launch activity or a user interface. Without it, tools like Frida or the debugger provided by the Android studio, are not able to start the application and debug it. Effectively the researcher doesn’t have the means to start the application or even to know, in a deterministic way, when the application will be executed.
One option could be the patching of the manifest and adding the launch activity. This poses its own problems, like deciding which existent activity would handle the request, and even checking if the code would be able to handle it. This leads to increased tampering with the application’s way of working, eventually creating unpredictable problems.
Enabling dynamic analysis on the application
On Android when doing dynamic analysis on a regular application the analyst can either activate the debug option on the application manifest or use an instrumentation toolkit like Frida. To activate the debugging the researcher needs to change the debuggable attribute (android:debuggable="true") in the Application section of the application manifest to true, and repackage the application.
To use Frida, assuming there is no root level or the target application is headless, the analyst would need to patch the application, make it load the Frida Gadget and give it the Internet permission. These changes, like the previous, also need a repackaging of the application.
Android requires that all application packages are digitally signed. In regular applications and in an open environment, the certificate that signs the application has little importance and a researcher can simply use a freshly generated self-signed certificate . However this is not the case if the target application is a system application. The sections ahead explain why.
Android security architecture
This section will describe the obstacles that a researcher must overcome to reach the point where it is possible to perform the dynamic analysis.
Digital certificate collision
As described above, in order to install an application its package needs to be digitally signed, which shouldn’t be a problem. However, there are a few situations where the certificate that signs the package actually matters. One of these cases, and for good security reasons, is when a user tries to upgrade an application. Imagine the following scenario;
An attacker is able to trick the victim into downloading and installing a fake Instagram application, posing it (to the system) as an upgrade to the original application. If there weren’t checks on the package signatures the malicious application would get access to all the data and configuration from the legitimate application. To avoid this, Android checks if the two applications are signed by the same certificate. If they are not, it won’t allow the installation of the fake upgrade. There are other ways to perform such an attack, but that is beyond the scope of this research.
This means that when the changes needed for dynamic analysis are made and the application is repacked, the patched application will not be able to be installed because the signatures will not match.
Replace the original application
Since the patched application cannot be installed on top of the original application, the obvious solution would be to simply uninstall the original one and replace it with the patched one. However this presents another barrier in this specific use case. As it was explained in the first section, these applications cannot be uninstalled. At best they can be deactivated but not uninstalled, making this a huge limitation to overcome in order to dynamically analyze these applications.
The fact that it's not possible to upgrade applications with different digital signatures is not the only limitation imposed. On the Android operating system, applications may share user IDs (UIDs). This is particularly useful if a developer wants to share information between applications using simple mechanisms provided by Linux without having to use mechanisms provided by the framework. However the security architecture imposes, and rightfully so, that applications that share the user ID must all be signed by the same certificate. Unfortunately, there are several system applications that share the same user ID, so it is likely that a researcher will bump into this limitation throughout their research.
The method to be able to dynamically analyze system applications on Android requires several actions.
As it was shown in the previous sections there are several obstacles that need to be overcome in order to dynamically analyze system applications on the Android platform.
Not necessarily in this order, but first the original target application needs to be uninstalled and removed, then identify if there are shared user ID applications. If these applications exist they need to be either uninstalled or re-signed with the same certificate used to sign the patched version of the target application. The image below illustrates the necessary steps.
Since system applications are often headless for the sake of this document, instrumenting target system application is the best way to perform non-interactive dynamic analysis.
This means that the application needs to be patched, and the instructions for it to load Frida library (gadget) into its address space must be added.
Before jumping into the details of the method it's important to explain the target and the environment. This research was conducted on the standard Android emulator using the Pixel 4 XL skin, an x86 image with API 30. For demonstration purposes the selected target application was the MtpService which is responsible for handling the MediaTransferProtocol setup when a new USB device is connected to the device. This is an open sourced application which was perfect for testing as the reversed code and behavior could always be compared with the original code.
Patching and re-signing target application
The first step to patch the target is to unpack it using apktool which can be found here along with the instructions to pack and unpack applications.
The place to actually perform the patching will be highly dependent on the target application, there is not “one spot fits all”. As a rule of thumb the library should be loaded as early as possible in the target application. A good way to do that is to load the library inside the method that handles the “BOOT_COMPLETED” action as this will be the first one to be executed upon device reboot.
The manifest will have the class that handles that action named on the receiver section. In the target application the class is called “com.android.mtp.MtpReceiver”. In this class look for the “onReceive()” method as that will be the method that will be executed once the action is broadcasted.
Using the apktool one can decompile the classes.dex and edit the smali code to load the library. The smali code should look like the image below.
Line 151 declares a variable string object referred to as v0 and stores the word “gadget” inside of it. Line 153 invokes the static method “loadLibrary()” which resides inside the System package. With a single String object as argument, the v0 object was previously declared. These two instructions are enough to load a static library into the application address space. The image below illustrates how the code looks like in Java.
Now before repacking the whole application the Frida library needs to be added into the package. In the root of the package (created by apktool after extraction) create a directory called “lib/” inside it and create a directory with the architecture of the library, in this case it's “x86” since this is the emulator. Inside this directory place the library file.
Now, because of the Android installer there are a few tweaks that need to be done. First the library file must start with the prefix “lib” and end with the extension “.so”. So even though the instructions make the system load a library called “gadget”, the file name must be “libgadget.so”. Frida gadget needs a configuration file, whose details will be addressed in the next section. This file also needs to be inside the package so that it is made available upon the loading of the library. Frida gadget will look for a configuration file with the same name as the library itself but with an extension “.config”. So as per Android requirements this file must be called “libgadget.config.so”.
Conforming to these naming criteria will ensure that upon installation the patched version of the target application will load the Frida gadget once the device is rebooted. Once all the files are in place, the application needs to be repacked into an APK file using apktool.
Signing the target application
Now the package needs to be re-signed. The certificate can be generated with a simple command such as :
Keep in mind that this same certificate may be needed to sign applications that share the user ID (UID) with your target application. Before the package can be signed the zip file needs to be aligned which is done with the command below.
Afterwards the package should be signed using the apksigner with the command line below:
Searching for shared ID applications
As It happens, the MtpService application installation still fails with the error below.
This means that there are applications that share the same userid but are signed with a different certificate. This can be confirmed by checking the manifest snippet below.
There is a property called “android:sharedUserId” which is defined and set to “android.media”, meaning that there are other applications that use the same userid. Any application using this shared userid must be a system application. A not very elegant but effective method to find all the applications is to download all the system applications, extract and convert the manifest into a text format and search for the shared id property.
Once all of these are known, they can either be removed, using the same technique described in the previous section; OR they can be replaced with a version signed with the same certificate. The latter is the preferred action because there may be unforeseen dependencies between the applications.
Uninstall/replace the original target application package
This is a crucial step in this method, without which Android security architecture will never allow the installation of the patched version of the target application. So in order to do this a tool called Magisk, will have to be installed. Magisk is a “rooting” tool that will allow users to obtain root level access on their devices without changing the whole operating system image. The tool provides a method for users to patch the kernel which can then be re-flashed into the device.
This method is the least intrusive possible, while allowing root access. Because these are system applications analysis, the root access is not that relevant (keep in mind that the application must be patched to have the Frida library in its own code) since there is no need to run Frida server as a daemon with high privileges.
Magisk has another crucial feature: It allows a user to define a virtual filesystem which will be laid on top of the actual file system at boot time. This allows any folder or file to be remapped to the version replacing the original ones flashed into the partitions upon system creation.
The first step is to install Magisk on the system that will be used for the analysis. The installation itself is beyond the scope of this article, but once installed you should be able to see the home screen of the Magisk Application, and should look something like the image below.
The application being targeted here is stored at “/system/priv-app/MtpService/” and the image below shows that's where the application package is located at.
If one tries to delete the package manually, one can see that “/system” directory is mounted on a read-only filesystem which, as seen below, doesn’t allow the file to be deleted, even though the files are owned as root.
So the way to address this is to use what Magisk calls “modules”, where in reality a user can define a filesystem path to be overlaid by Magisk on top of the real path. Thus changing the contents of the real path. The overlay can simply delete a full path or replace it with one’s own contents.
The Magisk modules configuration is stored in “/data/adb/modules”, the full module documentation can be found here, and in this case one just needs to create a directory with the name desired for the module to have and recreate the path to overlay. By adding the file “.replace” on the last directory, one simply replaces that directory by an empty one.
The image above shows the empty directory after the module has been created.
Now that all the obstacles have been overcome, the patched application can simply be installed and the device rebooted. This should start the patched application with the instrumentation toolkit embedded in it and ready to use.
Updated: 13 Jan 2023 - Added extra explanation about Frida hooking limitation on headless applications