Ensuring the security and reliability of software products is one of the biggest challenges for modern businesses. As a product grows, each new feature opens doors for potential security vulnerabilities and performance bottlenecks.
Dynamic analysis and reverse engineering are two approaches that allow developers to explore the inner workings of their applications while theyโre running. With these insights, developers can identify vulnerabilities and discover potential security issues.
In this article, youโll learn how to analyze your app dynamically and uncover vulnerabilities using Frida. Weโll show you real-world examples of the Frida tool in action based on our own project experience. This article will be useful for security researchers, CTOs, and CSOs who want to ensure the protection of their products.
Contents:
What is dynamic analysis and why is it important?
Dynamic analysis and reverse engineering are two crucial cybersecurity methods that allow businesses to enhance product security.
Dynamic analysis allows cybersecurity specialists to evaluate the behavior of software while itโs running. This is effective for:
- Vulnerability detection โ uncover potential vulnerabilities and plan cybersecurity enhancements to protect your product from data breaches
- Real-world simulation โ understand how your product will behave in real-world conditions, allowing you to examine your product from the usersโ perspective and see how it handles sensitive data and crucial operations
- Performance optimization โ beyond security, dynamic analysis helps optimize app performance, which can help you improve your productโs user experience and optimize infrastructure costs
- Compliance assurance โ identify potential breaches before they lead to compliance violations and penalties
Dynamic analysis is used in reverse engineering, which allows developers to dissect software and understand its inner workings even without source code or documentation.
At Apriorit, we specialize in reverse engineering and use it to help businesses assess and improve the security of their software. This includes protecting it from breaches, malware attacks, and data leaks that can cause substantial reputational and monetary damage.
There are many tools that allow businesses to conduct dynamic analysis and reverse engineering. In our reversing projects, we often use Frida.
Want to bring your productโs security to the next level?
Make your software vulnerability-free with the help of Aprioritโs top reverse engineers and cybersecurity professionals.
What is Frida?
Frida is a dynamic open-source instrumentation toolkit that allows developers and reverse engineers to inject JavaScript code into running applications. Injection enables developers to trace function calls, modify function behavior, and intercept data in real time, making it an invaluable tool for dynamic analysis.
Frida provides a comprehensive set of APIs that can be used to interact with running applications.
Fridaโs architecture is based on a clientโserver model. The Frida server runs on the target device or computer, while the client is used to interact with the server and inject JavaScript code into running applications.
Letโs look at a few examples of what dynamic app analysis tasks you can perform using Frida. Weโll start with desktop applications.
Dynamic analysis of a desktop application with Frida
Frida supports Windows, macOS, and Linux, making it a reliable choice for analyzing desktop applications regardless of the operating system they run on. Letโs look at some of the tasks you can perform with Frida.
Before we can perform any dynamic analysis or manipulation actions, we need to inject Frida into our desktop app. This is a relatively straightforward process:
- Install Frida on the target system.
- Use the command
frida-server -l 0.0.0.0.
This will start the Frida server and make it accessible to other devices on the network. - Attach the Frida client to the target process and inject the Frida script.
Hooking MessageBox in a Windows desktop application
To demonstrate how to hook functions with Frida, letโs consider the example of hooking the MessageBox function in a Windows desktop application. This function is used to display a message box to the user, making it an excellent target for dynamic analysis.
To hook the MessageBox function using Frida, we first need to write a Frida script. The script will attach to the target process, find the address of the MessageBox function, and hook the function using the Interceptor. Hereโs our custom script from one of our projects:
function hook_messagebox() {
var user32 = Module.find("user32.dll");
var MessageBoxA = user32.findExportByName("MessageBoxA");
Interceptor.attach(MessageBoxA, {
onEnter: function(args) {
console.log("[*] MessageBox called.");
}
});
}
setTimeout(hook_messagebox, 0);
In this script, we use Module.findBaseAddress to find the base address of the user32.dll library, which contains the MessageBox function. We then get the address of the MessageBox function using the findExportByName method (for private methods, we can use add with offset). Finally, we use Interceptor.attach to hook the MessageBox function, logging a message to the console when the function is called.
To run this script, save it to a file (such as hook_messagebox.js) and run the Frida client with the following command:
frida -f target.exe -l hook_messagebox.js
This will attach Frida to the target process and inject the script. When the target process calls the MessageBox function, Frida will intercept the function and log a message to the console.
Read also
9 Best Reverse Engineering Tools for 2023 [Updated]
Enhance software security, maintain legacy code, and improve your product compatibility with third-party components using only the best reversing tools. Discover the most efficient programs along with practical examples of using them.
Modifying NSURLRequest in a macOS desktop application
Another powerful capability of Frida in desktop applications is modifying data in real time. For example, letโs consider the case of a macOS desktop application that uses NSURLRequest to make HTTP requests. We can use Frida to modify the HTTP request before it is sent, enabling us to bypass security measures or modify the applicationโs behavior.
To modify the NSURLRequest in a macOS desktop application using Frida, we first need to write a Frida script. The script will attach to the target process, find the address of the NSURLRequest object, and modify its properties using JavaScript. Hereโs an example:
function modify_nsurlrequest() {
// Find the address of the NSURLRequest object
var requestPtr = null;
var objc_msgSend = new NativeFunction(Module.findExportByName("libobjc.A.dylib", "objc_msgSend"), "pointer", ["pointer", "pointer"]);
Interceptor.attach(objc_msgSend, {
onEnter: function (args) {
var sel = ObjC.selectorAsString(args[1]);
if (sel === "initWithURL:cachePolicy:timeoutInterval:") {
requestPtr = args[0];
}
},
onLeave: function (retval) {
if (requestPtr !== null) {
var request = new ObjC.Object(requestPtr);
request.setValue_forHTTPHeaderField_("my-custom-header", "X-Custom-Header");
requestPtr = null;
}
}
});
}
// Attach to the target process and wait for it to start
Process.enumerateApplications({
onMatch: function (info) {
if (info.name === "TargetApp") {
console.log("Attaching to " + info.name + " (" + info.pid + ")");
// Attach to the process and wait for its main module to be loaded
Process.attach(info.pid, {
onModuleLoaded: function (module) {
if (module.name === "TargetApp") {
// Wait for the script runtime to be ready
setTimeout(modify_nsurlrequest, 0);
}
}
});
}
},
onComplete: function () {
console.log("Failed to find target process");
}
});
This script uses the objc_msgSend function to intercept calls to the initWithURL:cachePolicy:timeoutInterval: method of the NSURLRequest class. When this method is called, we save the address of the initialized NSURLRequest object. When the method returns, we create an ObjC.Object instance representing the NSURLRequest object and modify its properties as desired.
Next, we use Process.enumerateApplications to find the target process by name. When the process is found, we attach to it using Process.attach and wait for its main module to be loaded. When the main module is loaded, we call the setTimeout function to schedule the call for the modify_nsurlrequest function during the next event loop iteration, giving the script runtime a chance to fully initialize.
Once the modify_nsurlrequest function is called, it will intercept calls to the NSURLRequest initializer method and modify the properties of any initialized NSURLRequest objects as desired.
These are just a few examples of dynamic analysis tasks you can perform with Frida in desktop applications. Letโs now take a look at Fridaโs uses for mobile apps.
Dynamic analysis of a mobile app with Frida
Dynamic analysis is an effective way to identify vulnerabilities in mobile applications because it allows us to monitor application behavior in real time.
Frida can be used to perform dynamic analysis of both Android and iOS applications. With Frida, we can hook into a running application and monitor its behavior, including function calls, network traffic, and memory use.
To use Frida for dynamic analysis of an application, we first need to install the application on a physical or virtual device. Then, we can attach Frida to the running process and start monitoring the applicationโs behavior.
Frida can be used with unrooted, rooted, and jailbroken devices. Installation is described in detail in the official Frida documentation. Generally, it involves running the run frida-server command on the target device and connecting to it using USB in debugging mode.
Letโs take a look at some examples of what you can do with Frida to run dynamic analysis on your mobile app.
Bypassing root detection
Many developers implement root detection to prevent users from running their applications on rooted devices. However, this can be bypassed using dynamic analysis with Frida.
Letโs look at an example where we use Frida to bypass root detection in an Android application. In this case, weโll assume the application is using the SafetyNet API to check if the device is rooted.
To bypass root detection, weโll use Frida to intercept the call to the SafetyNet API and modify the return value to indicate that the device is not rooted. Hereโs the JavaScript code to achieve this:
Interceptor.attach(Module.findExportByName("libfoo.so", "safetynet_check"), {
onLeave: function(retval) {
retval.replace(0x0);
}
});
However, modern mobile apps and systems often employ multiple layers of security measures to detect and prevent various forms of tampering and unauthorized access. As a result, bypassing root detection in contemporary apps and systems may require more extensive and sophisticated hooking techniques.
You can find these techniques and implementation examples on CodeShare.
Related project
Improving a SaaS Cybersecurity Platform with Competitive Features and Quality Maintenance
Find out how bug fixes and new functionality helped our client make their platform more stable and competitive. Discover the success story of how Apriorit helped a SaaS cybersecurity provider receive appreciation from end users for quick fixes of reported issues and overall UI/UX improvements.
Tampering with API Calls
Mobile applications often use APIs to communicate with backend servers. By tampering with API calls, an attacker can modify application behavior or steal sensitive data.
Weโll use this technique ethically for Frida dynamic instrumentation. Tampering with API calls can help you assess your appโs overall security, inspect traffic, and monitor behavior.
Frida allows tampering with API calls in iOS applications. In this case, weโll assume the application is using the URLSession API to make a request to a backend server.
To tamper with the API call, weโll use Frida to intercept the call to the URLSession API and modify a request before it is sent to the server. Hereโs the code to achieve this:
Interceptor.attach(ObjC.classes.NSURLSession["- dataTaskWithRequest:completionHandler:"].implementation, {
onEnter: function(args) {
var request = new ObjC.Object(args[2]);
var mutableRequest = request.mutableCopy();
mutableRequest.addValue("YourModifiedHeaderValue", forHTTPHeaderField: "YourModifiedHeaderField");
args[2] = mutableRequest;
}
});
By tampering with API calls, you can assess the applicationโs security vulnerabilities. At Apriorit, we use this method to simulate various attack scenarios and identify potential weaknesses in the way an application handles data, communicates with servers, or responds to unexpected inputs.
Reverse engineering with Frida
You can use Frida for reverse engineering. It provides a dynamic analysis environment that helps you examine how an application behaves while itโs running. Frida allows us to hook into an applicationโs execution flow, monitor and manipulate function calls and arguments, and intercept data being sent or received by the application.
At Apriorit, we use Frida for a variety of reverse engineering tasks, such as:
- Extracting encryption keys
- Analyzing network traffic
- Tracing system calls
- Identifying malware behavior
- Researching proprietary protocols
- Analyzing binary code
There are other activities like malware instrumentation with Frida, that allow cybersecurity specialists to reverse engineer malware.
In this guide, we demonstrate the first three tasks using an Android application as an example. Frida is particularly well-suited for the Android platform, while other tools might be more suitable for reverse engineering tasks on desktop and iOS platforms.
Extracting encryption keys
The Apriorit team of security experts uses Frida to extract encryption keys used by an application to secure sensitive data. By hooking into the applicationโs encryption functions, we can intercept the keys being generated or used by the application and log them for further analysis.
Hereโs a sample of a script that uses Frida to extract an encryption key from an application:
Java.perform(function () {
var key = null;
var keygen = Java.use('javax.crypto.KeyGenerator');
var keygen_init = keygen.init.overload('int');
keygen_init.implementation = function (keysize) {
console.log('[*] Key size: ' + keysize);
keygen_init.call(this, keysize);
key = this.generateKey();
};
var cipher = Java.use('javax.crypto.Cipher');
var cipher_init = cipher.init.overload('int', 'java.security.Key');
cipher_init.implementation = function (opmode, secretKey) {
console.log('[*] Secret key algorithm: ' + secretKey.getAlgorithm());
console.log('[*] Secret key format: ' + secretKey.getFormat());
console.log('[*] Secret key encoded: ' + secretKey.getEncoded());
cipher_init.call(this, opmode, secretKey);
};
});
This script hooks into the javax.crypto.KeyGenerator and javax.crypto.Cipher classes and intercepts their function calls. When the KeyGenerator initializes a new key, the script logs the key size and generates the key. When the Cipher is initialized with a secret key, the script logs information about the key.
Read also
The Evolution of Reverse Engineering: From Manual Reconstruction to Automated Disassembling
Handle security tasks of any complexity efficiently and quickly by fully automating reverse engineering activities. Discover the key techniques, tools, and methods recommended by our cybersecurity researchers.
Analyzing network traffic
Frida can be used to analyze an applicationโs network traffic by hooking into network functions and intercepting data being sent or received by the application. This can be useful for identifying sensitive data being transmitted over the network, as well as for understanding how the application communicates with external services.
Hereโs an example script that uses Frida to intercept network traffic:
Java.perform(function() {
var URL = Java.use("java.net.URL");
var HttpURLConnection = Java.use("java.net.HttpURLConnection");
var OutputStreamWriter = Java.use("java.io.OutputStreamWriter");
var BufferedReader = Java.use("java.io.BufferedReader");
// Intercept the request
HttpURLConnection.getOutputStream.implementation = function() {
var outputStream = this.getOutputStream();
var requestMethod = this.getRequestMethod();
var url = this.getURL().toString();
// Print out the request method and URL
console.log("[+] Intercepted " + requestMethod + " request to " + url);
// Read the request body
var request = "";
// Get the InputStream object
var inputStream = this.getInputStream();
// Create a new InputStreamReader object
var inputStreamReader = InputStreamReader.$new.overload('java.io.InputStream', 'java.lang.String')(inputStream, 'UTF-8');
// Create a new BufferedReader object
var BufferedReader_instance = BufferedReader.$new.overload('java.io.Reader')(inputStreamReader);
var line = "";
while ((line = BufferedReader_instance.readLine()) != null) {
request += line;
}
// Print out the request body
console.log("[+] Request body: " + request);
// Modify the request
if (requestMethod == "POST") {
var modifiedRequest = "param1=value1¶m2=value2";
console.log("[+] Modifying request body to: " + modifiedRequest);
// Get the OutputStreamWriter constructor
var OutputStreamWriter = Java.use('java.io.OutputStreamWriter');
// Get the Charset and StandardCharsets classes
var Charset = Java.use('java.nio.charset.Charset');
var StandardCharsets = Java.use('java.nio.charset.StandardCharsets');
// Get the OutputStream object
var outputStream = this.getOutputStream();
// Get the UTF-8 Charset object
var utf8Charset = StandardCharsets.UTF_8;
// Create a new OutputStreamWriter object
var outputStreamWriter = OutputStreamWriter.$new.overload('java.io.OutputStream', 'java.nio.charset.Charset')(outputStream, utf8Charset);
outputStreamWriter.write(modifiedRequest);
outputStreamWriter.flush();
outputStreamWriter.close();
}
return outputStream;
};
// Intercept the response
HttpURLConnection.getInputStream.implementation = function() {
var inputStream = this.getInputStream();
// Read the response body
var response = "";
// Get the InputStream object
var inputStream = this.getInputStream();
// Create a new InputStreamReader object
var inputStreamReader = InputStreamReader.$new.overload('java.io.InputStream', 'java.lang.String')(inputStream, 'UTF-8');
// Create a new BufferedReader object
var BufferedReader_instance = BufferedReader.$new.overload('java.io.Reader')(inputStreamReader);
var line = "";
while ((line = BufferedReader_instance.readLine()) != null) {
response += line;
}
// Print out the response body
console.log("[+] Response body: " + response);
return inputStream;
};
});
This script intercepts HTTP requests and application responses and displays information about them, including:
- Request method
- URL
- Request body
- Response body
It also demonstrates how to use Frida to modify the request body before it is sent to the server. By intercepting network traffic in this way, security researchers can analyze how an application communicates with its backend servers, identify vulnerabilities and potential attack vectors, and develop exploits to take advantage of them.
When combined with SSL pinning bypass mechanisms, Frida can also allow developers to manipulate traffic in addition to intercepting it.
Tracing system calls
Identifying malware behavior is an essential task in security research. Developers can use Frida for this task.
One approach to identifying malware behavior is to use Frida to trace system calls made by the application. System calls are the mechanisms by which applications interact with the operating system. By tracing these calls, security specialists or researchers can gain insights into the applicationโs behavior and identify potential vulnerabilities.
Another approach is to use Frida to trace the applicationโs interactions with the file system and other resources. By monitoring file system activity, researchers can identify any suspicious files or directories that an application may create or access.
Hereโs an example of the script we created to trace system calls made by an application using Frida:
function WhoCalledMe(instance, amount) {
var Thread = Java.use('java.lang.Thread')
var threadinstance = Thread.$new()
var stack = threadinstance.currentThread().getStackTrace()
var stackClassNames = stack.map(function(x){return x.getClassName()})
var index = stackClassNames.indexOf(instance.$className)
if (index === -1) {
console.log('failed to find calling class in the stacktrace: ' + stack + '\n class: ' + instance.$className)
return
}
return stackClassNames.slice(index + 1, index + 1 + amount)
}
This script creates a new thread using java.lang.Thread and prints the number (based on the amount value) of class names from the call stack.
Depending on your needs, you can customize this script for tasks related to method call tracing, debugging, or profiling in a Java application.
Conclusion
Dynamic analysis plays a crucial role in identifying vulnerabilities in applications, as it allows you to look into your softwareโs inner workings.
In this article, weโve discussed the powerful dynamic analysis tool Frida and its various use cases in security research and reverse engineering of desktop and mobile applications.
If youโre looking for ways to protect your web, desktop, or mobile application from security threats, our cybersecurity team and reverse engineering experts will be happy to assist you in creating a bulletproof security strategy and choosing tools that will best suit your needs.
Ready to deliver a protected and competitive solution?
Detect and fix all security issues by leveraging the power of dynamic analysis and reverse engineering services from Apriorit.