- The big picture
- What needs protecting
- Develop a threat model for your application
- The four layers of mobile application protection
- Decompilation and modification
- Dynamic analysis and tampering
- Emulators and Virtualization Apps
- Network communications interception
- Mobile app fraud
Threats / 02 Dynamic analysis and tampering
Dynamic Analysis & Tampering in Practice
What is dynamic binary instrumentation?
Dynamic Binary Instrumentation (DBI) is a technique used to gain understanding of the internals and behavior of a mobile app when it’s actively running. It works by injecting a portion of code - known as bootstrapper - into the target’s app memory space to gain effective control of the app and execute some instrumentation code.
There are many dynamic binary instrumentation frameworks or toolkits available. Here we will focus mainly on Frida, one of the most popular DBI toolkits for Android & iOS, used extensively during reverse engineering activities. It is the de facto choice for mobile reverse engineers and security researchers alike.
Frida offers two main operating modes: server mode and gadget mode.
In server mode, Frida will run as a standalone process inside the target mobile platform. Attackers set up Frida by downloading Frida’s server binary for the respective mobile platform and architecture, pushing it to the device using tools like Android Debug Bridge (ADB) or SSH access for Android and iOS respectively. Then they execute the relevant command to boot it.
Running Frida in server mode requires elevated privileges (such as those provided by root). That’s because Frida needs this permission to be able to attach and inject itself into the target application or process.
Now, let’s take a look at how Frida works at a technical level in this server mode.
Frida begins by hijacking the main thread of the target application or process using ptrace API. Then it allocates a memory region in the same target app or process for its bootstrapper code using mmap API. This bootstrapper code is used by Frida to create a native thread using pthread API - this will be used to create a long-running socket so that we can communicate with the Frida process running on the device, loading its bridge or agent library via dlopen API and, finally, executing its entry point. The bridge or agent library - which resides in the target application’s memory space - contains the just-in-time (JIT) compiler that will do the actual code instrumentation.
In server mode, attackers can specify how they’d like to instrument an app using Frida.
The first option is spawn. With spawn, instead of hijacking the target app or process directly, Frida first hijacks the parent process of all apps (i.e. Zygote on Android, launchd on iOS) using the mechanism described above. And finally it spawns the target app based on its identifier (ID).
The second option is attach, which instructs Frida to hijack the target app directly and performs the same mechanism as described above.
Spawning the app with Frida naturally allows for earlier instrumentation. This is usually preferable for the attacker because it gives them access to any sensitive operations the app performs on launch. This includes any related to protection mechanisms, such as unpacking encrypted code and running RASP checks).
However, attaching to the process may be equally useful, depending on the adversary’s goals. Let’s say that the target app unpacks encrypted code incrementally (i.e. on demand, whenever the code needs to be made accessible to the operating system) and Frida has some issues in discovering all of them. If the attacker wants to see and instrument all of those decrypted code, they can just attach later at some point.
Gadget mode, on the other hand, involves direct injection of a Frida library or framework into the application package. It’s therefore a form of binary modification, as covered in the Decompilation and Modification page. Attackers download the Frida agent library, decompile your app, inject the library and write some code to ensure the app loads this library on runtime. They then re-compile, re-sign, and install this new version of the app on their device. When the app is launched, provided that Frida is loaded as early as possible, it will perform the same operations as in server mode - creating a native thread to open up the socket and pausing the app’s execution until it can successfully communicate with Frida through that socket.
So, what’s the difference between server mode and gadget mode from an attacker’s perspective?
The main advantage of gadget mode - i.e. of injecting the Frida agent library - is that an attacker can perform instrumentation without the need for a rooted or jailbroken device. This is especially advantageous if the attacker intends to run the modified app on other users’ devices.
On the other hand, operating in server mode - specifically with spawn - allows for earlier and deeper instrumentation. Because Frida has already hijacked the parent process, it can instrument system functions as well as user-defined functions (or the code that you write) which will be executed early, such as in application or library constructors. And another benefit for the attacker is that there is no need to tamper with the application binary, which may be a more labour-intensive process.
Fortunately, from a security perspective, there are mitigations against all of these modes. We’ll explore these later on this page.
Debugging is primarily the process of identifying bugs or faults in software. Seen more generally, however, it is simply a means of analyzing the internals and behavior of a software while it’s actually running. A debugger (which in this case we can also refer to as a tracer) works by attaching itself to the process of the target app (the tracee). It pauses its execution at a certain point (breakpoint) so that the user can inspect the app’s state and the current values of variables.
Let’s dive a little deeper on how debuggers work technically. We’ll focus on LLDB as an example because it’s used for apps on both mobile platforms, Android and iOS.
ptrace API is useful to a debugger in much the same way as it is to a DBI tool. And LLDB is no exception. But on both Android and iOS, attaching to the process of the target app (the tracee) using this API would require either root access or the tracee being flagged as debuggable.
Dynamic binary instrumentation vs debugging
The key difference between dynamic binary instrumentation and debugging is how they take control of the app and how they perform their work. Dynamic binary instrumentation (DBI) toolkits will seem to be a natural part of the target application or process because they reside in that app’s memory space. So, DBI frameworks essentially operate from within the target application itself.
Debuggers are mainly external processes which simply attach to the target app or process and then transmit signals from the outside to control the target app’s execution.
This makes dynamic binary instrumentation frameworks stealthier and harder to detect than debuggers. Android, for example, offers a security feature called Security-Enhanced Linux which is basically a sandboxing or access control system for every app or process in the device. Debuggers often require this feature to be disabled, so this is a clear indicator of a debugging attempt.
DBI frameworks such as Frida are also able to perform early instrumentation due to its app spawning functionality - something that debuggers usually don’t have. This is why DBI toolkits are generally more useful to security researchers and bad actors than debuggers are. However, debuggers still have their own advantages. For example, debuggers can easily deal with multi-thread apps and are often shipped as part of the mobile app development SDK, which means they are particularly convenient to use and keep up-to-date.
How attackers use dynamic analysis & tampering
One of the most important aspects of dynamic tampering is being able to inspect and modify the behavior of an application. This can be achieved via function hooking or tracing. By using a dynamic binary instrumentation toolkit like Frida or a debugger like LLDB, bad actors can intercept any user-defined functions. That is the functions that you create in your mobile app, as well as system library functions loaded into it. Once a function is intercepted, bad actors can then modify its arguments and return values or patch its implementation in-memory.
Modify and dump memory layout
Dynamic binary instrumentation toolkits like Frida rely on a bridge or agent library that resides in the same memory space or region occupied by your mobile application. Debuggers can also attach to your app and access its memory layout through the relevant system API like ptrace. This means that DBI frameworks and debuggers have the same comprehensive access to every bit of data in your application’s memory layout. Bad actors can therefore use DBI toolkits like Frida or debuggers to explore your app’s memory. By doing so they can locate the address of a particular function to investigate, or they can simply dump the entire memory layout for further analysis later.
Mobile application binaries are often hardened (through obfuscation and encryption), making life more difficult for reverse engineers and security researchers. This makes reverse engineering by means of decompilation considerably slower and in some cases impossible. Dynamic instrumentation toolkits like Frida offer a more efficient alternative. Frida provides functionality like Instruction API which can be used to dynamically disassemble the code of a function or a particular memory address.
What can attackers achieve by dynamic analysis & tampering?
Internal Data and Intellectual Property (IP) Theft
Access to sensitive information
Bad actors may be able to extract sensitive information from your app at runtime thanks to dynamic binary instrumentation toolkits like Frida or debuggers like LLDB. For example, as described above, both Frida and debuggers are able to access your memory layout. Imagine you forgot to remove sensitive information of your app such as API keys or access tokens to your servers and cryptographic keys from the memory. The attacker could then use Frida or debugger to spot and retrieve that information from the memory for further attacks. For example, using your own API keys or access tokens as a way to compromise your backend.
Reverse engineering and intellectual property (IP) theft
Mobile applications often contain proprietary algorithms, data formats, or protocols to provide functionality to their users. Unsurprisingly, adversaries may try to reverse-engineer a competitor’s mobile app to understand its inner workings and extract any useful information from it that might give them an edge. Attackers can use Frida to discover and hook all the user-defined functions and decompile the code on the fly. From there, they can further locate which function or code block contains proprietary information.
Abuse of Restricted Functionalities
Cheating and spoofing
Tools like Frida and debuggers (e.g. GDB/LLDB) are also ideal for gamers who want to cheat the system and gain an unfair advantage. They can use either or both to explore your mobile game’s memory layout to locate the address of a function or a game variable they’re targeting. This might be a player’s health, power, movement, or in-game credits. Once they’ve found it, they can easily modify its value to their heart’s content.
Prevention & Mitigation
Ensure that your app is not debuggable
As mentioned above, for Android the system will allow a debugger to attach to an app’s process if the app has the android:debuggable="true" attribute in its AndroidManifest.xml. iOS apps must have the <key>get-task-allow</key> set to <true/> in their Entitlements. Both of these options should therefore be set to false. Given the security risks of allowing an app to be debugged, both Google Play and the App Store enforce this as a requirement before uploading an app for distribution. Nevertheless, it’s important to be aware of the importance of these flags, especially for apps that are distributed directly to users.
Code and Resource Hardening
Encryption may be helpful against some forms of dynamic analysis and tampering, but code needs to be decrypted before it can run. This is one of the reasons why it’s crucial to combine protections against both static and dynamic forms of attack. Still, obfuscation and code virtualization can make dynamic reverse engineering considerably more difficult. And as we explain elsewhere in this guide, encryption can be an effective way to guarantee code and app integrity, therefore preventing binary modification exploits based on using Frida in Gadget mode.
Secure Runtime Environment
Detect dynamic binary instrumentation toolkits in the memory mappings
As explained above, dynamic instrumentation toolkits like Frida rely on a bridge or agent library residing in your mobile application’s memory space or mappings. It’s therefore possible to detect traces of dynamic instrumentation toolkits, and to take action. This is a crucial feature of Runtime Application Self-Protection (RASP) mechanisms.
Detect if a debugger is attempting to attach
Since debuggers are basically external processes that seek to trace your app from the outside, they can be detected. Indeed, Android and iOS offer APIs that assist with detecting debuggers. And RASP mechanisms can offer reinforced checks that - in combination with other checks - are more difficult for an attacker to bypass.
Implement device attestation: root, jailbreak, and custom firmware detection
To be able to use Frida in server mode, or to attach a debugger to an app that doesn’t explicitly allow debugging, attackers will need escalated privileges: a rooted or jailbroken device. So, checking whether the device on which your app is running is rooted or not can help prevent instrumentation or debugging.
Application Integrity and Anti-Tampering
Prevent binary modification exploits
As mentioned above, Frida also offers Gadget mode where attackers modify the target app by embedding its bridge or agent library. In that case they can perform dynamic instrumentation on non-rooted or jailbroken devices. This is another reason why it’s so important that your app is equipped with a solution that makes binary modification impossible or pointless.