Following the initial triage and forensic analysis, which flagged a suspicious application, in this phase we will conduct an in-depth analysis of that application. This will involve both static analysis, where we decompile and examine the app’s source code, and -in the upcoming section- [dynamic analysis](./4-dynamic-analysis.md), where we observe the app’s behavior while it runs, including intercepting network traffic.
1. Lab setup
Security measures
When analyzing malicious apps, it is important to ensure a safe testing environment:
Malware handling (during static analysis): store any malicious app samples in a password-protected `zip` file to prevent accidental spread outside of your controlled environment.
Dedicated environment (during dynamic analysis): always install or run malicious apps on a dedicated device intended solely for lab purposes or within an emulator. In this guide, we use an emulator because it runs in isolation from our host system and, by default, restricts access to real hardware components such as the microphone. The app, when enabled, will monitor the microphone, camera, and location, so it’s essential that the emulator’s default settings are maintained to disable real hardware access and use simulated data. The emulator’s camera is set to display a static image, and the microphone input is disabled by default, preventing the app from recording any real audio (It’s crucial to leave these settings unchanged). Additionally, the emulator allows you to simulate GPS locations, enabling you to control what location data the app receives without exposing your actual location.
VPN usage (during dynamic analysis): use a VPN to prevent your `ip` address from being exposed. In our testing, the malicious app running in the emulator communicates with a remote server, which could potentially expose your real `ip` address. A Virtual Private Network (VPN) reroutes your internet traffic through a remote server, masking your real `ip` with the VPN server’s `ip`.
Additional security: be aware of and implement any additional hardening and security measures that your specific setup may require.
Tools installation
Install `jadx` on the forensic workstation: a tool for decompiling binary app files (`.apk`) to obtain the source code for analysis. Visit the official release page on GitHub and download the cross-platform zip file.
After downloading, unzip the file and run it from the command line:
“`bash
// Ensure Java installed
[host]$ sudo apt install openjdk-11-jdk
// Run jadx gui
[host]$ cd /path/to/jadx/bin
[host]$ ./jadx-gui -h
“`
You should see an output similar to:
2. Static analysis
Following the [initial triage](./1-triage.md) and [forensic analysis](./2-forensics.md), which flagged a suspicious application, we now perform a static analysis to further understand the app’s capabilities by examining its source code. This phase will allow us to identify any suspicious code and understand how the app might be interacting with the system and user data.
The first step is to launch `jadx-gui` via the command line, passing the malicious app as a parameter. This app was previously downloaded from the phone using `AndroidQF`. During the forensic phase in Part 2, we selected the option in `AndroidQF` to download the apps, which saved a copy of them in the output folder -in our case- located at `./b0dbb2ca-47c0-4221-a3d6-a0b2a8ff6ec7/apks/`. By filtering for the malicious app named `com.systemservice`, we find the malicious app binary (`.apk`). To facilitate analysis, we have shared a sample of the malicious `.apk` in a password-protected `.zip`[file](./to-share/docs.zip) with the name `com.systemservice.zip`, with the password set to “infected”.
“`bash
[host]$ ./jadx-gui com.systemservice.apk
“`
`jadx-gui` should now be open, displaying the decompiled code of the malicious app for analysis.
Manifest Analysis
The first step in assessing the app’s capabilities is to examine its Android Manifest file after decompiling the app using `jadx`. You can locate this file by navigating to the Resources folder and finding the `AndroidManifest.xml`. Once you find it in the left pane, double-click to display its contents.
As we will see next, the Manifest provides a comprehensive overview of the app’s structure, including its name, permissions, and key components.
App package name
Android package names typically follow a convention similar to Java’s inverted domain name structure (such as `com.app.name`). This naming convention is designed to ensure that each app has a unique identifier across the Android ecosystem. In this case, the package name `com.systemservice` might be intentionally misleading, giving the impression of a legitimate system service when it is actually a malicious app instead.
The app’s package name, `com.systemservice`, can be found in the Manifest file, specifically within the `<manifest>` tag under the package attribute.
As we saw during the forensic phase, the package name `com.systemservice` was flagged by `mvt` as a known Indicator of Compromise (IOC), identifying it as associated with the stalkerware `TheTruthSpy`.
App display name
To identify the app’s display name on the device we can also check the `AndroidManifest.xml` file using `jadx` and search for the `android:label` attribute in the `<application>` tag. This attribute often points to the app’s display name.
The `@string/app_service` reference points to a string resource defined in the `strings.xml` file, typically located in the `res/values/` directory of the app’s project structure. You can use the Text search feature in `jadx` to search directly for the string `app_service` to find out the actual app name the user sees in the phone displayed.
That search shows a match in the resource section (generally containing additional static files and files within an app), confirming that “Google services” is the name displayed in the UI by this app on the infected Android device.
During the initial triage, we identified a suspicious app with the display name “Google services.” Simultaneously, `mvt` flagged the package name `com.systemservice` as malicious, associated with known IOCs. By analyzing the Android Manifest file, we can now confirm that these two identifiers -“Google services” as the display name and `com.systemservice` as the package name- refer to the same app we are analyzing.
Permissions requested by the app
The Manifest also lists the permissions that the app requests. These permissions provide insight into the capabilities of the malicious app:
Several concerning permissions were identified in the Manifest, indicating the app’s potential to abuse user data and privacy:
Location tracking: the app requests permissions such as `ACCESS_FINE_LOCATION`, `ACCESS_COARSE_LOCATION`, and `ACCESS_BACKGROUND_LOCATION`, which enable it to track the device’s location and even while the app is in the background.
Reading contacts, SMS, call logs: with permissions like `READ_CONTACTS`, `READ_SMS`, `RECEIVE_SMS` and `READ_CALL_LOG`, the app can access the user’s contacts, text messages and call history.
Microphone and camera access: the app has `RECORD_AUDIO` and `CAMERA` permissions, allowing it to access the device’s microphone and camera, enabling the app to potentially record audio and capture images or videos.
This aligns with the findings from the forensic stage, where `mvt` also flagged this app for requesting several potentially dangerous permissions.
Analyzing key components of the app with `jadx`
Analyzing an app’s code can be complex and time-consuming. We use the Manifest as a guide to identify key components. In this analysis, we will focus on three key points:
1. The entry point when the app starts (from a system perspective): `”com.systemservice.common.groupService.UIAppController”`.
2. The main component of the app that abuses Accessibility services.
3. Additional capabilities of the app.
Understanding Android app components:
Android apps are structured with the following components, each serving a specific role:
Activities: these are the entry points for user interactions with the app. Each activity typically represents a single screen with a user interface.
Services: these are background components that perform long-running operations without a user interface. They can continue running even when the user isn’t interacting with the app.
Broadcast Receivers: these components allow the app to listen for system-wide broadcast messages, such as when the device is charging or a call is received. They can initiate other components of an app in response to a system event.
Content Providers: these components manage app-specific data. They handle data access and provide a consistent interface for querying and modifying this data.
1. Analyzing the app’s entry point
There are two main entry points according to the Manifest:
-`com.systemservice.common.groupService.UIAppController`, defined within the `<application>` tag.
-`com.systemservice.UIFirstActivity`, defined within the `<activity>` tag.
Here’s the relevant snippet from the manifest:
From this snippet, we can identify the following:
System entry point: the `”com.systemservice.common.groupService.UIAppController”` class, specified in the `android:name` attribute of the `<application>` tag, is the first component initialized when the app starts. This class is responsible for managing the global state of the app and performing necessary initializations before other components are created.
User interface entry point: The `com.systemservice.UIFirstActivity` activity, indicated by the `android.intent.action.MAIN` and `android.intent.category.LAUNCHER` attributes, serves as the main entry point from a user’s perspective.
> Note: each dot (`.`) in the class name corresponds to a folder within the decompiled app’s directory structure
This activity is launched when the user taps the app icon in the phone.
The code within `UIAppController` (in `com/systemservice/common/groupService/UIAppController`) gives us insight into the app’s capabilities through its modules names mentioning contacts, sms, calls, navigation monitoring, location and audio recording:
2. Component related to the abuse of Accessibility services:
Accessibility services in Android are designed to assist users with disabilities by providing apps the ability to interact with the device on a deeper level. However, when misused, these services can give an app extensive control over the device, enabling it to monitor and log user actions, read screen content, and perform actions on behalf of the user.
To understand how the app might be abusing these services, we need to analyze the code that processes and stores data captured through Accessibility services. Specifically, we’ll focus on the methods that handle accessibility events and any logging functions that could capture sensitive user inputs, such as keystrokes or on-screen text.
Misuse of Accessibility services can grant the app several powerful capabilities, such as capturing any text displayed on the device, that can include sensitive information like passwords or messages.
To understand how the app binds to the Accessibility Services to exploit its functionality, we can examine two key places in the source code: the Manifest file and the configuration in the `accessibility_service.xml` file.
Manifest declaration
The Accessibility Service Permission (`BIND_ACCESSIBILITY_SERVICE`) is declared within a `<service>` component. Unlike standard permissions declared with `<uses-permission>`, this permission is tied directly to specific app components, such as a service. This binding enables the app to utilize the Accessibility API, allowing it to track user behavior and read on-screen content.
`accessibility_service.xml` configuration file
The `accessibility_service.xml` file (inside `Resources/res/xml` folder in `jadx`) is the resource file that defines the configuration settings for an Accessibility Service in Android. This file specifies how the Accessibility Service should behave and what types of events it should listen for. The settings in this XML file determine the level of access and control the service has over the device, such as whether it can retrieve the content of the windows and the types of accessibility events it monitors.
In our app, the configuration allows the app to capture a broad range of accessibility events (`android:accessibilityEventTypes=”typeAllMask”`) and retrieve window content (`android:canRetrieveWindowContent=”true”`). This means the app can monitor virtually all interactions on the device and read whatever is displayed on the screen, making it capable of capturing sensitive information like passwords, messages, among others.
Examining the UIAccessibilityService Class
From examining the manifest, we can identify that `UIAccessibilityService` is the key component in the app that abuses the accessibility feature.
The service class `UIAccessibilityService`, referenced in the manifest as `android:name=”.common.boostReceiver.UIAccessibilityService”`, is located in the codebase under the path `com/systemservice/common/boostReceiver/UIAccessibilityService`.
> Note: each dot (`.`) in the class name corresponds to a folder within the decompiled app’s directory structure, and the initial dot (`.`) references the app’s base package name, `com.systemservice`.
To locate the decompiled code for that class, navigate through the folders in the left pane, beginning from the Source code folder.
In static code analysis, understanding the lifecycle of each component is important because it reveals the typical sequence of method calls that occur automatically during the app’s execution. Each Android component —such as activities, services, broadcast receivers, and content providers— has a defined lifecycle that indicates and when certain of their methods are invoked by the system. For an Activity, methods like `onCreate()`, followed by `onStart()` and `onResume()`, are called by the system at different stages of the activity’s existence. Therefore those methods should be the ones we need to review as we know the system will automatically call them.
For a Service, the sequence typically starts with `onCreate()`, and if present, `onStartCommand()` handles the subsequent commands sent to the service after it is started. In this case, we’ll focus specifically on the `onAccessibilityEvent()` method of the service class as it is directly related to processing accessibility events we are interested in at this point.
In this app, this method calls auxiliary functions to log or process this data further. For example, on our app the `onAccessibilityEvent()` calls an auxiliary function within the same class, that is responsible for identifying the source application of an event (ex. `com.snapchat.android` for Snapchat app or `com.skype.raider` for Skype app) and logging this information:
Additionally, within the `onAccessibilityEvent()` method, the text content associated with the accessibility event is logged using the `getText()` method of the `AccessibilityEvent` class. This might include text that the user has typed or text that is being displayed on the screen.
“`java
public void onAccessibilityEvent(android.view.accessibility.AccessibilityEvent r20) {
// Retrieves the text content associated with the event
StringBuilder sb = new StringBuilder();
sb.append(“password1 = “);
// retrieves the text associated with the Accessibility event
List<CharSequence> textList = accessibilityEvent.getText();
String text = textList.toString();
sb.append(text);
// continues logging sensitive information
(…)
}
“`
Unfortunately, `jadx` is unable to decompile to `Java` code this specific code snippet within the `onAccessibilityEvent()` method correctly. Instead, the original output from `jadx` appears directly in `smali` code (low-level, human-readable representation of Android bytecode) as follows.
Although analyzing Smali code is out of the scope of this guide, it’s worth noting that needing to read Smali instead of Java decompiled code is common due to decompiler limitations.
3. Additional capabilities
While it’s beyond the scope of this guide to analyze every aspect of the app in detail, there are several strings and patterns you can look for in the source code to identify other capabilities of the app. These include permissions declared in the manifest, API calls that enable malicious functionality (such as accessing the device’s camera or microphone), and Content Provider URIs, which are used to access various types of data on Android devices such as SMS messages, call logs, contacts, and media.
By searching for these strings in the decompiled code using `jadx` “Search text” functionality, you can get an initial understanding of the app’s potential for malicious behavior. However, keep in mind that the code might be obfuscated, which could make it more challenging to identify these strings. Also, the presence of these strings could be part of dead code (that is never executed by the app) or require higher privileges (like root access) to function.
Below is a non-exhaustive list of strings you can search for, which may indicate further capabilities:
Methods |
|——————————|———————|——————|————-|
| SMS Messages | `”android.permission.READ_SMS”` | `sms/inbox`, `content://sms`| getMessageBody, getOriginatingAddress</br> from class `android/telephony/SmsMessage`</br>sendTextMessage, divideMessage</br> from class `android/telephony/SmsManager`</br>query (with the string: `content://sms`) </br> from class `android/content/ContentResolver` |
| Call Logs | `”android.permission.READ_CALL_LOG”`, `”android.permission.READ_PHONE_STATE”`, `”android.permission.CALL_PHONE”`, `”android.permission.PROCESS_OUTGOING_CALLS”` | `CallLog.Calls.CONTENT_URI`, `call_log` | query (with the string: `content://call_log`) </br> from class `android/content/ContentResolver` |
| Stored Media | `”android.permission.READ_EXTERNAL_STORAGE”`| `content://media/external`, `MediaStore.Images.Media.EXTERNAL_CONTENT_URI`, `content://media` | setOutputFile, start</br> from class `android/media/MediaRecorder`</br>acquireLatestImage</br> from class `android/media/ImageReader` |
| Web Navigation | `”android.permission.INTERNET”` | `WebView`, `URLConnection`, `http://`, `https://` | parse</br> from class `android/net/Uri`</br>getInputStream</br> from class `java/net/HttpURLConnection`</br>openConnection</br> from class `java/net/URL` |
| Location | `”android.permission.ACCESS_FINE_LOCATION”`, `”android.permission.ACCESS_COARSE_LOCATION”`, `”android.permission.ACCESS_BACKGROUND_LOCATION”` | `location` | getLastKnownLocation</br> from class `android/location/LocationManager` |
| Apps content | | package names such as `”com.whatsapp”`, `”com.facebook.katana”`, `”com.instagram.android”`, `”com.twitter.android”` | key files: `/data/data/com.whatsapp/databases/msgstore.db` |
| Contacts | `”android.permission.READ_CONTACTS”` | `content://contacts`, `ContactsContract.Contacts.CONTENT_URI` | query (with the string: `content://contacts`) </br> from class `android/content/ContentResolver`</br> class `android/provider/ContactsContract` |
| Calendar Entries | `”android.permission.READ_CALENDAR”` | `calendar`, `content://com.android.calendar` | query</br> from class `android/content/ContentResolver`</br> class `android/provider/CalendarContract` |
| Keystrokes | `”android.permission.BIND_ACCESSIBILITY_SERVICE”` | `AccessibilityService` | performAction, getText</br> from class `android/view/accessibility/AccessibilityNodeInfo`</br> class `android/accessibilityservice/AccessibilityService` |
| Microphone & Camera activation | `”android.permission.RECORD_AUDIO”`, `”android.permission.CAMERA”` | `mic`, `camera` | setAudioSource</br> from class `android/media/MediaRecorder`</br>takePicture, open</br> from class `android/hardware/Camera` |
For example, when investigating SMS access, the Manifest indicates that the app requests the `android.permission.READ_SMS` permission, which allows it to read SMS messages stored on the device. To investigate further, we search for the string `”content://sms”` in the source code. This search reveals a snippet where the app queries the SMS content provider (via the URI `content://sms`) to retrieve SMS messages sent or received within a specified date range.
Here’s the relevant code snippet (inside the class `p152s5.C2743b`):
Semi-automated analysis with `mobSF`and `quark`
In addition to the manual review of the decompiled source code using `jadx`, we can complement the analysis with other tools.
`MobSF`
The Mobile Security Framework (`mobSF`) is an automated, multi-architecture tool that assists in analyzing the behavior of a malicious app based on is `.apk` file.
Using `mobSF`:
You can submit a malicious app directly to their live instance, uploading the `.apk` file from the infected device to `https://mobsf.live/`. However, keep in mind that you will be sharing the APK with them. Ensure that it is a known malware by verifying its hash with VirusTotal first as we saw before in the forensic phase. If it is unknown, consider whether you want to share the sample with third parties.
Additionally, you can run `mobSF` locally by following the instructions provided here using `docker`. A useful guide on installing Docker in a GNU/Linux system is available here.
To run `mobSF` locally, you can use the following commands:
“`
// Pull the mobSF Docker image
[host]$ sudo docker pull opensecurity/mobile-security-framework-mobsf:latest
// Run mobSF Docker container
[host]$ sudo docker run -it –rm -p 8000:8000 opensecurity/mobile-security-framework-mobsf:latest
“`
After running the above command, you can access `mobSF` by navigating to `127.0.0.1:8000` in your browser. The default credentials are `mobsf/mobsf`. Once logged in, upload the malicious `.apk` file and wait for `mobSF` to analyze it.
Analyzing `mobSF` output
1. Summary of the app: `mobSF` provides a summary of the app details such as the app’s name, package name and hash values, along with a security score for the app.
2. App components: `mobSF` also summarizes the app’s components, including activities, services, receivers, and content providers present in the code.
3. Code explorer: `mobSF` allows you to view the app’s `AndroidManifest.xml`, source code in Java, and Smali code directly through a code explorer or by downloading the source code.
Here’s a snippet from the code explorer in MobSF:
Abusive permissions: `mobSF` lists the permissions requested by the app, categorizing them into “Top Malware Permissions” and “Other Common Permissions”.
It also explains the potential danger of each permission, particularly those that allow access to sensitive user data.
Using mobSF adds an automated layer of analysis, helping to quickly identify suspicious behaviors and permissions, which complements the manual code review for a more comprehensive investigation.
`Quark`
quark is an open-source tool designed to help identify suspicious behavior in a given apk . The tool comes with several pre-defined rules and also allow you to create your own. These rules contain specific strings, methods, and class names that are matched against the decompiled code of the app.
“`bash
// Install quark
[host]$ pip3 install -U quark-engine
// Download rules
[host]$ freshquark
// List and inspect rules downloaded
[host]$ ls /home/<user>/.quark-engine/quark-rules/rules
// Run quark and filter results by confidence level > 60%
[host]$ quark -s -a TheTruthSpy.apk -t 60
“`
When running `quark` we consider only those matches with a confidence percentage of 60% and above, as lower scores are likely false positives.
Analyzing the output of `quark`
When analyzing with `quark` our malicious app, the tool identifies potential suspicious behaviors.
The table below lists some of the rules flagged by quark that may indicate malicious capabilities within
the app worth investigating further reading the code of the app. The content of each of these rules is available by the filename here.
| Filename | Rule | Confidence |
|————|—————————————————————————-|————|
| 00077.json | Read sensitive data(SMS, CALLLOG, etc) | 100% |
| 00035.json | Query the list of the installed packages | 100% |
| 00202.json | Make a phone call | 80% |
| 00002.json | Open the camera and take picture | 80% |
| 00094.json | Connect to a URL and read data from it | 100% |
| 00048.json | Query the SMS contents | 100% |
| 00126.json | Read sensitive data(SMS, CALLLOG, etc) | 60% |
| 00201.json | Query data from the call log | 80% |
| 00189.json | Get the content of a SMS message | 100% |
| 00083.json | Query the IMEI number | 60% |
| 00186.json | Control camera to take picture | 100% |
| 00076.json | Get the current WiFi information and put it into JSON | 60% |
| 00191.json | Get messages in the SMS inbox | 80% |
| 00142.json | Get calendar information | 100% |
| 00160.json | Use accessibility service to perform action getting node info by View Id | 100% |
| 00010.json | Read sensitive data(SMS, CALLLOG) and put it into JSON object | 60% |
| 00112.json | Get the date of the calendar event | 60% |
| 00011.json | Query data from URI (SMS, CALLLOGS) | 100% |
| 00161.json | Perfom accessibility service action on accessibility node info | 80% |
| 00173.json | Get bounds in screen of an AccessibilityNodeInfo and perform action | 80% |
| 00200.json | Query data from the contact list | 80% |
| 00075.json | Get location of the device | 100% |
| 00053.json | Monitor data identified by a given content URI changes(SMS, MMS, etc.) | 100% |
Conclusion
Based on our research, the app has the capability to monitor SMS content, contact information, calendar events, call logs, location information, and keystrokes vis the abuse of Accessibility services. These capabilities are need highly invasive permissions are granted and accessibility services are enabled on the device. During the triage phase, we confirmed that these permissions and settings were indeed active on the infected phone.
Additionally, we identified that the app disguises its behavior by using the app name “Google Services,” impersonating a legitimate Google service, and using the Google Services icon.
This guide was created by tes and is shared under Creative Commons BY-NC-SA license; for any errors or enhancements, please share your feedback via email (`[email protected]`) or keybase (`https://keybase.io/texturas`)