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, 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 and forensic analysis, 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 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:

Monitor capabilityPerms to searchURIs/stringsMethods
SMS Messages"android.permission.READ_SMS"sms/inbox, content://smsgetMessageBodygetOriginatingAddress from class android/telephony/SmsMessage
sendTextMessagedivideMessage
from class android/telephony/SmsManager
query (with the string: content://sms) 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_logquery (with the string: content://call_log )
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
from class android/media/MediaRecorder
acquireLatestImage
from class android/media/ImageReader
Web Navigation"android.permission.INTERNET"WebViewURLConnectionhttp://https://parse
from class android/net/Uri
getInputStream
from class java/net/HttpURLConnection
openConnection
from class java/net/URL
Location"android.permission.ACCESS_FINE_LOCATION",
"android.permission.ACCESS_COARSE_LOCATION"
"android.permission.ACCESS_BACKGROUND_LOCATION"
locationgetLastKnownLocation
from class android/location/LocationManager
Apps contentpackage 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)
from class android/content/ContentResolver
class android/provider/ContactsContract
Calendar Entries"android.permission.READ_CALENDAR"calendar, content://com.android.calendarquery
from class android/content/ContentResolver
class android/provider/CalendarContract
Keystrokes"android.permission.BIND_ACCESSIBILITY_SERVICE"AccessibilityServiceperformAction, getText
from class android/view/accessibility/AccessibilityNodeInfo
class android/accessibilityservice/AccessibilityService
Microphone & Camera activation"android.permission.RECORD_AUDIO",
 "android.permission.CAMERA"
miccamerasetAudioSource
from class android/media/MediaRecorder
takePicture, open
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 mobSFand 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:

Bash
// 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.

FilenameRuleConfidence
00077.jsonRead sensitive data(SMS, CALLLOG, etc)100%
00035.jsonQuery the list of the installed packages100%
00202.jsonMake a phone call80%
00002.jsonOpen the camera and take picture80%
00094.jsonConnect to a URL and read data from it100%
00048.jsonQuery the SMS contents100%
00126.jsonRead sensitive data(SMS, CALLLOG, etc)60%
00201.jsonQuery data from the call log80%
00189.jsonGet the content of a SMS message100%
00083.jsonQuery the IMEI number60%
00186.jsonControl camera to take picture100%
00076.jsonGet the current WiFi information and put it into JSON60%
00191.jsonGet messages in the SMS inbox80%
00142.jsonGet calendar information100%
00160.jsonUse accessibility service to perform action getting node info by View Id100%
00010.jsonRead sensitive data(SMS, CALLLOG) and put it into JSON object60%
00112.jsonGet the date of the calendar event60%
00011.jsonQuery data from URI (SMS, CALLLOGS)100%
00161.jsonPerfom accessibility service action on accessibility node info80%
00173.jsonGet bounds in screen of an AccessibilityNodeInfo and perform action80%
00200.jsonQuery data from the contact list80%
00075.jsonGet location of the device100%
00053.jsonMonitor 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.

In the next section, we will cover dynamic analysis of the sample to complement what we have seen so far.

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`)