Intermediate guide - How to statically analyze a potentially malicious Android app

Posted by Esther Onfroy ‐ 25 min read

Introduction

Static analysis is a crucial technique in the realm of Android application reverse engineering. It involves examining the application’s code and resources without actually executing it. This approach allows for the identification of potential vulnerabilities, malicious code, and hidden functionalities that could pose a threat to users’ privacy and security.

This guide delves into the intricacies of conducting static analysis on suspicious Android applications. It elucidates the key technical aspects to be scrutinized and investigated, empowering reverse engineers and security professionals to effectively assess potential risks.

Prerequisites

Before embarking on the static analysis journey, ensure you have the following prerequisites in place:

  • APKTool: APKTool is a versatile tool for unpacking and reassembling Android application packages (APKs).
  • Java decompiler: A Java decompiler, such as Jadx-gui or Procyon, is essential for examining the application’s bytecode.
  • Static Analysis Tools: Dedicated static analysis tools, such as MobSF, DroidLysis or Pithus, can automate the detection of common vulnerabilities and malicious patterns.
  • Native Code Reverse Engineering tools: A tool such as Ghidra, IDA or radare2 is recommended to conduct more in depth analysis of native code. However, this guide will not cover this kind of analysis.

Technical aspects to investigate

At this point, we will be using Jadx-gui and focusing on the analysis of the decompiled Java code as well as the content and resources of the Android application we want to analyze.

Beware, many Android packages are now packed. Packers typically encrypt a payload DEX (or at least hide its existence, opacify a payload blob etc). So, when reversing a state-of-the-art sample, you should first check if your sample is packed (use apkid or droidlysis, or check manually for use of DexClassLoader). If your sample is not packed, good, go on with your static analysis. If your sample is packed, don’t waste any more time analyzing the packer: unpack the sample first and retrieve the payload DEX and analyze that.

Android manifest

The AndroidManifest.xml file resides within the depths of the application’s APK file, typically in its root directory. To unveil its contents, extract the APK file and utilize a text editor or a specialized manifest viewer tool to reveal its secrets.

The package name serves as the application’s unique identifier, akin to a digital fingerprint. It resides within the manifest tag, declared using the package attribute. This crucial piece of information specifies the application’s namespace and provides a reference point for its components.

The manifest serves as a blueprint for the application’s components, outlining its activities, services, broadcast receivers, and content providers. Each component is defined using its own XML element, adorned with attributes that dictate its properties and intent filters.

The uses-permission tags define the permissions that the application seeks from the Android system. These permissions determine the sensitive data or system features that the app can access, granting it the right to interact with the device’s capabilities.

The uses-feature tags define the hardware and software prerequisites that the application needs for its proper functioning. This information ensures that the app can operate seamlessly on the intended devices.

Intent filters serve as communication protocols, defining the types of intents that an application’s components can respond to. These filters orchestrate the app’s interactions with other apps and system events, enabling seamless inter-connectivity.

The uses-sdk tag specifies the minimum and target SDK versions that the application supports, ensuring compatibility across different Android versions.

The findings in the manifest should be corroborated with a thorough analysis of the application’s code. This meticulous comparison can uncover discrepancies between declared permissions and actual code behavior, exposing potential security vulnerabilities or hidden functionalities.

Understanding the core components that drive an application’s functionality is crucial. Let’s delve into intents, services, activities, and receivers.

Intents serve as messengers, carrying information and instructions between different parts of the Android system. They act as a bridge between applications, enabling them to communicate and initiate actions. An intent encapsulates the desired action, the data to be acted upon, and any additional parameters. For example, the well-known “share with” action on most mainstream app uses intents to forward the content to be shared.

Services operate behind the scenes, performing tasks without requiring direct user interaction. They run in the background, even when the application is not in the foreground, handling tasks like playing music, downloading data, or sending notifications. Services enhance an application’s capabilities without disrupting the user experience.

Activities represent the user interface of an application, providing the visual elements and interactive components that users interact with. They form the building blocks of an app’s navigation, allowing users to transition between different screens and perform actions. Each activity focuses on a specific task or functionality within the application.

Receivers act as listeners, responding to system-wide events or broadcasts. They remain dormant until triggered by a specific event, such as incoming calls, SMS messages, or changes in device state. Once activated, receivers can perform tasks like updating application data or notifying the user.

Permissions

The static analysis involves scrutinizing the permissions requested by the application. This provides valuable insights into the application’s intended behavior and potential access to sensitive data. Excessive or unnecessary permissions can raise red flags, indicating potential misuse of user information or unauthorized access to device functionalities. For instance, a simple flashlight app requesting access to contacts or location data should raise suspicion.

Evaluating permissions is a fundamental step in the static analysis of suspicious Android applications. Permissions act as gateways to sensitive user data and device functionalities, and their thorough examination can expose potential security risks and privacy violations.

Here follow some permission that should raise suspicion depending on the application’s purpose:

READ_CONTACTS and WRITE_CONTACTS: These permissions demand scrutiny when an application’s stated purpose does not align with accessing or modifying contacts. Unauthorized access to contacts could lead to data harvesting and potential identity theft. For instance, a simple game requesting access to contacts raises suspicion, as it has no legitimate need for such information.

ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION: These permissions raise concerns if the application’s core functionality does not involve location-based services. Unauthorized access to location data could enable tracking and profiling of user movements, compromising privacy. For example, a weather app may legitimately require coarse location data, but a flashlight app requesting precise location tracking is a red flag.

READ_SMS and SEND_SMS: These permissions raise red flags if the application’s purpose does not involve sending or receiving SMS messages. Unauthorized access to SMS could enable interception of sensitive information, such as two-factor authentication codes or personal communications. For instance, a social media app requesting SMS permissions raises suspicion, as it has no legitimate need to access SMS data.

RECORD_AUDIO: This permission raises concerns if the application’s functionality does not involve audio recording. Unauthorized audio recording could lead to eavesdropping and privacy violations. For example, a voice recorder app may legitimately require audio recording permissions, but a photo editing app requesting such access is suspicious.

CAMERA: This permission raises suspicion if the application’s purpose does not involve using the device’s camera. Unauthorized camera access could enable capturing images or videos without the user’s consent, thus compromising privacy. For instance, a calculator app requesting camera permissions is a red flag, as it has no legitimate need for camera access.

READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE: These permissions raise concerns if the application’s functionality does not involve accessing or modifying files on external storage. Unauthorized access could lead to data theft or modification of sensitive information. For example, a file manager app may legitimately require external storage access, but a game requesting such permissions raises suspicion.

READ_PHONE_STATE: This permission raises red flags if the application’s purpose does not involve accessing device identifiers or phone state information. Unauthorized access could enable tracking and profiling of user behavior. For instance, a messaging app may legitimately require phone state information for push notifications, but a game requesting such access is suspicious.

RECEIVE_BOOT_COMPLETED: This permission raises suspicion if the application’s functionality does not require automatic startup at boot time. Unauthorized auto-start could enable persistent background activity and unauthorized data collection. For example, an alarm clock app may legitimately need to start at boot, but a game app requesting such permission raises concerns.

By meticulously examining these permissions and comparing them to the application’s stated purpose, you can identify discrepancies and potential misuse of user data. Excessive or unnecessary permissions should raise red flags, prompting further investigation into the application’s behavior and potential malicious intent.

Code Obfuscation

Code obfuscation is a technique employed by developers to protect their intellectual property or make reverse engineering more challenging. However, malicious actors may also use obfuscation to conceal malicious code or hinder analysis efforts. Examining the code for obfuscation techniques, such as renaming variables, adding junk code, or using encryption, can help identify potential attempts to hide malicious intent.

Identifying the type of code obfuscation used in an Android application by examining decompiled Java code requires careful analysis of code structure, naming conventions, and control flow patterns. Here are some indicators to look for:

  1. Meaningless or random identifiers: Obfuscated code often replaces meaningful variable, method, and class names with random or nonsensical identifiers. This makes it difficult to understand the code’s purpose and functionality.

  2. Unnecessary code structures: Obfuscation techniques may introduce redundant or unnecessary code structures, such as nested loops, conditional statements, or dummy variables. These structures aim to confuse analysis tools and make the code harder to follow.

  3. Control flow modification: Obfuscators may alter the control flow of the code by introducing opaque branching, opaque predicates, or dead code. This makes it challenging to trace the execution path and understand the program’s logic.

  4. String encryption: Obfuscated code may encrypt or encode strings to hide sensitive information or make it difficult to identify specific strings during analysis.

  5. Reflection and dynamic loading: Obfuscators may employ reflection or dynamic loading techniques to hide the actual code being executed. This makes it harder to statically analyze the code and understand its behavior.

  6. Native code integration: If the application includes native code components, obfuscation techniques may be applied to the native code as well. This can make it challenging to analyze the interactions between Java and native code.

By identifying these patterns and anomalies in the decompiled Java code, you can determine the type of obfuscation used and gain a better understanding of the techniques employed to conceal the code’s true intent.

Native Code

Native code, written in languages like C or C++, can bypass the security restrictions of the Android runtime environment. Identifying native code calls within the application’s bytecode is crucial, as these calls could potentially execute harmful actions or access sensitive data without the safeguards provided by the Android framework.

To identify the invocation of native code in an Android application by examining the decompiled Java code, you can follow these steps:

  1. Identify native libraries by looking for the System.loadLibrary() method in the decompiled Java code. This method loads a native library, which is a shared object file (.so) containing native code.

  2. Search for native method declarations by looking for methods declared with the native keyword. These methods are stubs that indicate the presence of native code implementations.

  3. Examine method calls by identifying method calls that invoke the native methods declared in step 2. These calls represent the points where the Java code interacts with the native code.

  4. Cross-reference with native libraries by correlating the native method names with the symbols exported by the loaded native libraries. This helps confirm that the native code is indeed being invoked.

Here’s an example of what invocation of native code could look like:

public class NativeExample {

    static {
        System.loadLibrary("nativelib"); // Loading the native library
    }

    public native int nativeMethod(int input); // Declaring a native method

    public void callNativeMethod() {
        int result = nativeMethod(42); // Invoking the native method
        System.out.println("Native method result: " + result);
    }
}

In this example, the System.loadLibrary() call loads the native library named “nativelib”. The nativeMethod() method is declared as a native method, and its implementation resides in the native library. The callNativeMethod() method invokes the nativeMethod(), indicating the interaction between Java and native code.

Reflection

Reflection is a powerful programming technique that allows applications to dynamically execute code at runtime. While legitimate uses exist, reflection can also be abused by malicious actors to bypass static analysis checks and execute hidden code. Analyzing the application’s use of reflection can help identify potential attempts to conceal malicious behavior.

To identify reflection calls in an Android application by examining decompiled Java code, you can look for the following patterns:

  1. Use of reflection classes: Reflection calls typically involve classes from the java.lang.reflect package, such as Class, Method, and Field. These classes provide the means to dynamically access and manipulate classes, methods, and fields at runtime.

  2. String-based method invocation: Reflection often involves invoking methods using strings instead of direct method calls. Look for code that constructs method names or class names using strings and then uses reflection APIs to invoke those methods or access those classes.

  3. Dynamic class loading: Reflection can be used to dynamically load classes from external sources, such as DEX files or network resources. Look for code that uses the ClassLoader class to load classes at runtime.

Here’s an example of what reflection calls could look like:

public class ReflectionExample {

    public void invokeMethodUsingReflection() throws Exception {
        Class<?> myClass = Class.forName("com.example.MyClass"); // Dynamically loading a class
        Method myMethod = myClass.getMethod("myMethod", String.class); // Finding a method using reflection
        Object myObject = myClass.newInstance(); // Creating an instance of the class
        myMethod.invoke(myObject, "Hello World"); // Invoking the method using reflection
    }
}

In this example, the invokeMethodUsingReflection() method uses reflection to dynamically load the com.example.MyClass class, find the myMethod method, create an instance of the class, and invoke the method using reflection. This demonstrates how reflection can be used to dynamically interact with classes and methods at runtime.

Networking and data transmission

Investigating the application’s network communication patterns and data transmission is essential to uncover potential data leaks or unauthorized data collection. Examining the code for network calls, data transfer methods, and server interactions can reveal suspicious activities, such as sending user data to unknown servers or collecting excessive information without the user’s consent.

To identify network operations and data transmission in an Android application by examining decompiled Java code, you can look for the following patterns:

  1. Networking classes and APIs: Look for the usage of classes and methods from the java.net package, such as URL, HttpURLConnection, and Socket. These classes provide the means to establish network connections, send and receive data, and interact with network resources.

  2. URL construction and connection establishment: Identify code that constructs URLs using strings or other data sources and then establishes connections using HttpURLConnection or other networking classes.

  3. Data input and output streams: Look for code that utilizes input and output streams, such as InputStream and OutputStream, to read and write data to network connections.

  4. Data serialization and deserialization: Identify code that serializes or deserializes data using libraries like JSON or XML parsers. This may indicate that structured data is being transmitted over the network.

  5. Third-party networking libraries: Check for the usage of third-party networking libraries, such as Volley or Retrofit, which provide higher-level abstractions for network communication.

Here’s an example of how what network operations and data transmission code could look like:

public class NetworkExample {

    public void sendDataToServer() throws Exception {
        URL url = new URL("https://example.com/api/data"); // Constructing a URL
        HttpURLConnection connection = (HttpURLConnection) url.openConnection(); // Establishing a connection
        connection.setRequestMethod("POST"); // Setting the request method
        connection.setDoOutput(true); // Enabling output

        // Writing data to the server
        OutputStream out = connection.getOutputStream();
        String data = "{\"message\": \"Hello World\"}";
        out.write(data.getBytes());
        out.close();

        // Reading response from the server
        InputStream in = connection.getInputStream();
        byte[] responseData = new byte[1024];
        int bytesRead = in.read(responseData);
        String response = new String(responseData, 0, bytesRead);
        System.out.println("Server response: " + response);
    }
}

In this example, the sendDataToServer() method constructs a URL, establishes an HTTP connection, sends JSON data to the server, and reads the response. This demonstrates how network operations and data transmission can be identified in decompiled Java code.

Cryptography and encryption

Secure data handling is paramount for protecting sensitive user information but it can also be used for hidding information related to malicious activities. Analyzing the application’s use of cryptography and encryption algorithms helps understand what the application encrypts or decrypts.

To identify cryptographic operations and encryption/decryption operations in an Android application by examining decompiled Java code, you can look for the following patterns:

  1. Usage of Cryptographic APIs: Look for the usage of classes and methods from the java.security and javax.crypto packages, such as Cipher, MessageDigest, and KeyGenerator. These classes provide the means to perform encryption, decryption, hashing, and key generation.

  2. Encryption Algorithm Initialization: Identify code that initializes encryption algorithms using Cipher or other cryptographic classes. This typically involves specifying the algorithm, mode of operation, and padding scheme.

  3. Key Management: Look for code that generates, stores, or retrieves cryptographic keys. This may involve using KeyGenerator, SecretKey, or KeyStore classes.

  4. Data Encryption and Decryption: Identify code that encrypts or decrypts data using Cipher or other cryptographic classes. This typically involves passing data and keys to encryption or decryption methods.

  5. Hashing Operations: Look for code that computes hash values using MessageDigest or other hashing algorithms. This may be used for data integrity checks or password verification.

Here’s an example of what cryptographic operations and encryption/decryption operations could look like:

public class CryptoExample {

    public void encryptData(String data, String key) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); // Initializing the encryption algorithm
        byte[] keyBytes = key.getBytes("UTF-8");
        SecretKey secretKey = new SecretKeySpec(keyBytes, "AES");
        cipher.init(Cipher.ENCRYPT_MODE, secretKey); // Initializing the cipher for encryption

        byte[] encryptedData = cipher.doFinal(data.getBytes("UTF-8")); // Encrypting the data
        System.out.println("Encrypted data: " + new String(encryptedData));
    }

    public void decryptData(byte[] encryptedData, String key) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); // Initializing the decryption algorithm
        byte[] keyBytes = key.getBytes("UTF-8");
        SecretKey secretKey = new SecretKeySpec(keyBytes, "AES");
        cipher.init(Cipher.DECRYPT_MODE, secretKey); // Initializing the cipher for decryption

        byte[] decryptedData = cipher.doFinal(encryptedData); // Decrypting the data
        System.out.println("Decrypted data: " + new String(decryptedData));
    }
}

In this example, the encryptData() method initializes an AES cipher in CBC mode with PKCS5 padding, generates a secret key from a key string, encrypts the data, and prints the encrypted data. The decryptData() method performs the reverse operation, decrypting the data using the same key and algorithm. This demonstrates how cryptographic operations can be identified in decompiled Java code.

Third-party Libraries

Many applications rely on third-party libraries to provide additional functionalities or enhance performance. However, outdated or vulnerable third-party libraries can introduce security risks. Examining the application’s use of third-party libraries and ensuring they are up-to-date and free from known vulnerabilities is crucial to prevent potential exploits and protect the application’s overall security posture.

To identify third-party libraries in an Android application by examining decompiled Java code, you can look for the following indicators:

  1. Package names: Third-party libraries often have distinct package names that differ from the application’s own package name. Look for imports or references to packages that don’t belong to the application’s namespace.

  2. Class names and dependencies: Third-party libraries typically introduce new classes and interfaces that are not part of the standard Android SDK. Identify classes and interfaces that are not recognized as part of the core Android framework.

  3. Code style and conventions: Third-party libraries may have different coding styles, naming conventions, or commenting practices compared to the application’s own code. Look for inconsistencies in code style that suggest the use of external libraries.

  4. Documentation and licensing: Some third-party libraries include copyright notices, license headers, or documentation comments that identify their origin. Look for such comments or annotations within the decompiled code.

Here’s an example of what the usage of third-party libraries could look like:

import com.squareup.okhttp3.OkHttpClient; // Importing a third-party library class

public class NetworkExample {

    public void sendDataToServer() throws Exception {
        OkHttpClient client = new OkHttpClient(); // Creating an instance of a third-party library class
        // ... network operations using the third-party library ...
    }
}

In this example, the import statement and the instantiation of the OkHttpClient class indicate the use of the OkHttp third-party library. Identifying such patterns can help you determine which third-party libraries are being used in the application.

Hidden functionalities

Malicious applications may contain hidden or undocumented functionalities that could pose a security risk or violate user privacy. Identifying such hidden features requires careful examination of the code, resources, and manifest file. Look for code that is executed conditionally or triggered by specific events, as well as obfuscated or encrypted code that may conceal unauthorized actions.

One of the multiple ways to hide functionnalities is to package them into an encrypted DEX file (packing technique) that will be dynamically loaded at runtime. To identify how an encrypted DEX file is dynamically loaded in the decompiled Java code of an Android application, you can look for the following:

  1. Method calls to the ClassLoader class: This class provides methods for loading classes from DEX files. Methods of interest include loadClass(), loadDex(), and loadDexFromMemory().
  2. References to encrypted DEX files: Encrypted DEX files are typically stored as assets in Android applications. Look for references to asset files with .dex extensions.

Here’s an example of what the Java code loading a DEX file dynamically could look like:

// Load an decrypted DEX file from the assets directory.
DexClassLoader dexClassLoader = new DexClassLoader(
    "assets/decrypted.dex",
    null,
    null,
    ClassLoader.getSystemClassLoader());

// Load a class from the encrypted DEX file.
Class<?> encryptedClass = dexClassLoader.loadClass("com.example.encrypted.EncryptedClass");

Once you have identified the code that is dynamically loading the DEX file, you can use a variety of techniques to decrypt the file and analyze its contents.

Here’s another example of what the Java code loading a DEX file from memory could look like:

// Load an encrypted DEX file from memory.
byte[] decryptedDexBytes = decrypt("assets/encrypted.dex"); // Get the decrypted DEX file bytes from somewhere.

DexClassLoader dexClassLoader = new DexClassLoader(
    decryptedDexBytes,
    null,
    null,
    ClassLoader.getSystemClassLoader());

// Load a class from the encrypted DEX file.
Class<?> encryptedClass = dexClassLoader.loadClass("com.example.encrypted.EncryptedClass");

In this example, the encrypted DEX file is decrypted with a method decrypt() returning the decrypted content as bytes that are then loaded from memory, instead of from the assets directory. This is another common way for malware to dynamically load encrypted DEX files.

Android API

The Android API is a set of classes and interfaces that developers can use to build Android apps. The API provides access to all of the features and functionality of the Android platform, including the operating system, hardware, and services.

The API is divided into several different packages, each of which provides access to a specific set of functionality. For example, the android.app package provides access to classes and interfaces related to the user interface and application lifecycle. The android.content package provides access to classes and interfaces related to the system’s broadcast mechanism and content providers.

The Android API is constantly evolving, and new features and functionality are added with each new release of the platform.

Android managers are special classes that provide access to specific system resources and functionality. For example, the TelephonyManager class provides access to information about the device’s telephony capabilities, such as the network type, carrier, and phone number.

Managers are used to perform a variety of tasks, including:

  • Retrieving information about the device’s state: For example, the TelephonyManager class can be used to retrieve the device’s phone number or the network type it is connected to. The LocationManager class can be used to retrieve the device’s current location.
  • Controlling device resources: For example, the PowerManager class can be used to put the device to sleep or wake it up. The AudioManager class can be used to adjust the device’s ringer volume or media volume.
  • Registering for system events: For example, the LocationManager class can be used to register for a callback event when the device’s location changes. The BroadcastReceiver class can be used to register for a callback event when a specific broadcast message is received.

Managers are a powerful tool for developers, and they are used in a wide variety of Android apps. For example, the TelephonyManager class is used by phone and messaging apps to make and receive calls and text messages. The LocationManager class is used by navigation and mapping apps to track the user’s location.

Here are some additional examples of how Android managers can be used in apps:

  • A music player app can use the AudioManager class to adjust the volume of the music being played.
  • A camera app can use the CameraManager class to access the device’s camera and take pictures or videos.
  • A flashlight app can use the CameraManager class to turn on the device’s LED flash.
  • A game app can use the SensorManager class to access the device’s accelerometer and gyroscope to control the game.
  • A fitness tracking app can use the SensorManager class to access the device’s accelerometer and gyroscope to track the user’s movements.

Android managers are an essential part of the Android API, and they allow developers to create apps that interact with the device’s hardware and system services.

Android services & managers

AccessibilityService: This API enables applications to assist users with disabilities by providing alternative interactions with the device. However, malicious applications can abuse this API to intercept user interactions, capturing sensitive information displayed on the screen or performing unauthorized actions. For instance, a malicious application could intercept banking credentials or personal messages by monitoring user interactions.

AccessibilityEvent: This API provides information about events occurring on the device, such as screen changes, button presses, or text input. Malicious applications can exploit this API to monitor user activity, capture sensitive data, or inject malicious code into other applications. For example, a malicious application could monitor keystrokes to steal passwords or inject code into a legitimate application to gain control over the device.

ContactsContract: This API provides access to the user’s contacts, including names, phone numbers, and email addresses. Malicious applications can exploit this API to harvest contact information for spamming, phishing, or identity theft. For instance, a malicious application could collect contact details and sell them to spammers or use them for targeted phishing attacks.

LocationManager: This API allows applications to access the user’s location data, including GPS coordinates and network-based location. Malicious applications can abuse this API to track user movements, monitor their whereabouts, or sell location data to third parties. For example, a malicious application could track a user’s movements to identify their home or workplace, potentially compromising their privacy and security.

SMSManager: This API provides access to SMS messages, enabling applications to send and receive text messages. Malicious applications can exploit this API to intercept sensitive information, send spam messages, or incur unwanted charges. For instance, a malicious application could intercept two-factor authentication codes or send premium-rate SMS messages without user consent.

AudioRecord: This API enables applications to record audio from the device’s microphone. Malicious applications can misuse this API to eavesdrop on conversations, collect private audio recordings, or violate user privacy. For example, a malicious application could record private conversations or collect sensitive audio data for unauthorized purposes.

Camera: This API allows applications to capture images and videos using the device’s camera. Malicious applications can abuse this API to take pictures or record videos without user consent, potentially compromising privacy and security. For instance, a malicious application could secretly take pictures or record videos of the user or their surroundings, violating their privacy.

ExternalStorage: This API provides access to files stored on external storage, such as SD cards. Malicious applications can exploit this API to steal sensitive data, modify files without authorization, or spread malware. For example, a malicious application could steal confidential documents, modify files to corrupt data, or spread malware to other devices.

TelephonyManager: This API provides access to device identifiers and phone state information. Malicious applications can misuse this API to track user behavior, profile their activities, or link their device to other personal data. For instance, a malicious application could track app usage patterns, collect device identifiers for targeted advertising, or link the device to other personal information for profiling purposes.

Android APIs calls

To investigate deeper, you can look at different calls to methods exposing sensitive information such as the phone number or SMS messages. Here’s the list of Android Java methods that provide access to sensitive data:

Reading the IMEI

The IMEI (International Mobile Equipment Identity) is a unique identifier for mobile devices. Accessing it can enable device tracking, profiling, and targeted advertising.

Code example:

TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
String imei = telephonyManager.getDeviceId();

Reading the IMSI

The IMSI (International Mobile Subscriber Identity) is a unique identifier for SIM cards. Accessing it can enable user identification, location tracking, and potential surveillance.

Code example:

TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
String imsi = telephonyManager.getSubscriberId();

Reading the phone number

Accessing the phone number can enable user identification, targeted advertising, and potential harassment or scams.

Code example:

TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
String phoneNumber = telephonyManager.getLine1Number();

Retrieving SMS messages

Accessing SMS messages can expose sensitive information, such as two-factor authentication codes, personal communications, or financial transactions.

Code example:

ContentResolver contentResolver = getContentResolver();
Cursor cursor = contentResolver.query(Uri.parse("content://sms/inbox"), null, null, null, null);
while (cursor.moveToNext()) {
  String address = cursor.getString(cursor.getColumnIndex("address"));
  String body = cursor.getString(cursor.getColumnIndex("body"));
  // Process SMS message data
}

Retrieving SIM and carrier information

Accessing SIM and carrier information can reveal user’s location, network details, and potentially sensitive personal data.

Code example:

TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
String simOperator = telephonyManager.getSimOperator();
String simCountryIso = telephonyManager.getSimCountryIso();
String simSerialNumber = telephonyManager.getSimSerialNumber();

Reading the Android Advertising ID

The Android advertising ID is a unique identifier used for targeted advertising. Accessing it can enable user profiling and tracking across apps.

Code example:

AdvertisingIdClient.Info adInfo = AdvertisingIdClient.getAdvertisingIdInfo(this);
String advertisingId = adInfo.getId();

Getting precise location from GPS

Accessing precise GPS location can enable user tracking, location-based profiling, and potential privacy violations.

Code example:

LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
LocationListener locationListener = new MyLocationListener();
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListener);

Getting precise location from Wi-Fi networks

Accessing precise location from Wi-Fi networks can reveal user’s whereabouts, movement patterns, and potentially sensitive location data.

Code example:

LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
LocationListener locationListener = new MyLocationListener();
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListener);

Getting precise location from cell towers

Accessing precise location from cell towers can enable user tracking, location-based advertising, and potential privacy intrusions.

Code example:

TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
CellLocation cellLocation = telephonyManager.getCellLocation();

Executing OS commands

Executing arbitrary OS commands can lead to system compromise, data theft, or unauthorized access to sensitive resources.

Code example:

Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec("su");

Conclusion

Static analysis is a powerful tool for identifying potential security risks and malicious behavior in Android applications. By carefully examining the application’s code, resources, and manifest file, you can effectively assess the application’s trustworthiness and protect users from potential harm.