Android Fundamentals

About Android


Android is a mobile operating system created for touchscreen devices like phones and tablets. Based on a modified version of the Linux Kernel, it was developed by the Open Handset Alliance consortium and commercially sponsored by Google. Most Android devices come pre-installed with Google Mobile Services (GMS), a proprietary software suite that includes apps like Google Play and Google Chrome. Google collaborates with various vendors, such as Samsung and HTC, to allow customization of user interfaces and software features. Beyond smartphones and tablets, Android OS is also used in smart TVs and wearables developed by Google. Android applications are distributed through various app stores, including Google Play Store, Amazon Appstore, Samsung Galaxy Store, Huawei AppGallery, and open-source platforms such as Aptoide, F-Droid, APKPure, and APKMirror.


History

In October 2003, Andy Rubin, Rich Miner, Nick Sears, and Chris White founded Android Incorporated in Palo Alto, California. In 2005, after Rubin's efforts to attract investors, Google acquired the company for the reported price of around $50 million. After some time, Rubin's team at Google developed the first mobile device powered by the Linux kernel. In 2007, an early prototype with no touchscreen and a physical QWERTY keyboard was created, followed by the HTC Dream (also known as T-Mobile G1, the first commercial Android device released in September 2008.) Android's major releases before version 10 were named after desserts. For example, the first Android versions were called Cupcake, Donut, Eclair, and Froyo.

In 2010, Google launched the Nexus series, and in May 2013, it announced a special version of the Samsung Galaxy S4 that included Google Play, followed by the HTC One Google Play edition and the Moto G Google Play edition. In June 2014, Google introduced Android One, a program aimed at enabling third-party manufacturers to produce high-quality smartphones easily and at low cost. In October 2016, Google replaced the Nexus series with the Pixel and Pixel XL smartphones, the first phones designed and marketed entirely by Google. On September 3, 2019, Google released Android 10 (initially referred to as Android Q) for Pixel devices, officially ending the tradition of naming major Android versions after desserts.


Versions

Below is a table showing the correlation between Android codenames, versions, API levels, and release dates.

Name Version API Level Release Date
Android 1.0 1.0 1 September 23, 2008
Android 1.1 1.1 2 February 9, 2009
Android Cupcake 1.5 3 April 27, 2009
Android Donut 1.6 4 September 15, 2009
Android Eclair 2.0 5 October 27, 2009
Android Eclair 2.0.1 6 December 3, 2009
Android Eclair 2.1 7 January 11, 2010
Android Froyo 2.2 – 2.2.3 8 May 20, 2010
Android Gingerbread 2.3 – 2.3.2 9 December 6, 2010
Android Gingerbread 2.3.3 – 2.3.7 10 February 9, 2011
Android Honeycomb 3.0 11 February 22, 2011
Android Honeycomb 3.1 12 May 10, 2011
Android Honeycomb 3.2 – 3.2.6 13 July 15, 2011
Android Ice Cream Sandwich 4.0 – 4.0.2 14 October 18, 2011
Android Ice Cream Sandwich 4.0.3 – 4.0.4 15 December 16, 2011
Android Jelly Bean 4.1 – 4.1.2 16 July 9, 2012
Android Jelly Bean 4.2 – 4.2.2 17 November 13, 2012
Android Jelly Bean test 18 July 24, 2013
Android KitKat 4.4 – 4.4.4 19 October 31, 2013
Android KitKat 4.4W – 4.4W.2 20 June 25, 2014
Android Lollipop 5.0 – 5.0.2 21 November 4, 2014
Android Lollipop 5.1 – 5.1.1 22 March 2, 2015
Android Marshmallow 6.0 – 6.0.1 23 October 2, 2015
Android Nougat 7.0 24 August 22, 2016
Android Nougat 7.1 – 7.1.2 25 October 4, 2016
Android Oreo 8.0 26 August 21, 2017
Android Oreo 8.1 27 December 5, 2017
Android Pie 9 28 August 6, 2018
Android 10 10 29 September 3, 2019
Android 11 11 30 September 8, 2020
Android 12 12 31 October 4, 2021
Android 12L 12.1 32 March 7, 2022
Android 13 13 33 August 15, 2022
Android 14 14 34 October 4, 2023
Android 15 15 35 September 3, 2024
Android 16 16 Beta 36 March 13, 2025

The Android version of the device, along with other information, can be found under Settings -> About emulated device -> Android version.

Android version 13 details: Security update November 5, 2022; Google Play update October 1, 2022; Baseband 1.0.0.0; Kernel 5.15.41; Build number 9302419.


Hardware

Although Android supports a range of hardware architectures, the majority of devices use the ARM (AArch64) architecture. Architectures such as x86 and x86-64 have also been supported, primarily in later Android versions that included Intel processors. The unofficial project Android-x86 provided support for the x86 architecture even before it was officially supported by Android. Non-native architectures like x86 can also run on the Android Emulator included with the SDK, as well as on various third-party emulators. Android devices typically include a variety of hardware components, such as video cameras, GPS, orientation sensors, dedicated gaming controls, accelerometers, gyroscopes, barometers, magnetometers, proximity sensors, pressure sensors, thermometers, and touchscreens. Moving on to the next section, we will discuss the main components of the Android operating system and their attributes.


Android Operating System


As we mentioned in the previous section, Android is a Linux-Based OS, and once someone gains access to a shell on the device, Linux commands can be executed. The Linux shell, as we will see in the upcoming sections, will provide a text-based I/O (input/output) interface between users and the system kernel. The example below shows how to navigate the file system and list the contents of the directory /sdcard/.

Android Shell

Android Operating System

emu64x:/ # cd /sdcard/
emu64x:/sdcard # ls -l

total 104
drwxrws--- 2 u0_a143  media_rw 4096 2022-12-28 11:48 Alarms
drwxrws--x 5 media_rw media_rw 4096 2022-12-28 11:47 Android
drwxrws--- 2 u0_a143  media_rw 4096 2022-12-28 11:48 Audiobooks
drwxrws--- 2 u0_a143  media_rw 4096 2022-12-28 11:48 DCIM
drwxrws--- 2 u0_a143  media_rw 4096 2022-12-28 11:48 Documents
drwxrws--- 2 u0_a143  media_rw 4096 2023-04-19 01:18 Download
drwxrws--- 3 u0_a143  media_rw 4096 2022-12-28 11:48 Movies
drwxrws--- 3 u0_a143  media_rw 4096 2022-12-28 11:48 Music
drwxrws--- 2 u0_a143  media_rw 4096 2022-12-28 11:48 Notifications
drwxrws--- 4 u0_a143  media_rw 4096 2022-12-30 15:09 Pictures
drwxrws--- 2 u0_a143  media_rw 4096 2022-12-28 11:48 Podcasts
drwxrws--- 2 u0_a143  media_rw 4096 2022-12-28 11:48 Recordings
drwxrws--- 2 u0_a143  media_rw 4096 2022-12-28 11:48 Ringtones

Android Software Stack

The Android platform consists of six components, which we will analyze in the following paragraphs. The image below shows the Linux-based software stack Android uses, which contains these components.

Android architecture layers: System Apps, Java API Framework, Native C/C++ Libraries, Android Runtime, Hardware Abstraction Layer (HAL), Linux Kernel.

Platform architecture\

Linux Kernel

The Linux kernel is the foundation of the Android platform, and is responsible for managing device hardware such as the display, camera, bluetooth, wifi, audio, USB, and more. Android Runtime also relies on this layer to perform functionalities like threading and memory management.

Additionally, the Linux kernel allows Android to take advantage of numerous security features (like a user-based permissions model and process isolation) that:

Kernel-Level Security Features
Prevent users from reading each other's files.
Prevent users from exhausting each other's memory.
Prevent users from exhausting CPU resources.
Prevent users from exhausting devices resources, like telephony, GPS, and Bluetooth.

Hardware Abstraction Layer (HAL)

The Hardware Abstraction Layer (HAL) is a software layer that provides the Android operating system with a standardized interface for interacting with hardware components, such as cameras, Bluetooth, sensors, and input devices. Acting as a bridge between hardware and the higher-level software layers, HAL ensures consistency in how software accesses hardware features. Because different hardware components may have unique requirements and capabilities, writing portable software that works across devices can be challenging. HAL addresses this by isolating hardware-specific implementation details from the Android framework. It is implemented as a collection of shared libraries that are dynamically loaded by the Android framework at runtime. This architecture allows device manufacturers to implement custom support for their hardware while maintaining compatibility with the broader Android platform.

Android Runtime

Android Runtime (ART) is the managed runtime environment used by the Android operating system to execute applications. Introduced in Android 5.0 Lollipop as a replacement for the Dalvik virtual machine, ART brought significant architectural improvements to app execution. The primary distinction between ART and Dalvik lies in their compilation strategies: ART uses Ahead-of-Time (AOT) compilation, while Dalvik relied on Just-in-Time (JIT) compilation. With AOT, application code is compiled into native machine code at install time, resulting in faster app launch times and improved runtime performance.

ART is capable of running multiple virtual machines concurrently, even on low-memory devices, and it executes applications packaged in the DEX (Dalvik Executable) format. Importantly, ART maintains backward compatibility with applications originally built for Dalvik. Some of the core features and benefits of Android Runtime include:

ART Features
Improved garbage collection.
Better memory management.
Better debugging support.
Optimized compression of the DEX file.

Native C/C++ Libraries

The Native C/C++ Libraries component is a set of libraries written in the C and C++ programming languages, and are included in the Android operating system. Developers generally use these libraries to achieve high performance or write low-level code to interact directly with the hardware. Hardening techniques for increasing security can also be implemented using native C++ code.

Android components like ART and HAL are created using native code, and in order for these components to work, access to native libraries is needed. Applications can access these libraries through the Java Native Interface (JNI), while programmers can use the Android NDK to access native libraries directly from their native code.

Java API Framework

Another essential part of the Android platform architecture is Java API Framework. This component provides software tools and interfaces for building Android applications. Below are some of the components tha Java API Framework provides.

Components
View System
Resource Manager
Notification Manager
Activity Manager
Content Providers
Location Manager
Package Manager

System Apps

System Apps is the top layer core component of the platform architecture. This component includes all the pre-installed applications that come with the Android operating system. Such apps include:

Apps
Contacts
Messaging
Camera
Browser
Calendar
Maps
Settings

Pre-installed apps can only be modified on rooted devices. However, developers can use them in their applications using the provided APIs. For example, a developer could create an app that uses the camera to scan a QR code.


Dalvik VM

The Dalvik Virtual Machine (DVM) was developed by Google and introduced with the first version of Android in 2008. Android applications written in Java or Kotlin are compiled into Java bytecode and then transformed into Dalvik bytecode, packaged in .dex (Dalvik Executable) or .odex (Optimized Dalvik Executable) file formats. Unlike the Java Virtual Machine (JVM), which is stack-based, the Dalvik VM is a register-based virtual machine. This architectural difference allows for more efficient execution on devices with limited CPU and memory resources, which is ideal for mobile environments.

Dalvik was the default runtime environment in Android versions prior to API level 21 (Lollipop). It was eventually replaced by the Android Runtime (ART), which was introduced as a preview in Android 4.4 (KitKat) and became the default in Android 5.0. ART maintains compatibility by using the same .dex bytecode format as Dalvik, but differs significantly in its execution model. While Dalvik used Just-in-Time (JIT) compilation, ART initially used Ahead-of-Time (AOT) compilation — compiling bytecode into native machine code at install time, resulting in faster startup and improved performance. In later Android versions, ART evolved to include hybrid JIT + AOT and Profile-Guided Optimizations (PGO), further enhancing runtime efficiency and battery performance.


Rooting

Android separates the flash storage into the following two main partitions.

The partition /system/ is used by the operating system, and the partition /data/ is used for user data and application installations. In Android, users don't have root access to the operating system, and some partitions (like /system/) are read-only. However, rooting the device can be achieved by exploiting security flaws. Having a rooted device enhances the capabilities and customizability of the device, and helps with debugging and overall security assessments. Rooted Android devices are also more susceptible to malicious viruses and malware, since the rooting process disables some of the built-in security features of the operating system. In some models like Google Pixel and OnePlus, rooting can also be achieved by unlocking the bootloader via the OEM Unlocking option.


Important Directories

Android's file structure is very similar to other Linux distributions. The directories listed below are some of the most important to consider while conducting Android app assessments.

Directory Description
/data/data Contains all the applications that are installed by the user
/data/user/0 Contains data that only the app can access
/data/app Contains the APKs of the applications that are installed by the user
/system/app Contains the pre-installed applications of the device
/system/bin Contains binary files
/data/local/tmp A world-writable directory
/data/system Contains system configuration files
/etc/apns-conf.xml Contains the default Access Point Name (APN) configurations. APN is used in order for the device to connect with our current carrier’s network
/data/misc/wifi Contains WiFi configuration files
/data/misc/user/0/cacerts-added User certificate store. It contains certificates added by the user
/etc/security/cacerts/ System certificate store. Permission to non-root users is not permitted
/sdcard Contains a symbolic link to the directories DCIM, Downloads, Music, Pictures, etc.

Android Security Features


Kotlin and Java are the two primary languages used to develop Android applications. The Android SDK tools compile application source code along with resource files and assets into an Android Package (APK). An APK is an archive file with a .apk extension that contains all the components needed to install and run an Android app, including compiled bytecode (.dex), manifest metadata, resources, and native libraries.

Each Android application runs within its own isolated security sandbox, enforced by the underlying Linux-based architecture. This sandboxing model is supported by several core Android security features:

Security Features
Android is a multi-user Linux system where each application is treated as a separate user.
By default, the system assigns each app a unique Linux user ID (UID). This UID is used by the system for access control, but is not exposed to the app itself.
File system permissions ensure that only the app assigned a particular UID can access its own files.
Each app runs in its own process, and each process runs in a separate instance of the Android Runtime (ART) virtual machine, ensuring memory isolation.
The system launches the app's process as needed and terminates it when no longer required or when reclaiming system resources.
Android enforces the principle of least privilege, meaning apps only receive the permissions necessary to perform their core functionality. Additional privileges must be explicitly declared in the app's manifest and approved by the user (or system, depending on the API level).

Application Sandbox

Android uses Linux’s user-based security model to isolate applications by assigning each app a unique user ID (UID) and running it in its own process. This creates a kernel-level application sandbox that enforces strict boundaries between apps and the system, preventing unauthorized data access or code execution across app boundaries. Apps cannot interact with each other or access system resources beyond their privileges unless explicit permissions are granted. Because the sandbox is enforced by the Linux kernel, these protections apply uniformly to all code running above the kernel, including native binaries, OS services, libraries, and user applications. Escaping this sandbox requires compromising the kernel itself, typically through a privilege escalation exploit.

Processes 1466 and 1732 run App1 and App2 with Dalvik VM/Android Runtime, accessing filesystems at /data/data/com.example.myapp1 and /data/data/com.example.myapp2.

Executing the following command, we can see that all applications have a different UID.

Android Shell

Android Security Features

root:/# ls -l /data/data/

<SNIP>
drwx------  4 system         system         4096 2022-12-28 11:47 android
drwx------  4 bluetooth      bluetooth      4096 2022-12-28 11:47 com.android.bluetooth
drwx------  5 radio          radio          4096 2022-12-28 11:48 com.android.ons
drwx------  5 shell          shell          4096 2022-12-28 11:49 com.android.shell
drwx------ 11 u0_a114        u0_a114        4096 2022-12-30 12:41 com.android.chrome
drwx------  5 u0_a119        u0_a119        4096 2022-12-28 11:49 com.android.camera2

In addition to the UID-based discretionary access control (DAC) sandboxing that Android uses, many other protections have been introduced to strengthen app and system isolation. The following protections were introduced in previous Android releases.

Additional Protections
SELinux Mandatory Access Control (MAC): Separates the system from the apps.
SELinux sandbox extension: Isolates apps across physical users.
Filter seccomp-bpf: Sets a limit to the syscalls that apps are allowed to use.
Individual SELinux sandboxes and Mandatory Access Control (MAC): Exists for all non-privileged apps with targetSdkVersion >= 28.
Limited raw view of the filesystem: No direct access to paths like /sdcard/DCIM.

Sharing files between applications also requires strong security practices and implementation. Setting app data as world accessible has been disallowed since targetSdkVersion >= 28 due to the inability to specify the desired recipient, and thus, malicious apps could potentially gain access to sensitive information from other apps.


Application Signing

To install an application on a device or upload it to the Play Store, the APK file must be signed. Signing the APK is crucial for security, as it protects the package from malicious modifications. Devices running Android 7.0 and earlier support JAR signing (v1), but this method does not offer complete protection—certain parts of the APK, such as ZIP metadata, are not covered by the signature.

Signature Scheme v2 was introduced in Android 7.0 and later to improve APK integrity and guard against unauthorized changes. Signature Scheme v3, introduced in Android 9, added support for including additional metadata in the signing block. Starting with Android 11, APK Signature Scheme v4 is used, which employs a Merkle hash tree calculated over all bytes of the APK and follows the structure of an fs-verity hash tree. The v4 signature is stored in a separate file and requires a corresponding v2 or v3 signature. The image below shows the flow of signature validation when an application is installed.

Flowchart for APK verification: Open APK, check for v3, v2, v1 signatures. Verify using respective rules. If verified, install APK; if not, reject APK.

Signature v2 and v3 perform checks that invalidate the APK file if there are any modifications. This way, attacks like injecting DEX files into the APK file are prevented. Signature Scheme v1, however, is vulnerable to this kind of attack. The Janus vulnerability (CVE-2017-13156) allows malicious actors to inject DEX files into the APK-without affecting the signatures-in cases where the APK is signed using the Signature Scheme v1. As a result, they can install and run the modified app.

The malicious DEX file is prepended to the APK file, and the Android runtime accepts it as a valid update made to the earlier, legitimate version of the app. Then, the Dalvik VM will load the code of the DEX file. Android devices 5.0 < 8.1 that are signed using Signature Scheme v1 are affected by Janus.

Application signing can be performed in several ways:

Signing Ways
Android Studio, via the Generate Signed App Bundle / APK build option.
The jarsigner / apksigner tools.
Play App Signing.

The certificates that are used to sign an application are self-signed. One can sign an APK file with apksigner tool (Signature Scheme v4) using the following commands:

Signing an APK File

Android Security Features

root@htb[/htb]$ echo -e "password\npassword\njohn doe\ntest\ntest\ntest\ntest\ntest\nyes" > params.txt
root@htb[/htb]$ cat params.txt | keytool -genkey -keystore key.keystore -validity 1000 -keyalg RSA -alias john
root@htb[/htb]$ zipalign -p -f -v 4 myapp.apk myapp_signed.apk
root@htb[/htb]$ echo password | apksigner sign --ks key.keystore myapp_signed.apk

These commands accomplish the following for us:

APK Signing Steps
Creates a file, params.txt, with the necessary input data for keytool to generate a keypair.
Pipes the contents of params.txt into keytool to automate the key generation process, storing the key in key.keystore.
zipalign allows uncompressed files within myapp.apk to be accessed directly via mmap, creating an optimized application named myapp_signed.apk
The final app is signed with apksigner, using the key stored in key.keystore and the password being echoed through the pipe

Verified Boot

Verified Boot is an Android security feature that ensures the integrity of the operating system. This is achieved using a unique set of cryptographic keys to sign and verify the boot image and ensure that only the authorized parties can modify the system. While Android is booting up, each stage verifies the integrity and authenticity of the next stage, and if the signature is valid, then the device boots up normally. Otherwise, the device either won't boot, or it will provide the user with a message updating them that the device is tampered with. Apart from this, Verified Boot utilizes Rollback Protection to prevent exploits from becoming persistent. This is done by ensuring that Android is only updating to the newest versions. The recommended boot flow for a device is as follows:

Flowchart for device boot process: Start, check if device is locked. If valid OS found, verify root of trust. If not, cannot boot. Warn about custom OS or unverified OS. Update rollback protection or boot OS based on user confirmation and eio mode.

Before proceeding with the questions, ensure that you have followed the steps provided in the sections Android Studio and Android Emulators -> Android Studio AVD, and have set up a virtual Android device. This device will be used throughout the remainder of the module and subsequent challenge questions.


APK Structure


The Android Package Kit file—commonly known as an APK—is the file format used by the Android operating system to distribute and install applications. An APK is essentially an archive that contains all the components needed for an Android app to run. Among its contents is the application's compiled code, stored in a single DEX (Dalvik Executable) file. When an Android application is compiled, the Java (or Kotlin) source code is first converted into Java bytecode, which is then transformed and optimized into a DEX file. These DEX files are executable and can be interpreted by the Dalvik Virtual Machine (DVM) or the Android Runtime (ART), depending on the device and Android version.

In addition to compiled code, APK files include resources such as assets, images, UI layouts, and the AndroidManifest.xml file—all of which are necessary for the application to function. APK files use the .apk extension and, since they are ZIP-based archives, they can be unpacked with standard tools such as the unzip command in Linux.

APK Structure

root@htb[/htb]$ unzip myapp.apk
root@htb[/htb]$ ls -l

total 27584
-rw-r--r--    1 bertolis  bertolis     4220 Jan  1  1981 AndroidManifest.xml
drwxr-xr-x   49 bertolis  bertolis     1568 May 10 13:36 META-INF
drwxr-xr-x    3 bertolis  bertolis       96 May 10 13:36 assets
-rw-r--r--    1 bertolis  bertolis  8285624 Jan  1  1981 classes.dex
drwxr-xr-x    9 bertolis  bertolis      288 May 10 13:36 kotlin
drwxr-xr-x    6 bertolis  bertolis      192 May 10 13:36 lib
drwxr-xr-x  545 bertolis  bertolis    17440 May 10 13:36 res
-rw-r--r--    1 bertolis  bertolis   922940 Jan  1  1981 resources.arsc

The files extracted from the APK are encoded, and neither the source code nor the configuration files are human-readable.

APK Structure

root@htb[/htb]$ vim AndroidManifest.xml

A screen displaying a sequence of random symbols and characters, starting with "1 ^C^@^H^@^D^N^...".

The image below shows the unzipped structure of an APK file:

Diagram of APK structure: META-INF (certificates), assets (application assets), lib (native code), res (resources), AndroidManifest.xml (app info), classes.dex (compiled code), resources.arsc (resources info).

META-INF

This folder is generated when the application is signed, and it contains verification information. Any modification made to the APK file will lead to invalidation, and the APK will need to be resigned. Listing the content of this directory reveals the following files.

APK Structure

root@htb[/htb]$ ls -l META-INF/

total 664
-rw-r--r--  1 bertolis  bertolis   1103 Jan  1  1981 CERT.RSA
-rw-r--r--  1 bertolis  bertolis  77917 Jan  1  1981 CERT.SF
-rw-r--r--  1 bertolis  bertolis  77843 Jan  1  1981 MANIFEST.MF
<SNIP>
File Description
CERT.RSA Contains the public key and the signature of CERT.SF.
CERT.SF Contains a list of names/hashes of the corresponding lines in the MANIFEST.MF file.
MANIFEST.MF Contains a list of names/hashes (usually SHA256 in Base64) for all the files of the APK, and is used to invalidate the APK if any of the files are modified.

assets

This folder contains assets that developers bundle with the application, and can be retrieved by the AssetManager. These assets can be images, videos, documents, databases, and other raw files. Xamarin, Cordova, and React-Native applications will use this folder to save code and DLL's as well.

lib

This folder contains native libraries with compiled code targeting different device architectures. Android applications that use the Native Development Kit (NDK) may include components written in C or C++. When an app includes native libraries, they are stored in the lib directory as shared object files with the .so extension. Separate SO files are generated for each supported architecture, typically organized under subdirectories following this structure.

APK Structure

root@htb[/htb]$ ls -l lib/

total 0
drwxr-xr-x  3 bertolis  bertolis  96 May 10 13:36 arm64-v8a
drwxr-xr-x  3 bertolis  bertolis  96 May 10 13:36 armeabi-v7a
drwxr-xr-x  3 bertolis  bertolis  96 May 10 13:36 x86
drwxr-xr-x  3 bertolis  bertolis  96 May 10 13:36 x86_64

res

This folder contains predefined application resources that cannot be modified by the user at runtime, unlike assets. These resources include XML files defining color state lists, UI layouts, fonts, values, configurations for OS versions, screen orientations, network settings, and more.

APK Structure

root@htb[/htb]$ ls -l res/

<SNIP>
drwxr-xr-x 1 bertolis bertolis 10762 Jan 27 16:05 color
drwxr-xr-x 1 bertolis bertolis  6624 Jan 27 16:05 drawable
drwxr-xr-× 1 bertolis bertolis    30 Jan 27 16:05 raw
drwxr-xr-× 1 bertolis bertolis   466 Jan 27 16:05 xml

AndroidManifest.xml

The manifest file contains metadata about the application. It defines essential attributes and components that the system uses to manage the app, including:

Components
Package name
SDK Version
Build Version
Permissions
NetworkSecurityConfig
Activities
Providers
Services

classes.dex

This file contains all compiled Java (or Kotlin) classes in DEX (Dalvik Executable) format, which are executed by the Android Runtime (ART) on devices running Android 5.0 or higher, or by the Dalvik VM on earlier versions. Large applications may include multiple DEX files (enabled via multidex), named sequentially as classes2.dex, classes3.dex, and so on.

resources.arsc

This file contains precompiled resources that are used by the app at runtime. It maps resource identifiers in the code (e.g., R.string.app_name) to their actual values, such as strings, colors, layouts, and styles. It also includes a binary representation of XML resources. In some APKs, you may also find a kotlin/ folder, which exists in apps written in Kotlin and contains Kotlin-specific metadata used by the runtime and tooling.


Android Studio


Android Studio is an Integrated Development Environment (IDE) based on JetBrains' IntelliJ IDEA and is the official IDE for Android application development. Familiarity with the Android Studio project structure can provide valuable insight during reverse engineering. Android Studio is available for Windows, Linux, and macOS. On Windows and macOS, you can download the .exe and .dmg installers respectively, and follow the Setup Wizard to complete the installation. In this example, we will demonstrate how to install Android Studio on a Debian-based Linux distribution. Replace the version number in the link with the latest one, if necessary.

Android Studio

root@htb[/htb]$ wget https://redirector.gvt1.com/edgedl/android/studio/ide-zips/2024.3.1.14/android-studio-2024.3.1.14-linux.tar.gz
root@htb[/htb]$ tar xvzf android-studio-2024.3.1.14-linux.tar.gz
root@htb[/htb]$ sh android-studio/bin/studio.sh

Once the Setup Wizard starts, click Next on the first four windows to download the SDK and accept the License Agreement. Then, wait for the components to download and click Finish.

Android Studio Setup Wizard downloading components. URL: https://dl.google.com/android/repository/sys-img/google\_apis\_playst...36\_r05.zip. Options: Show Details, Cancel.

On the next window, click on the Create Project button to create a new project.

Welcome screen of Android Studio Meerkat 2024.3.1 Patch 1. Options: New Project, Open, Clone Repository. Menu: Projects, Customize, Plugins, Learn.

Select Empty Views Activity and choose Groovy DSL (build.gradle) in the Build configuration language option, and click Next.

New Project screen in Android Studio. Templates for Phone and Tablet: No Activity, Empty Activity, Gemini API Starter, Basic Views Activity, Bottom Navigation Views Activity, Empty Views Activity. Options: Next, Cancel, Finish.

Finally, we give the app a name, select the programming language (Java in our case) and click Finish.

New Project setup for Empty Views Activity. Fields: Name "My Application", Package "com.example.myapplication", Save location, Language "Java", Minimum SDK "API 24 (Nougat)", Build configuration "Groovy DSL". Options: Finish, Cancel.

After clicking 'Finish', we wait for the indexing process to complete before proceeding with the following steps. The above setup configures the project to use Java as the default programming language.


Project Structure

Once the project is created, we see the project structure.

Android Studio interface showing: Toolbar Menu, Project Explorer with "MainActivity.kt" open, Editor Window with Kotlin code, Tool Window, Navigation Bar, and Status Bar.

Understanding the Android Studio project structure is essential for reverse engineering, as it provides insight into how the app is organized during development.

The project contains the following folders.

app

Files Description
manifest Contains essential metadata about the app, including the package name, components (activities, services, etc.), permissions, network configuration, API level, and more.
java Contains the application's Java source code.
res Contains app resources such as UI strings, images, layout XML files, and other static assets used by the app.

Gradle Scripts

Files Description
build.gradle Defines build configurations for the project or module, including dependencies, build types, and whether code optimization tools (such as ProGuard) are enabled.
proguard-rules.pro Specifies custom rules for ProGuard

While we won't explore Gradle Scripts in detail in this module, it's important to understand that they form the foundation of Android Studio’s build system. For instance, Gradle can be configured to generate multiple APKs from a single project, each offering different features.


Types of Applications

Depending on the technologies used, Mobile applications can be categorized into three basic types.

Native apps

Native apps are built specifically for a particular operating system. Android native apps are typically developed using Kotlin or Java. They are generally considered more secure than hybrid apps because they have direct access to platform-level security features and system APIs.

Web apps

Web apps, while similar in appearance to native apps, are developed to be responsive and accessible from mobile web browsers. They are typically built using HTML, CSS, and JavaScript. Web apps can be vulnerable to web-based attacks due to their reliance on web technologies and browser security.

Hybrid apps

Hybrid apps combine elements of both native and web apps and are designed to be cross-platform. They use WebViews to display web content within a native app container. While offering the flexibility of web apps, hybrid apps can also be vulnerable to web-based attacks, including cross-site scripting (XSS) attacks, weak SSL implementations, and more.

In the following sections, we'll see some examples of creating native and hybrid Android applications.


Native Apps


Native apps are software applications written in a specific programming language and tailored to run on a particular platform. In the context of Android, native apps are primarily developed using Java or Kotlin and built with Android Studio, typically leveraging components from the Android Software Development Kit (SDK).

Google, the tech giant who owns and develops Android, is using Kotlin as the default programming language for app development. However, Java is still a popular choice and many users prefer it over Kotlin. In the following steps, we will show how to create a simple application in Java. Since we have already created a new project, let's examine how the app layout is created and connected with the Java code and other resources. On the left side section under app/res/layout we select the activity_main.xml file.

Project Explorer showing app structure: manifests with AndroidManifest.xml, java with MainActivity, res with layout containing activity\_main.xml, and values with colors.xml, strings.xml, themes.

Layouts define the structure of the user interface of the application, and Android uses XML to create such layouts. In this file, activity_main.xml, we can add text, buttons, images, and other things that will be displayed to the user. The following snippet will create the layout of an application that prints a message on the screen when the button is tapped.

Code: xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="My Application"
        android:textSize="32sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.097" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/title"
        app:layout_constraintVertical_bias="0.403" />

    <TextView
        android:id="@+id/message"
        android:layout_width="380dp"
        android:layout_height="31dp"
        android:text="@string/message"
        android:textSize="20sp"
        android:textAlignment="center"
        android:textIsSelectable="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button"
        app:layout_constraintVertical_bias="0.25" />

</androidx.constraintlayout.widget.ConstraintLayout>

The above snippet consists of three objects: two TextView and one Button. It is worth noticing the following attributes.

Attribute Description
tools:context=".MainActivity Defines the Activity in which the layout will be used. This is primarily used in the layout editor for preview purposes and does not affect the runtime behavior of the app.
android:id Assigns a unique identifier to the object, allowing it to be referenced in Java code (such asMainActivity.java) and other resources. The @+id prefix indicates it will be created as a new resource, whereas @id would be used if the resource were already defined.
android:text Sets the text content of the TextView or Button. The value can be a hardcoded string, like android:text="My Application", or a reference to a string resource from the relative to the project file res/values/strings.xml. Referencing string resources is the recommended approach for localization and maintainability. The example below shows the contents of the strings.xml file used in this project.

Code: xml

<resources>
    <string name="app_name">My Application</string>
    <string name="message">Hello World!</string>
</resources>

These values can also be accessed from the Java code using the R class. The R class, which is auto-generated by Android, contains the IDs of all the resources in the res/ directory. Familiarity with these procedures will give us a better understanding of the app during the process of reverse engineering. Let's now see the content of the file MainActivity.java, which contains the Java code of the app.

Code: java

package com.example.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    TextView message;
    Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        message = (TextView)findViewById(R.id.message);
        button = (Button)findViewById(R.id.button);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                message.setText("Hello from Java!");
            }
        });
    }
}

The class MainActivity includes the method OnCreate(). This method is called when the activity is starting, thus everything inside will run automatically. The line setContentView(R.layout.activity_main); indicates that the activity_main.xml file will be used to set the layout of this activity. Method OnCreate() is also used for initializations since it runs when the activity starts. The variables message and button are initialized to point to the corresponding objects found in the activity_main.xml file.

Code: java

message = (TextView)findViewById(R.id.message);
button = (Button)findViewById(R.id.button);

Using the R.java file, the R.id.message points to the android:id="@+id/message" attribute in the activity_main.xml file we saw earlier. Finally, the line message.setText("Hello from Java!"); will be executed as soon as the button is clicked, like the line button.setOnClickListener indicates. Once it's tapped, the text Hello from Java! will be set in the TextView.

Mobile app screen titled "My Application" with a purple button labeled "BUTTON" and text "Hello from Java!" below.

The snippet below shows the same app written in Kotlin. To configure the project to use Kotlin as the default programming language, we must select Kotlin in the Language field on the New Project window while creating a new project in Android Studio. Then, optionally, we can also select Kotlin DSL (build.gradle.kts) in the Build configuration language field to utilize Kotlin syntax for Gradle build scripts.

Code: kotlin

package com.example.myapplication

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.TextView

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val message = findViewById<TextView>(R.id.message)
        val button = findViewById<Button>(R.id.button)

        button.setOnClickListener {
            message.text = "Hello from Java!"
        }
    }
}

Although Kotlin and Java use different syntax, most tools will generate the same pseudocode when reverse engineering the app. This means that it's not necessary for us to master both programming languages. Once the app is developed, we can export a signed APK file ready for installation. Under Build -> Generate Signed Bundle / APK..., we select APK and click Next.

Generate Signed Bundle or APK screen. Options: Android App Bundle (smaller size, on-demand features) or APK. Buttons: Help, Cancel, Next.

On the next window, we click Create new... to create a new key.

Generate Signed Bundle or APK screen. Fields: Module, Key store path, Key store password, Key alias, Key password. Options: Create new, Choose existing, Remember passwords. Buttons: Help, Cancel, Next.

Then, we set the Key store path, Password, Alias, and First and Last Name, and click OK.

New Key Store screen. Fields: Key store path, Password, Key alias, Key password, Validity (25 years), Certificate details (Name: John Doe, Organizational Unit, Organization, City, State, Country Code). Buttons: Cancel, OK.

Once the key is created, we click Next.

Generate Signed Bundle or APK screen. Fields: Module, Key store path, Key store password, Key alias, Key password. Options: Create new, Choose existing, Remember passwords. Buttons: Help, Cancel, Next.

Finally, we select the option release and click on Finish.

Generate Signed Bundle or APK screen. Destination Folder: /Users/bertolis/AndroidStudioProjects/MyApplication/app. Build Variants: debug, release. Buttons: Help, Cancel, Finish.

The signed APK file can be found under the directory ~/AndroidStudioProjects/MyApplication/app/release/, with the name app-release.apk.

Native Apps

root@htb[/htb]$ ls -l ~/AndroidStudioProjects/MyApplication/app/release/

total 8856
-rw-r--r--@ 1 bertolis  bertolis  4527105 May  3 01:44 app-release.apk
-rw-r--r--@ 1 bertolis  bertolis      379 May  3 01:44 output-metadata.json

We can rename and install the exported APK file directly on the device.


Native Code


Native code is compiled to run on a specific processor architecture, which can give apps written in native code a performance advantage on compatible hardware. Android Studio supports the inclusion of native code through the Native Development Kit (NDK), which allows developers to write portions of the app in C or C++. This is often done to reduce latency, optimize for hardware capabilities, or in some cases harden the security of an application.

Native code is typically included in the application as shared libraries (.so files). These libraries can then be invoked from Java or Kotlin using the Java Native Interface (JNI). JNI is a framework that defines how managed code (Java/Kotlin) interacts with unmanaged native code (C/C++), enabling cross-language method calls and data exchange.

Diagram showing interaction between C++ Native Code and Java App via JNI. C++ Function calls Java Method through JNI, which handles function calls and returns results.

Let's create a Native C++ project to get more familiar with the way it works. To accomplish this, first launch Android Studio and navigate to New Project -> Native C++. Proceed to name your app, then in the following window under the C++ Standard section, select Toolchain Default from the dropdown menu. Finally, click Finish.

New Project screen in Android Studio. Templates for Phone and Tablet: Primary/Detail Flow, Navigation Drawer Activity, Responsive Activity, Settings Activity, Scrolling Activity, Tabbed Activity, Fragment + ViewModel, Game Activity (C++), Native C++. Options: Cancel, Next, Finish.

Once the project is created, we notice the native-lib.cpp file found under the App -> cpp folder in the left side section of Android Studio.

Project Explorer showing app structure: manifests with AndroidManifest.xml, java with MainActivity, cpp with native-lib.cpp, res with layout containing activity\_main.xml, and values with colors.xml, strings.xml, themes.

The following snippet of C++ code shows a function that returns the string Hello from C++.

Code: c

#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

The function name Java_com_example_myapplication_MainActivity_stringFromJNI follows the JNI naming convention and indicates that it will be called from the MainActivity class. The line return env->NewStringUTF(hello.c_str()); returns a string to the Java layer. It's important to understand how this function handles calls from Java, as this pattern frequently appears during reverse engineering. Let’s also take a look at the MainActivity.java file to see how the native-lib.cpp library is loaded and how the native function is called.

Code: java

package com.example.myapplication;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    TextView message;

    // Used to load the 'myapplication' library on application startup.
    static {
        System.loadLibrary("myapplication");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        message = (TextView)findViewById(R.id.sample_text);
        message.setText(stringFromJNI());
    }

    /**
     * A native method that is implemented by the 'myapplication' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}

Inside the MainActivity class we can see the line System.loadLibrary("myapplication");. This is where the library is loaded statically, defined by the name myapplication. Listing the content of the file App -> cpp -> CMakeLists.txt reveals the following snippet.

Code: cmake

add_library( # Sets the name of the library.
        myapplication

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        native-lib.cpp)

As we can see, the name of the file native-lib.cpp is set to myapplication. The CMakeLists.txt is used to describe the files of the native C++ project. Back to the MainActivity.java we can also see the line public native String stringFromJNI();, which indicates the declaration of the native method stringFromJNI(). This method returns the string Hello from C++ that we saw earlier in the native-lib.cpp file, and it is then printed on the screen through the TextView message object.

Mobile app screen titled "My Application" with text "Hello from C++" centered on a white background.

We are not limited to only loading a library statically. Libraries can be loaded while the application is running as well. The following snippet of code shows a class that loads the library while the app is running.

Code: java

public class Update {
    // Method declaration
    public native String stringFromJNI();

    // Copy the file locally and load it
    public String update(String path_sd_card, String filesDir){
        FileOutputStream outputStream;
        FileInputStream inputStream;

        try {
            inputStream = new FileInputStream(new File(path_sd_card + "/Download/libupgrade.so"));
            outputStream = new FileOutputStream(new File(filesDir + "/libupgrade.so"));

            FileChannel inChannel = inputStream.getChannel();
            FileChannel outChannel = outputStream.getChannel();
            inChannel.transferTo(0, inChannel.size(), outChannel);
            inputStream.close();
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        System.load(filesDir + "/libupgrade.so");

        // Returns the value of the function stringFromJNI() from the libupgrade.so file
        return stringFromJNI();
    }
}

After the file is copied into the application's home directory, the line System.load(filesDir + "/libupgrade.so"); loads the library. Bad code implementation could lead to remote command execution, a scenario we will see in a later module.


Javascript & WebViews


WebViews are components that allow developers to embed and display web content directly within an Android application. While powerful, improper use of WebView features can introduce serious security risks-a common issue in Android apps. Poor WebView implementation can expose the app to a wide range of vulnerabilities, such cross-site scripting (XSS) and local file inclusion (LFI). Because of this, the official Android documentation recommends using the system's default browser to deliver web content whenever possible, instead of embedding it with a WebView.

In recent Android versions, the default WebView configuration has become more restrictive, mitigating many common attacks by default. However, developers often enable advanced features (such as JavaScript execution or file access) that must be handled with extreme care to avoid introducing vulnerabilities.

In the next section, we’ll walk through creating a simple application using WebViews, HTML, CSS, and JavaScript to better understand how WebViews function. Before we begin, make sure to create a new project as described in the Android Studio section. The following XML snippet will define a WebView element inside the activity_main.xml layout file.

Code: xml

<WebView
        android:id="@+id/webview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

Let's now create a reference that points to this object, in the MainActivity.java file.

Code: java

package com.example.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.webkit.WebView;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        WebView webview = (WebView) findViewById(R.id.webview);
        webview.getSettings().setJavaScriptEnabled(true);
        webview.loadUrl("file:///android_asset/html/index.html");
    }
}

The above code will create a reference to the WebView object and load the index.html file, which we are going to create next. The line webview.getSettings().setJavaScriptEnabled(true); means that the WebView is allowed to execute Javascript code, and this is where vulnerabilities can occur. Android Studio displays the following message if this feature is used in the code.

Code snippet enabling JavaScript in a WebView with a warning about potential XSS vulnerabilities. Details: setJavaScriptEnabled(true), API level 1, default is false. Options to suppress warning or view more actions.

The HTML and Javascript files are usually placed under the folder app/assets (or from the Project view: app/src/main/assets). If the folder is missing, we can simply right-click on app and create a new folder named assets. The following is an example of a project structure that includes HTML, CSS, and Javascript.

Project Explorer showing app structure: manifests, java, generated java, assets with css (style.css), html (index.html), js (script.js).

Here are the snippets of the index.html and the script.js files. The HTML code calls the Javascript function printMessage() which then prints the message Hello from Javascript to the screen.

Code: html

<html>
<head>
    <link rel="stylesheet" href="../css/style.css">
    <script src="../js/script.js"></script>
</head>
<body>
<h1>
    <script>printMessage()</script>
</h1>
</body>
</html>

Code: javascript

function printMessage() {
    document.write("Hello from Javascript");
}

Mobile app screen titled "My Application" with text "Hello from Javascript" centered on a white background.

WebViews can also load content from sources outside the local project. Assuming we want to load Google's home page in the app we just created, then the webview.loadUrl("file:///android_asset/html/index.html"); will change to webview.loadUrl("https://www.google.com/");.

The permission <uses-permission android:name="android.permission.INTERNET" /> also needs to be added to the AndroidManifest.xml file, before the <application> </application> tags.

XML code snippet for Android manifest. Includes permission for internet access: \<uses-permission android:name="android.permission.INTERNET" />.

The picture below shows the Google page embedded in the app we created.

Mobile app screen titled "My Application" displaying the Google logo with a search bar below.

\


Application Frameworks


Building Android applications from scratch can be challenging as it requires knowledge of multiple languages and tools. Consequently, Android developers often use frameworks to develop applications faster, with better code quality, and simpler maintainability. An application framework is a set of libraries that provides developers with a structured way to build applications using pre-built components and tools. These often include UI elements, security and authentication mechanisms, error handling, logging systems, and more. Different application frameworks are used across various IDEs (Integrated Development Environments) and programming languages, which increases the overall attack surface. As a result, different methodologies are required when performing application penetration testing. In the following sections, we’ll explore some examples of using the most popular application frameworks for Android development. These examples are only provided to enhance our understanding of the concepts, and you are not required to reproduce the steps.


Flutter

Flutter is an open-source mobile application framework developed by Google. With Flutter, developers can build applications in the Dart programming language, using customizable widgets that can be combined to create complex user interfaces. As a cross-platform application framework, development is possible for Android, iOS, Web, and Desktop applications providing high performance through compiled native code.

Flutter application development can be done by downloading the Flutter SDK from the official website and setting up an IDE (like Android Studio) with the Flutter and Dart plugins. In the following example, we will see a simple app created with Flutter and discuss the project's structure, so we can better understand it while performing static analysis in later sections. The following snippet is a simple Hello World application written in Dart.

Code: dart

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Hello World App',
      home: Scaffold(
        appBar: AppBar(
          title: Text('My Flatter App'),
        ),
        body: Center(
          child: Text(
            'Hello from Flutter',
            style: TextStyle(fontSize: 28),
          ),
        ),
      ),
    );
  }
}

The above snippet of code will print the text Hello from Flutter in the center of the screen.

Mobile app screen titled "My Flutter App" with text "Hello from Flutter" centered on a white background.

In the screenshot below, we can see that the project structure includes directories that contain data responsible for compatibility with various platforms. Alongside them, we can also see the directory lib that contains the file main.dart. This is where the Dart code resides.

Project Explorer showing Flutter app structure: .dart\_tool, .idea, android, build, ios, lib with main.dart, linux, macos, test, web, windows.

As we mentioned earlier, Flutter compiles the code natively, and thus the app will store the compiled C++ code in shared libraries (.so files). However, penetration testers can still decode and examine the resources during the static analysis using the appropriate tools. Incidentally, reading the code of an application that includes native components requires a different approach than analyzing a typical Android app written in Java or Kotlin. This is because the tools used to decompile Java bytecode into human-readable pseudocode are not effective for shared libraries (.so files) containing compiled C or C++ code. Analyzing these native binaries requires specialized tools and techniques, which will be covered in later sections.


Xamarin

Xamarin is a cross-platform application development framework that supports building Android, iOS, and desktop applications. Owned by Microsoft, Xamarin allows developers to create Android apps using C# as the primary programming language within Visual Studio. To get started, you can install the Mobile development with .NET workload in Visual Studio. The following snippet shows a simple "Hello World" application written in C# using Xamarin.

Code: c

using Android.App;
using Android.OS;
using Android.Runtime;
using Android.Widget;
using AndroidX.AppCompat.App;

namespace MyApplication
{
    [Activity(Label = "@string/app_name", Theme = "@style/AppTheme", MainLauncher = true)]
    public class MainActivity : AppCompatActivity
    {
        Button button;
        TextView message;

        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
            Xamarin.Essentials.Platform.Init(this, savedInstanceState);
            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.activity_main);

            message = FindViewById<TextView>(Resource.Id.message);
            button = FindViewById<Button>(Resource.Id.button);

            button.Click += (sender, args) =>
            {
                message.Text = "Hello World!";
            };
        }
        public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
        {
            Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
            base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }
}

The above application will print the message Hello World! on the screen once the button is pressed.

Mobile app screen titled "MyApplication" with text "My Application" at the top, a blue "BUTTON" in the center, and "Hello World!" at the bottom.

The screenshot below shows the project structure of a Xamarin application in Visual Studio.

Solution Explorer showing MyApplication structure: References, Packages (3 updates), Assets, Properties with AndroidManifest.xml, Resources with drawable, layout (activity\_main.xml), mipmap folders, values, AboutResources.txt, Resource.designer.cs, MainActivity.cs.

When creating a Xamarin app, the C# source code is compiled into Common Intermediate Language (CIL) using the .NET compiler. This intermediate code is then interpreted or just-in-time compiled into platform-specific machine code at runtime, depending on the target environment. Unlike native C++ code, which is compiled into shared libraries (.so files), Xamarin applications bundle their intermediate code as .NET assemblies (.dll files), often packaged inside a single file like assemblies.blob.

Since Xamarin apps contain intermediate code rather than native binaries, the reverse engineering process differs from analyzing native C++ or Java/Kotlin applications. The extracted .dll files can be loaded into .NET reverse engineering tools (such as ILSpy, dnSpy, or dotPeek) to recover readable pseudocode. These methodologies will be discussed in later sections.


Other Frameworks

More application development frameworks can be used to create Android applications, with some of them being React Native, Apache Cordova (formerly PhoneGap), and Ionic. These frameworks can create hybrid cross-platform applications that run on Android, iOS, and web browsers using web-based technologies like Javascript, HTML, and CSS. We can install these frameworks using the NPM (Node Package Manager) command-line tool and start the development on Android Studio. Applications can then run on a Physical or virtual device. The following snippet is a simple Hello World using the React Native framework. This page will guide you through building and running a React Native app.

Code: react

import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View } from 'react-native';

export default function App() {
  return (
    <View style={styles.container}>
      <Text style={styles.text}>Hello From React Native</Text>
      <StatusBar style="auto" />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  text: {
    fontSize: 28,
  },
});

The snippet above will print Hello From React Native on the screen.

Mobile app screen with text "Hello From React Native" centered on a white background.

When an application is developed using React Native, the majority of the application's logic and UI are written in JavaScript. The framework will also create the MainActivity and other necessary Java classes that act as the entry point for your application. When the application is prepared for release, the JavaScript code will be bundled into a standalone file called index.android.bundle. This file is optimized and minified to improve performance and reduce the overall size of the application. While reversing apps created with React Native, apart from analyzing the Java code to identify the necessary entry points, testers should also analyze the Javascript code bundled in the index.android.bundle file. Another thing testers should keep in mind is that the attack surface will be different than native apps. Apps created with such frameworks may be susceptible to web vulnerabilities since they use web technologies.

On the other hand, apps created with Cordova and Ionic frameworks use a WebView component to render the user interface and execute the application code, which is HTML, CSS, and JavaScript. When we build an app using Cordova or Ionic, the web assets (HTML, CSS, JavaScript files) are packaged within the application as part of the project structure and can be found during reverse engineering under the directories assets/www/ and assets/public/ accordingly. Reversing apps created with such frameworks will be further discussed in a later section.


Activities


Application Components

Application components are the building blocks that define different parts of an Android application, such as the user interface and core functionality. These components are declared in the AndroidManifest.xml and can be used individually or in tandem with one another. Interprocess Communication (IPC) is a mechanism that allows for communication between applications or different processes within the same application. In the second case, applications usually consist of components that run in different processes, including Activities, Services, Broadcast Receivers, and Content Providers. As we mentioned in the previous sections, in Android each application runs in its own process, and thus, IPC has to make sure that applications have a way to communicate with each other when necessary. In the following paragraphs, we will discuss some of the most essential Application Components and Interprocess Communication mechanisms.

Activities Introduction

Activities are a fundamental application component, representing a single screen with a user interface and able to be presented in several modes, such as full-screen, floating, embedded, or multi-window. An Activity is the main component that allows the interaction between the user and the app, and can be started by other Activities, apps, or system events. Apart from managing and handling the application's user interface and interaction, Activities are also responsible for managing the app's lifecycle.


Activity Lifecycle

The lifecycle of an activity consists of six major stages called callbacks. The class below defines the entire lifecycle of an activity.

Code: java

 public class Activity extends ApplicationContext {
     protected void onCreate(Bundle savedInstanceState);
     protected void onStart();
     protected void onRestart();
     protected void onResume();
     protected void onPause();
     protected void onStop();
     protected void onDestroy();
 }

The system invokes the corresponding callback whenever an Activity enters a new state. Note that an application may only use some of the callbacks. The diagram below shows the lifecycle of an Activity.

Flowchart of Android activity lifecycle: Activity launched, onCreate(), onStart(), onResume() (Activity running), onPause() (another activity in foreground), onStop() (activity not visible), onDestroy() (activity shut down). Includes paths for onRestart() and app process killed.

Next, we will describe the main stages of the activity lifecycle.

onCreate()

In this stage, the activity is first created, and developers can initialize tasks like setting up the user interface, binding data to views, and configuring listeners or handlers. When a new Android project is created, the Android Studio automatically generates the class MainActivity.java which contains the method onCreate(). This is the method called during this stage. The following snippet shows an example of the onCreate() method.

Code: java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

        Toast.makeText(this, "This message will be displayed on the app start-up.", Toast.LENGTH_SHORT).show();
}

This method takes only one parameter, the Bundle savedInstanceState, which contains the activity's previously saved state. The above snippet will print the message on the screen as soon as the app starts. Since many initializations typically occur within this method (including the loading or initialization of sensitive information such as hard-coded credentials or keys), it serves as a key entry point for penetration testers during assessments. Another reason pentesters should pay attention during the examination of this method is that data is often passed as parameters whenever an activity is launched by an Intent, which we will see later in this section. This usually happens when the developer wants to send data from one activity to another, like session tokens or other values needed for the smooth operation of the application.

onStart()

Once the Created callback has been completed, it is always followed by the Started callback, where everything becomes visible to the user. The method onStart() is called when the activity is brought to the foreground and starts interacting with the user. At this point, resources are typically initialized in this lifecycle stage.

onResume()

Once the activity starts interacting with the user, the onResume() method will be called. Animations and other media, or interaction with the user, typically take place in this stage of the lifecycle. The Paused callback always follows this stage.

onPause()

If the user switches to another app or a dialog appears on top of the activity, it will no longer be active or focused. In this state, although the activity will remain visible to the user, any resource no longer needed is released. It is followed by the onResume() or the onStop() callbacks.

onStop()

At this point, the activity is no longer visible to the user and the resources are released. This activity is followed by either onRestart() or onDestroy().

onDestroy

At this point, if the system needs any memory or space resources, the activity will be destroyed to free them up. The activity will also be destroyed if the user chooses to close the activity.

onRestart()

In case the activity is restarting after having been stopped, the onRestart() will be called, followed by the onStart(). Initializations can be done again if needed at this point.


Launching an Activity

Launching an Activity in Android is a fundamental concept. An Activity represents a single screen with a user interface responsible for managing user interaction with the application. The following steps describe what is happening while launching an Activity.

Intent creation

To start an Activity programmatically, we first create an Intent object. Intents are messaging objects used to request an action from another component from the same or other applications. The target Activity and any other additional data required can be specified in the Intent object.

Code: java

// In the source Activity (e.g., MainActivity.java)
Intent intent = new Intent(this, TargetActivity.class);
// Optionally, you can add extra data to the Intent
intent.putExtra("key", "test");

As we can see, the parameter key with the value test has also been passed using the property putExtra().

Requesting Activity launch

Next, the startActivity() or startActivityForResult() is called from the source Activity, passing the Intent object as a parameter. The startActivity() is used to launch an Activity without expecting any result back, while startActivityForResult() is used when we expect results from the launched Activity.

Code: java

// For launching an Activity without expecting any result back
startActivity(intent);

// For launching an Activity and expecting a result back
int requestCode = 1; // A unique integer request code to identify the result
startActivityForResult(intent, requestCode);

Activity stack management

The Android operating system maintains an Activity stack as part of the task that the app belongs to. When a new Activity is launched, it's placed on top of the stack and becomes the active Activity. The previous Activity is paused and remains in the stack. The following image shows the progress between activities and the current back stack at each point.

Diagram of activity back stack: Start Activity 2, then Activity 3. Foreground activity changes from Activity 1 to 3. Navigate back to Activity 2 after Activity 3 is destroyed.

Activity lifecycle transitions

In this stage, the source Activity's onPause() method is called, and it becomes inactive. Meanwhile, the target Activity goes through a series of lifecycle methods, including onCreate(), onStart(), and onResume(), initializing its UI, setting up required resources, and starting any necessary background tasks. The necessary lifecycle methods should be implemented in the target Activity (e.g., TargetActivity.java).

Code: java

public class TargetActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_target);

        // Get data from the Intent
        String data = getIntent().getStringExtra("key");
    }

    // Other lifecycle methods, like onStart(), onResume(), onPause(), onStop(), onDestroy()
}

User interaction

The new Activity becomes visible and users can interact. When the user decides to navigate back, the current Activity is popped from the stack and its onPause(), onStop(), and onDestroy() lifecycle methods are called. The previous Activity in the stack becomes active again, resuming its onRestart(), onStart(), and onResume() lifecycle methods. If the target Activity needs to return any results, the code in it will look like this.

Code: java

// Set result and finish the Activity
Intent resultIntent = new Intent();
resultIntent.putExtra("result_key", "result_value");
setResult(RESULT_OK, resultIntent);
finish();

Returning a result (optional)

If the launched Activity was started using startActivityForResult(), it can return results to the calling Activity. This is done by calling setResult() in the launched Activity, followed by finish(). The calling Activity will then receive the result in its onActivityResult() method, where it can process the data accordingly. In the source Activity, the method that handles the results would look like this.

Code: java

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == 1) { // Match the request code used in startActivityForResult()
        if (resultCode == RESULT_OK && data != null) {
            String resultData = data.getStringExtra("result_key");
            // Process the result data
        }
    }
}

The following flowchart shows the above steps.

Flowchart of activity result process: Source Activity creates Intent. If expecting result, use startActivityForResult; otherwise, use startActivity. Target Activity lifecycle: onCreate, onStart, onResume. User interaction may return result, setting it and finishing, or just finish. Result handled in onActivityResult.

Apart from starting an activity by tapping the icon or through other applications, this can be done using ADB (Android Debug Bridge). Android Debug Bridge is a command-line tool that allows you to communicate with an Android device (emulator or physical device). It is primarily used for debugging, development, and testing purposes. Accessing Activities with the exported attribute set to true directly from ADB is possible, and this can sometimes raise security concerns.


Declaring Activities

In order to use an Activity properly, you must declare it in your app's manifest file. In Android, this file is called AndroidManifest.xml, and as we mentioned in previous sections, it is a configuration file that provides essential information about the application to the Android system. This information includes the app components, permissions, and other metadata. After creating a new Activity, it should be declared using the <activity> element as a child of the <application> element, as shown in the example below. The android:name attribute should contain the fully qualified Activity class name.

Code: xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapp">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <!-- Declare your Activity here -->
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!-- Declare other Activities if needed -->
        <!-- <activity android:name=".AnotherActivity" /> -->

    </application>

</manifest>

In the above snippet, the android.intent.action.MAIN action indicates that the MainActivity is the entry point of the app. This means it is the first Activity launched when the app starts. This action is typically used for the home screen of an app. While the activity name MainActivity is usually used as an entry point in Android applications, this name can be changed. Identifying the entry point of an application is very important during penetration testing since testers can better understand the application's flow, functionality, and overall structure, discover possible attack surfaces and eventually identify potential vulnerabilities and weaknesses. The second property, android.intent.category.LAUNCHER tells the Android system that this Activity should be listed in the system's app launcher. So when the user taps on the application's icon in the launcher, this Activity should be started.

Some activities may include the attribute exported. This attribute is used to specify whether other apps on the device can access an app component. The exported attribute can be set on <activity>, <service>, <receiver>, and <provider> elements in the AndroidManifest.xml file, and it accepts the boolean values true or false. Setting the exported attribute to true means that other apps on the device can access and invoke the app component. This configuration might expose the app to security risks. The following example shows an activity with the exported attribute set to true.

Code: xml

<manifest ...>
    <application ...>
        <activity
            android:name=".MyCustomActivity"
            android:exported="true">
            <!-- Intent filters if needed -->
        </activity>
    </application>
</manifest>

Services


A Service is an Android application component that performs long-running operations in the background without providing a user interface. Services can be used for tasks like downloading files, playing music, or communicating with a remote server, and can continue working even after the user has left the app. There are three types of services in Android.

Foreground Service

Foreground services perform operations that require user attention. They will provide users with notifications and continue running, even when the app has no interaction or is minimized. Foreground services must display notifications to inform the users that the service is running. Examples of such services include media players and navigation apps. A foreground service can be started by calling the startService() method.

Background Service

Background services perform operations that do not require user interaction. Starting with Android API level 26 (Android 8.0 Oreo), background services are no longer allowed to run unless the application is in the foreground. This change was introduced to conserve system resources and optimize battery life.

Bound Service

Bound services allow other application components to bind to them by calling the bindService() method. They provide a client-server interface that enables components—even across different processes—to interact with the service using Interprocess Communication (IPC).

Services extend the Service class.

Code: java

public class ExampleService extends Service {
    int startMode;       // indicates how to behave if the service is killed
    IBinder binder;      // interface for clients that bind
    boolean allowRebind; // indicates whether onRebind should be used
    ...
    }
}

Similar to activities, services have lifecycle callback methods that must be implemented to monitor changes in their state. The following diagram shows the service's lifecycle callback methods. On the left, the service is created using startService(), while on the right, using bindService().

Flowchart comparing unbounded and bounded service lifecycles. Unbounded: startService(), onCreate(), onStartCommand(), service running, stopped by itself or client, onDestroy(), shut down. Bounded: bindService(), onCreate(), onBind(), clients bound, unbindService(), onUnbind(), onDestroy(), shut down. Active lifetime highlighted.

Services must be declared in the AndroidManifest.xml file.

Code: xml

<manifest ...>
    <application ...>
        <service android:name=".MyForegroundService"/>
        <service android:name=".MyBackgroundService"/>
    </application>
</manifest>

Broadcast Receivers


Broadcast Receivers can be considered as both Application Components and Interprocess Communication (IPC) mechanisms. As an IPC mechanism, Broadcast Receivers enable communication between different applications by sending and receiving Intents. These Intents can be sent by the Android system, other apps, or the app itself. As an Application Component, Broadcast Receivers are designed to respond to system-wide or custom events broadcasted by other applications. Broadcast Receivers can act as a messaging system between different components across the Android ecosystem. For example, the system broadcasts an event when the device starts charging. Similarly, an app can send a custom broadcast to let other apps know that new data has been downloaded. Broadcast Receivers extend the BroadcastReceiver class and override the onReceive() method to match a specified Intent Filter declared in the AndroidManifest.xml. The following example shows a Broadcast Receiver handling an event where the device is charging.

Code: java

public class MyBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();

        if (action != null) {
            switch (action) {
                case Intent.ACTION_POWER_CONNECTED:
                    // Handle the power connected event
                    break;
                case Intent.ACTION_POWER_DISCONNECTED:
                    // Handle the power disconnected event
                    break;
                default:
                    // Handle other actions as needed
                    break;
            }
        }
    }
}

Broadcast Receivers also need to be declared in the AndroidManifest.xml file.

Code: xml

<manifest ...>
    <application ...>
        <receiver android:name=".MyBroadcastReceiver">
            <intent-filter>
                <action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
                <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
            </intent-filter>
        </receiver>
    </application>
</manifest>

The following methods are used for sending broadcasts to different kinds of receivers.

Method Description
sendOrderedBroadcast(Intent, String) Sends broadcasts to one receiver at a time.
sendBroadcast(Intent) Sends broadcasts to all receivers in an undefined order.
localBroadcastManager.sendBroadcast(intent) Send Intent broadcasts to local objects within your process. This method is deprecated since API 28, and LiveData is used instead.

Similar to Activities, broadcasting messages through ADB is also possible, which will be discussed in later sections. Beginning with Android 8.0 (API level 26), the system imposes additional restrictions on manifest-declared receivers. Manifest can not be used to declare receivers for most implicit broadcasts (broadcasts that don't target your app specifically). However, exceptions exist and can be found here.


Content Providers


Content Providers can be considered as Application Components and Interprocess Communication (IPC) mechanisms. As an IPC mechanism, Content Providers enable communication between applications by allowing them to access, modify, or delete data using a consistent interface through the ContentResolver class. As an application component, Content Providers are responsible for managing and exposing data structures within or to other apps. At the same time, they allow data sharing between different components in the app or with external apps. In other words, they act as the intermediate between the app and its underlying data storage. Content Providers use a standardized API based on the CRUD (Create, Read, Update, Delete) operations to interact with data. The data handled by a Content Provider can be stored in multiple structures, including local SQLite databases, the device's internal or external storage, or even on a remote server.

Diagram showing your application with content provider implementation connected to data storage and other applications.

Accessing a ContentProvider is typically done asynchronously in the background using a CursorLoader to execute queries. The Activity or UI component initiates a request to the CursorLoader, which performs the query by accessing the ContentProvider via the ContentResolver. This approach keeps the UI responsive while executing the query. The process involves multiple components, as demonstrated in the following image.

Flowchart showing data flow: Activity or Fragment to CursorLoader, to ContentResolver, to ContentProvider, connected to Data Storage.

The following code snippet retrieves words and their locales from the User Dictionary Provider. A User Dictionary Provider is a ContentProvider in Android that manages the user's custom dictionary. To achieve this, it calls ContentResolver.query(), which in turn invokes the ContentProvider.query() method implemented by the User Dictionary Provider.

Code: java

// Queries the user dictionary and returns results
cursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
    projection,                        // The columns to return for each row
    selectionClause,                   // Selection criteria
    selectionArgs,                     // Selection criteria
    sortOrder);                        // The sort order for the returned rows

Content Providers extend the ContentProvider class.

Code: java

public class MyContentProvider extends ContentProvider {
    // Implement required CRUD methods and other logic here
}

Content Providers, along with the permissions required to access the provider's data, must be declared in the AndroidManifest.xml file.

Code: xml

<manifest ...>
    <application ...>
        <provider
            android:name=".MyContentProvider"
            android:authorities="com.example.myapp.provider"
            android:exported="false" />
    </application>
</manifest>

Similar to Activities and Broadcast Receivers, Content Providers can be accessed using the Android Debug Bridge (ADB) through the terminal.


Intents


As mentioned in the previous section, Intents are messaging objects used by applications or the Android system to request actions from other components such as Activities, Services, and Broadcast receivers. While Intents are not primarily designed for Interprocess Communication (IPC), they may be used when an application wants to interact with a component (such as a service) that resides in a different process. There are three essential use cases for Intents, shown below with practical examples.

Starting an Activity

Intents are commonly used to launch new activities, passing data between components.

Code: java

/* Navigating from a list of contacts to a detailed view of the selected contact. 
   In the source Activity (ContactListActivity.java), an explicit Intent tells 
   Android to launch the target Activity (ContactDetailActivity.java) and passes 
   the selected contact's ID as extra data. This allows the target activity to 
   retrieve and display the correct contact details. */

Intent intent = new Intent(this, ContactDetailActivity.class);
intent.putExtra("contact_id", selectedContactId);
startActivity(intent);

Starting a Service

Services are used for background operations, and Intents are used to start or bind to them.

Code: java

/* Downloading a file in the background. This code starts a background Service
   (DownloadService) to handle a file download. An explicit Intent specifies the
   target Service class and attaches the file URL as extra data. The Service can
   then retrieve the URL from the Intent and begin the download operation in the 
   background. */

Intent intent = new Intent(this, DownloadService.class);
intent.putExtra("file_url", fileUrl);
startService(intent);

Delivering a Broadcast

Broadcasts allow apps to send or listen for system-wide or app-specific events.

Code: java

/* Informing other components that the battery is low. This code sends a custom
   broadcast with the action string `com.example.ACTION_BATTERY_LOW`. Any component 
   (within the same app or across apps) that has registered a BroadcastReceiver with
   a matching Intent filter will be notified when this broadcast is sent. */

Intent intent = new Intent("com.example.ACTION_BATTERY_LOW");
sendBroadcast(intent);

Next we will examine the two types of Intent IPC.

Explicit Intents

Explicit Intents are commonly used for navigating between activities within the same app or starting services. The target component (activity, service, or broadcast receiver) should be known and can be created by specifying the target component's class name in the Intent constructor.

Code: java

Intent intent = new Intent(this, TargetActivity.class);
startActivity(intent);

Implicit Intents

Implicit Intents are used when we don't know the exact target component, but know the action we want to perform and want the system to find a suitable component to handle the request. To create an implicit Intent, we must specify the action and the data (URI).

Code: java

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://www.example.com"));
startActivity(intent);

In addition, Intents can also carry data between components in the form of key-value pairs called extras.

Code: java

Intent intent = new Intent(this, TargetActivity.class);
intent.putExtra("key", "value");
startActivity(intent);

The following image shows how an implicit intent is delivered through the system to start another activity.

Flowchart showing Activity A using startActivity() to send an Intent to the Android System, which then calls onCreate() for Activity B. Steps labeled 1, 2, and 3.

Much like Application Components, Intents can be created using the Android Debug Bridge (ADB) through the terminal. Understanding and analyzing Intents while assessing an application is crucial—not only for gaining insight into the app’s flow but also for identifying potential security bypasses.


Binders


The Binder is Android's core Interprocess Communication (IPC) mechanism, enabling efficient and secure communication between different processes. It is built on a Remote Procedure Call (RPC) model, allowing a client process to invoke methods on a remote object located in another process as if the object were local.

Throughout the following paragraphs, "remote service" refers to a service running within the same application but in a different process. Binders are typically used through a Service that implements an interface defined in an AIDL file, which specifies the methods, parameters, and return values for IPC. The Service provides the requested functionality, while the Binder facilitates communication between the client and the service. The following code snippets demonstrate how a Service uses Binders to communicate with a remote client.

ICalculator.aidl

Here we see snippet of the ICalculator.aidl file containing the method's declaration.

Code: java

interface ICalculator {
    int add(int a, int b);
}

Next, we have a snippet of the CalculatorService.java file, which creates the service and implements the interface defined in the AIDL file.

Code: java

public class CalculatorService extends Service {
    private final ICalculator.Stub binder = new ICalculator.Stub() {
        @Override
        public int add(int a, int b) {
            return a + b;
        }

    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }
}

MainActivity.java

We now come to a snippet of the MainActivity.java file connecting and binding to the remote service CalculatorService, and subsequently calling its methods. Connecting to a service involves establishing a link with the service to communicate and interact with it, while binding to a service establishes a long-lasting connection between a client (such as an Activity) and a service. This allows the client to interact with the service, invoke its methods, and receive results synchronously.

Code: java

// Connecting to the remote service
private ServiceConnection serviceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        calculatorService = ICalculator.Stub.asInterface(service);
        performCalculations();
    }
        ...
};
....

// Binding to the remote service
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Intent intent = new Intent();
    intent.setComponent(new ComponentName("com.example.calculatorservice", "com.example.calculatorservice.CalculatorService"));
    bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
...

// Calling the methods
private void performCalculations() {
    if (calculatorService == null) {
        return;
    }

    try {
        int additionResult = calculatorService.add(10, 5);

        // Use the results as needed, e.g., display them in the UI
        // ...

    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

Binders are not declared in the manifest file directly, as they are a part of the Service implementation. However, if the Service runs in a different process, the attribute android:process should be specified in the AndroidManifest.xml file.

Code: xml

<manifest ...>
    <application ...>
        <service
            android:name=".MyService"
            android:process=":remote" />
    </application>
</manifest>


A Deep Link is an Interprocess Communication (IPC) mechanism that allows users to navigate directly to specific content within an app by tapping a URL found on a website, email, or any other location where links can be placed.

For example, a user might receive a promotional email about a flash sale on a specific product. Instead of directing the user to the website, the link opens the corresponding app to display the product. In some cases, if the app is not installed, the user is redirected to the app store to download and install it.The are two types of Deep Links, the Standard Deep Link, and the Android App Link.

Standard Deep Link

The example below demonstrates a website that provides deep links to list its computer products through the mobile app. The source code of the website looks like this:

Code: html

<div>
    <p>Buy our latest PC parts.</p>
    <a href="app://myapp/products/cpu"> </a>
</div>

In order for the above URL to open within the application, we must set up an intent filter in the Androidmanifest.xml file for the corresponding activity.

Code: xml

<activity android:name=".ProductsActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="app"
              android:host="myapp"
              android:pathPrefix="/products/" />
    </intent-filter>
</activity>

The table below provides a description of the most important elements of the above snippet.

Element Description
<activity android:name=".ProductsActivity"> Defines the activity to be launched once the link is tapped.
android:scheme="app" Sets the protocol. It defines the app (can be anything) part of the URL app://myapp/products/cpu included in the website.
android:host="myapp" Sets the host. It defines the myapp (can be anything) part of the URL app://myapp/products/cpu included in the website.
android:pathPrefix="/products/" /> Sets the path prefix. It defines the /products/ part of the URL app://myapp/products/cpu included in the website.

Now that we have set up the intent filter properly, let's take a look at the following Java snippet to see how it handles the incoming intent.

Code: java

public class ProductActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_planet);

        Intent intent = getIntent();
        String action = intent.getAction();
        Uri data = intent.getData();

        if (Intent.ACTION_VIEW.equals(action) && data != null) {
            String ProductName = data.getLastPathSegment();

              if (ProductName.equals("cpu")) {
                // Do something. For example, query the database for information on this product.
            }
        }
    }
}

In the above Java snippet, the if() statement checks if the value returned from the data.getLastPathSegment() method is equal to cpu. The value returned from the data.getLastPathSegment()method is actually the cpu part of the URL app://myapp/products/cpu.

This is how Android handles a Standard Deep Link. While deep linking is a powerful mechanism, security risks may arise from improper implementation. In the example above, the android:scheme attribute is set to app, and the android:host is set to myapp. However, Android does not enforce ownership verification for custom schemes, meaning any malicious app can declare itself as the default handler for that scheme, potentially leading to security vulnerabilities. To mitigate this risk, Android App Links should be used.

Android App Link

Assuming that the URL https://www.myapp.com/ leads to an existing website, the deep link in it would look like this.

Code: html

<div>
    <p>Buy our latest PC parts.</p>
    <a href="https://www.myapp.com/products/cpu"> </a>
</div>

Accordingly, the Androidmanifest.xml file will contain the following.

Code: xml

<activity android:name=".ProductsActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="https"
              android:host="www.myapp.com"
              android:pathPrefix="/products/" />
    </intent-filter>
</activity>

We notice that the android:scheme attribute is set to https, and the android:host is set to www.myapp.com. In this case, if the app that handles the deep link isn't installed, the link will open in a web browser listing the products. This is a feature added on Android 6.0 and higher, ensuring that only the verified domain owner can handle links to that domain within their app, and other potential malicious apps won't be able to handle this link.

On the other hand, bad programming could still lead to security issues. Imagine an application handling the URL https://www.myapp.com/home?uid=50&token=RLsB?19oYMAL6M5v. If the app doesn't verify the uid and token parameters passed in via the deep link, a malicious actor could craft their own link with different parameter values, leading to unauthorized access to another user's data. To enhance security when using Deep Links, it is suggested to use Android App Links (which ensure links are verified and securely handled) instead of generic Deep Links. Additionally, one should validate the input of parameters and avoid passing sensitive data through URLs.


Android Emulators


Setting up the testing environment requires our host machine, an emulated Android device, and the tools we will use for the application penetration testing. In the following paragraphs, we will discuss various Android emulators and set up one we are going to be using during the next sections.

Android emulators are a vital tool for application penetration testers. They are programs allowing users to run Android applications and simulate the Android operating system on devices like personal computers. They are widely used for app development, testing, and gaming purposes. Android emulators are a cost-effective solution that allows testing applications on various devices and Android API levels without needing each physical device. Apart from this, emulators also provide most of the capabilities that a real device does, like simulating incoming phone calls and text messages, camera, Bluetooth, sensors, accessing the Google Play Store, and more. Penetration testers can benefit from emulators since they can simulate real-world scenarios and evaluate Android application vulnerabilities.

Most real Android devices use ARM or ARM64 CPU architecture, specifically designed for mobile devices and providing high-energy performance. However, Android emulators use x86 or x86_64 CPU architectures for better performance and compatibility with the host machine's hardware. Different CPU architectures can affect how exploits or vulnerabilities behave in emulated environments compared to real devices. In the following sections, we will use Android Virtual Device (AVD) for testing applications, which is included in the Android Studio IDE.


Android Studio AVD

Android Studio's AVD is a fast and feature-rich emulator that allows us to test applications. It will help us run and debug applications developed with Android Studio in order to exploit other apps. Android Studio includes the AVD Manager, which simplifies creating, configuring, and managing Android Virtual Devices to simulate real-world scenarios. The following steps will show how to set up an Android Virtual Device (AVD) using Android Studio.

Note: It is not recommended to run the Android emulator within a Virtual Machine, as nested virtualization can cause significant performance delays.

Since we have already seen how to install Android Studio and create a new project, let's jump directly to creating the AVD. In a new Android project, navigate to Tools -> Device Manager.

System Image selection screen in Virtual Device Configuration. Tiramisu API Level 33, x86\_64, Android Tiramisu (Google APIs) is highlighted. Details on the right show API Level 33, Android Tiramisu by Google Inc., System Image x86\_64. Options to cancel, go to the previous or next step, and finish.

On the right side of the Android Studio, the Device Manager window will pop up, containing a default virtual device (Pixel 3a XL API 33) that might be created while installing Android Studio. The following image shows an example of the Device Manager window, having many devices created.

Tools menu in an IDE with "Device Manager" highlighted. Options include Tasks & Contexts, SDK Manager, Resource Manager, and more. Code editor visible in the background.

Let's see the process of creating a new device by clicking on the button under Tools -> Device Manager -> Create device. Along with other properties, an AVD contains a hardware profile that defines the characteristics of a device as shipped from the factory. The Device Manager comes pre-equipped with certain hardware profiles (e.g., Pixel devices), allowing users to define or tailor these profiles according to their requirements.

Device Manager showing virtual devices: Nexus 5X API 25, Pixel 3a XL API 33, Pixel 4 API 30, Pixel 4 XL API 30. Details include API level, size on disk, and actions. Options to create a device and switch between virtual and physical tabs.

Navigating to the x86 Images we can see the different Android API levels. The Google Play logo in the Play Store column indicates that the device includes Google Play Store and access to other services. Assuming we select the Pixel 4 device, moving on to the next step, we are prompted to choose a system image.

Select Hardware screen in Virtual Device Configuration. Pixel 4 is highlighted with details: 5.7" size, 1080x2280 resolution, 440dpi density. Options to choose other devices, create a new hardware profile, and navigate with cancel, previous, next, and finish buttons.

System images labeled with Google APIs provide access only to Google Play services, while images labeled with Google Play also provide a Google Play Store. If the download icon appears near the Release Name, the image is not installed, and we must download it. System images that include the Google Play Store are signed with a release key, and thus elevated privileges (root) are not supported. On the other hand, system images that only include Google APIs will allow elevated privileges (root) by using the ADB tool through the terminal. This is a necessary feature that we will utilize while conducting application penetration testing. Assuming we select the Tiramisu 33 x86_64 (Google APIs) image, moving on to the next step, we are prompted to give the AVD a name and verify the configurations.

Android Virtual Device Configuration screen. AVD Name: Pixel 4 API 33. Device: Pixel 4, 5.7" 1080x2280 440dpi. System: Tiramisu x86\_64. Startup orientation: Portrait. Options to change settings, show advanced settings, and navigate with cancel, previous, next, and finish buttons.

Once we click the Finish button and create the device, we can find it and start it in the Device Manager by clicking the Play button under the Actions tab.

Device Manager showing virtual devices. "Pixel 4 API 33 (My First AVD)" is highlighted. Details: API 33, 513 MB size. Options to create a device and launch AVD in the emulator.

Once the emulator is launched, it will look like this.

Android emulator displaying a smartphone home screen with time, date, and app icons. Emulator controls are visible on the right side.

On the device's right side, there is a vertical menu bar. We can stop the emulator by clicking the x icon on the top. Finally, among other action buttons in the menu bar, there is a 3-dot icon that contains various device settings we can configure as the device is running.


Other Emulators

Although Android Studio AVD is the emulator we will use throughout the following sections, several other emulators are available with unique features and capabilities. Some of the popular alternatives include the following emulators.

Corellium
Supports a wide range of mobile devices including both Android and iOS operating systems.
Offers a web-based interface.
Provides integrated advanced debugging tools.
Enables users to take snapshots of the emulator.
Emulates both standard and rooted/jailbroken devices.
Genymotion
Designed for app developers and testers
Offers a wide range of virtual devices with different Android versions
Supports advanced features like camera, GPS, and accelerometer emulation
Integrates with development platforms like Android Studio and Xamarin
Available for Windows, macOS, and Linux
BlueStacks
Widely popular and user-friendly
Multi-instance feature for running multiple apps simultaneously
Integrates with gaming platforms like Twitch and Discord
NoxPlayer
Lightweight and efficient emulator
Supports gamepad, keyboard, and script recording
Offers advanced features like GPS spoofing and virtual location
MEmu Play
High performance and compatibility
Offers multiple Android versions
Allows multiple instances and easy app cloning
Keyboard mapping and joystick support
LDPlayer
Optimized for gaming and productivity apps
Customizable controls and graphics settings
Multi-instance support and synchronization

Android Debug Bridge


Android Debug Bridge (ADB) is a versatile command-line tool that enables communication between a computer and a device. As a part of the Android SDK (Software Development Kit), it allows developers to perform tasks like installing and debugging applications, transferring files between the host computer and the device, and accessing the device through a shell. Android Debug Bridge is a client-server program that can be used on physical and emulated devices, and it includes the following three components.

Components Description
The client It runs on the host machine and can be invoked from the terminal by issuing the adb command.
The daemon (adbd) It runs as a background process on each device, and it is responsible for executing the commands in the device.
The server It runs as a background process on the host machine, and it manages the communication between the client and the daemon.

The following steps show how the ADB connection process works.

Step 1

The ADB client will start as soon as we issue an adb command in the terminal. When this happens, the client will check for an adb server process running on the host machine. If there isn't one, it starts the server process, which binds to the local TCP port 5037 and listens for commands sent from the adb client.

Step 2

The server then locates the connected emulators by scanning the odd-numbered ports from 5555 to 5585. Finally, it sets up the connection with the emulators that run an ADB daemon (adbd).

Step 3

Each emulator instance has a unique pair of ports to avoid conflicts when running multiple emulators simultaneously. These pairs are an even-numbered port for console connections and an odd-numbered port for ADB connections. In the console connection, we can issue commands to control the emulator using tools like telnet. In the ADB connections, we can perform various development and debugging tasks using the adb command. The following is an example of two connected emulators.

Emulator Service Port
Emulator 1 console 5554
Emulator 1 adb 5555
Emulator 2 console 5556
Emulator 2 adb 5557

Step 4

Once the connections are set up, we can use the adb command to access the emulators.


Installing and Using ADB

In the following paragraphs, we will see how to install and use ADB. The example below shows the installation process on three different operating systems.

Debian-based Linux

On Debian-based Linux, through the APT package manager:

Android Debug Bridge

root@htb[/htb]$ apt-get install adb

MacOS

On MacOS, through the Homebrew package manager:

Android Debug Bridge

root@htb[/htb]$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
root@htb[/htb]$ brew update
root@htb[/htb]$ brew install android-platform-tools

Windows

On Windows, through the Scoop command-line installer.

Android Debug Bridge

C:\> Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
C:\> iex (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh')
C:\> scoop bucket add extras
C:\> scoop install adb

ADB is also made available to us as part of the Android SDK Platform Tools package, available through Android Studio's SDK Manager. On Windows, the resulting platform tools including adb.exe are located at C:\Users\<username>\AppData\Local\Android\Sdk\platform-tools

Android Debug Bridge

Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

Install the latest PowerShell for new features and improvements! https://aka.ms/PSWindows

PS C:\Windows\system32> cd C:\Users\Owner\AppData\Local\Android\Sdk\platform-tools

PS C:\Users\Owner\AppData\Local\Android\Sdk\platform-tools> ls


    Directory: C:\Users\Owner\AppData\Local\Android\Sdk\platform-tools


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----         5/17/2024  12:24 PM        5857056 adb.exe
-a----         5/17/2024  12:24 PM         108320 AdbWinApi.dll
-a----         5/17/2024  12:24 PM          73504 AdbWinUsbApi.dll
-a----         5/17/2024  12:24 PM         439072 etc1tool.exe
-a----         5/17/2024  12:24 PM        1807136 fastboot.exe
-a----         5/17/2024  12:24 PM          54560 hprof-conv.exe
-a----         5/17/2024  12:24 PM         242128 libwinpthread-1.dll
-a----         5/17/2024  12:24 PM         477472 make_f2fs.exe
-a----         5/17/2024  12:24 PM         477472 make_f2fs_casefold.exe
-a----         5/17/2024  12:24 PM           1157 mke2fs.conf
-a----         5/17/2024  12:24 PM         754464 mke2fs.exe
-a----         5/17/2024  12:24 PM        1110529 NOTICE.txt
-a----         5/17/2024  12:24 PM          18404 package.xml
-a----         5/17/2024  12:24 PM             38 source.properties
-a----         5/17/2024  12:24 PM        2838304 sqlite3.exe

We also recommend adding the location of adb.exe to your PATH environment variable, so it can be run from any directory when working with PowerShell/Command Prompt. For instructions on how to do this, check out this article.

To start the ADB server on the host computer, we can type the following command.

Android Debug Bridge

root@htb[/htb]$ adb start-server

Once the ADB is installed on our host computer and the emulator is started, we can start issuing ADB commands. As mentioned earlier, starting the ADB server can also be done by directly executing commands using the ADB client. Below are some useful ADB commands.

Command Description
adb help List all commands.
adb kill-server Kills the adb server.
adb devices Lists connected devices.
adb root Restarts adbd with root permissions.
adb install <apk> Install app.
adb push <local> <remote> Copy file/dir to device.
adb pull <remote> <local> Copy file/dir from device.
adb logcat [options] [filter] [filter] View device log.

Let's list the connected devices using the following command.

Android Debug Bridge

root@htb[/htb]$ adb devices

List of devices attached
emulator-5554    device

The above command reveals one attached device using port 5554. Apart from providing interactive shell access to the emulated device, ADB can also be used non-interactively to execute commands from the host machine directly to the emulated device. The following adb command executes the whoami command inside the emulated device and prints the result to the terminal.

Android Debug Bridge

root@htb[/htb]$ adb shell whoami

shell

Let's issue the following command to get a shell in the emulated device.

Android Debug Bridge

root@htb[/htb]$ adb shell

emu64x:/ $

We can now start executing Linux commands inside the emulator.

Android Shell

Android Debug Bridge

emu64x:/sdcard $ ls -l /sdcard/

total 104
drwxrws--- 2 u0_a146  media_rw 4096 2023-04-28 17:30 Alarms
drwxrws--x 5 media_rw media_rw 4096 2023-04-28 17:29 Android
drwxrws--- 2 u0_a146  media_rw 4096 2023-04-28 17:30 Audiobooks
drwxrws--- 2 u0_a146  media_rw 4096 2023-04-28 17:30 DCIM
drwxrws--- 2 u0_a146  media_rw 4096 2023-04-28 17:30 Documents
drwxrws--- 2 u0_a146  media_rw 4096 2023-04-28 17:30 Download
drwxrws--- 3 u0_a146  media_rw 4096 2023-04-28 17:30 Movies
drwxrws--- 3 u0_a146  media_rw 4096 2023-04-28 17:30 Music
drwxrws--- 2 u0_a146  media_rw 4096 2023-04-28 17:30 Notifications
drwxrws--- 3 u0_a146  media_rw 4096 2023-04-28 17:30 Pictures
drwxrws--- 2 u0_a146  media_rw 4096 2023-04-28 17:30 Podcasts
drwxrws--- 2 u0_a146  media_rw 4096 2023-04-28 17:30 Recordings
drwxrws--- 2 u0_a146  media_rw 4096 2023-04-28 17:30 Ringtones

ADB Root

ADB can also provide the user with elevated privileges. The adb root command restarts the ADB daemon (adbd) on the Android emulator with root privileges. It gives elevated permissions to the user that allows for a wider range of command execution, as well as access to system files and directories that are typically restricted to regular users. Root access allows us to perform more in-depth debugging and testing during the application penetration testing. This said, running ADB as root is typically only available on emulators, as most production devices come with locked bootloaders and cannot be rooted without voiding the warranty. Also, as mentioned earlier in the Android Emulators section, only system images labeled with Google APIs will provide this feature. System images labeled with Google Play are signed with a release key, and elevated privileges (root) are not supported.

The following command will restart the adbd with root privillages.

Android Debug Bridge

root@htb[/htb]$ adb root

restarting adbd as root

Once it's restarted, we can execute the following command to confirm that we can run commands as the user root.

Android Debug Bridge

root@htb[/htb]$ adb shell whoami

root

Installing Apps and Transferring Files

Android Debug Bridge also allows us to install applications. Let's try to install the application we created in the section Native Apps using the following command.

Android Debug Bridge

root@htb[/htb]$ adb install myapp.apk 

Performing Streamed Install
Success

Once installed, we can locate the app on the device and tap on it to start. Another useful command of ADB is the push and pull commands. Using these commands, we can transfer files from the host computer to the device and vice versa. To push files from the host to the device, we can execute the following command.

Android Debug Bridge

root@htb[/htb]$ adb push ./myapp.apk /sdcard/Download/

./myapp.apk: 1 file pushed, 0 skipped. 14.9 MB/s (4527105 bytes in 0.291s)

We can verify by listing the content of the directory /sdcard/Download/ in the device.

Android Debug Bridge

root@htb[/htb]$ adb shell ls -l /sdcard/Download/

total 4428
-rw-rw---- 1 u0_a146 media_rw 4527105 2023-05-03 01:44 myapp.apk

Similarly, we pull files from the device to the host by executing the following command.

Android Debug Bridge

root@htb[/htb]$ adb pull /sdcard/Download/myapp.apk .

/sdcard/Download/myapp.apk: 1 file pulled, 0 skipped. 78.9 MB/s (4527105 bytes in 0.055s)

Setting Up a Real Device

Setting up ADB with a real Android device requires some extra steps on the device's side. After installing ADB on the host machine, we also have to enable USB Debugging on our Android device. To do this, we first have to go to Settings -> About phone or About device or About emulated device, find the Build number, and tap on it seven times. This will display the message You are now a developer!.

About emulated device screen showing Android version 13, IP address, Wi-Fi MAC address, device Wi-Fi MAC address, Bluetooth address, up time, and build number.

The Developer Options menu can be found under Settings -> Developer Options or Settings -> System -> Developer Options, depending on the Android version. After navigating to the path we mentioned earlier, the next step is to enable USB Debugging and confirm any prompts that appear.

Developer options screen with "Use developer options" enabled. USB debugging is on, wireless debugging, disable ADB authorization timeout, bug report shortcut, and enable verbose vendor logging are off.

Once enabled, we can connect the device to the computer using a USB cable. When prompted on the device, choose File transfer or MTP mode. We can confirm that the device is connected by executing the following command.

Android Debug Bridge

root@htb[/htb]$ adb devices

List of devices attached
1234567890ABCDEF    device

The device's serial number should be included in the output, indicating that ADB is successfully connected to your device. Finally, we can start executing adb commands from our host computer to the device.


Application Penetration Testing - Methodology and Tools


Mobile applications and services are vital for both our personal and professional life, making them prime targets for malicious actors seeking sensitive data. Modern Android applications are widely used in commerce, healthcare, banking, education, and more. Beyond storing sensitive information, these applications often contain security vulnerabilities that could be exploited. Penetration testers and developers play a crucial role in identifying and mitigating these vulnerabilities, reducing risks such as fraud, malware infections, and data breaches. For companies launching new applications, ensuring security and compliance is essential to avoiding legal issues and protecting user data.

Mobile application penetration testing is the process of assessing an app's security by simulating real-world attack scenarios. This methodology helps identify vulnerabilities and ensures that applications remain resilient against threats. The following steps outline a structured, high-level approach that Android penetration testers should follow for a comprehensive security evaluation.

1. Planning and Environment Setup

Objectives
Define the scope and objectives of the penetration test.
Obtain necessary permissions from the app owner or organization.
Set up the testing environment, including the necessary tools, operating systems, and emulators.

2. Enumeration and Information Gathering

Objectives
Gather information about the app and its architecture.
Understand the functionality of the application.
Enumerate any data structure the app uses that is stored in the local storage.

3. Static Analysis

Objectives
Understand the manifest file of the application. Review the app's components, permissions, and configurations.
Decompile and analyze the application's source code to understand the flow and find potential vulnerabilities.
Examine native or third-party libraries, frameworks, and dependencies the app uses.

4. Dynamic Analysis

Objectives
Monitor the application's behavior during runtime. Enumerate the local storage for files and data structures created after performing various functionalities.
Perform dynamic instrumentation to read the memory of the application during runtime.
Intercept network traffic and test the app for vulnerabilities such as authentication bypass and insecure data transmission.
Check for Server-Side attacks by analyzing API calls and looking for vulnerabilities such as injection and XSS attacks.

5. Documenting and Reporting

Objectives
Document the identified vulnerabilities, exploitation methods, impact, and potential risks.
Communicate the findings to the app owner or organization.
Provide recommendations for fixing vulnerabilities and mitigating security risks.

Suggested Tools

The following list presents some of the most widely used tools for mobile app penetration testing , focusing on reverse engineering, dynamic analysis, code instrumentation, network traffic analysis, and digital forensics. Familiarity with these tools is essential for anyone practicing their Android application penetration testing skills.

ADB

A versatile command-line tool that allows communication with Android devices. This tool will let us enumerate the local storage of the device and the installation directory of the application, see the app logs while it is running, and install and run other apps and tools necessary for the steps of the dynamic analysis and instrumentation.

JADX

This tool allows us to reverse engineer an application and view its source code through a graphical interface. It provides the user with a Java-like pseudocode that is close enough to the actual code. It also decodes the encoded resources that are packed in the APK file, making the file configurations and the other assets readable to humans. This tool is an essential part of the process since the testers can understand how the app works and what technologies are used.

APKTool

This tool also allows reverse engineering Android applications. It will decompile the source code and decode the resources of the APK file. APKTool also enables us to edit the source code and configuration files, recompile the code, and build the APK file again. This is called application patching. The decompiled code provided to the user is a symbolic language called Smali. This language is not easy to read, but if combined with a tool that offers Java-like pseudocode like JADX, pentesters can efficiently understand and change the flow of the application to their benefit.

Ghidra

An open-source software reverse engineering (SRE) framework developed by the National Security Agency (NSA). It is used for disassembling, decompiling, and analyzing binaries. In Android application penetration testing, Ghidra is often used to analyze the native C++ libraries loaded to the application.

Burp Suite

A tool used for web application penetration testing. While we often see it used for testing websites, it can also be used for testing any web-based application communicating with a server, including Android. In the Android application, penetration testing will be mainly used to intercept and analyze HTTP/HTTPS requests.

Frida

A dynamic instrumentation toolkit used by developers, reverse engineers, and security researchers. It enables us to inject snippets of JavaScript or native code into the running processes of Android applications, allowing us to analyze and manipulate them during runtime.

Autopsy

An open-source digital forensics platform that can be used for various tasks, including Android forensics. Autopsy will help us investigate disk images of Android devices and let us search for files, databases, calls, messages, and logs, using the provided GUI.


Automated Tools

Automated Android penetration testing tools are suitable for scanning common vulnerabilities. They offer a faster and cheaper solution than manual tools and processes, which provide more depth and vulnerability insights by combining human intelligence with automated tools.

MobSF

An automated security testing framework for Android, iOS, and Windows platforms that performs static, dynamic, and malware analysis on mobile applications.

Drozer

A comprehensive security and attack framework that allows us to assess the security of Android applications. It simulates various attack vectors and provides multiple tools to analyze, exploit, and debug Android applications.

Qark

A static code analysis tool that automates the discovery of security vulnerabilities in Android applications. It can also create Proof-of-Concept deployable APKs and ADB commands to exploit the vulnerabilities it finds.

Objection

A runtime mobile exploration toolkit that uses Frida to provide an easy environment for assessing Android and iOS application security. It automates several common tasks, such as bypassing SSL pinning, and users won't be needed to create custom scripts whenever they need to exploit common vulnerabilities.

Medusa

An extensible and modularized framework that automates processes and techniques practiced during the dynamic analysis of Android Applications. Medusa is based on Frida and can analyze and enumerate an app, attack common entry points, and automate processes like application patching, MITM attack, and more. Medusa can add and remove hooks for Java or Native methods and has more than 90 modules that can be combined.

Androbugs

Androbugs is an Android vulnerability analysis framework that automatically scans Android applications and checks for security issues and vulnerabilities.

It is crucial to remember that combining both manual and automated techniques usually results in a better evaluation. Recapping this module, we went through the most critical fundamental concepts necessary for understanding the methodologies that will follow in subsequent modules. We analyzed the structure of the Android OS and the architecture of applications, discussed the different types of apps, and examined examples of applications built with various frameworks. We studied their components and IPCs, and learned how to set up a testing environment and interact with emulators.

Following the Penetration Testing methodology discussed in this section, we are now ready to prepare for the next module, which regards Android Application Static Analysis. In this upcoming module, we will reverse engineer apps and study the source code to identify security issues and exploit vulnerabilities.