Introduction to Frida for Android Penetration Testing
Frida is a dynamic instrumentation toolkit that allows developers, security researchers, and reverse engineers to inject their own scripts into running processes. It’s an indispensable tool for Android application penetration testing, enabling real-time modification of application behavior without recompiling the source code. This guide will walk you through setting up a Frida lab on an Android emulator and demonstrate how to bypass common security measures like root detection and SSL pinning.
What is Frida?
At its core, Frida works by injecting a JavaScript engine (powered by Google’s V8) into a target process. This allows you to write JavaScript code that interacts directly with the application’s memory, methods, and libraries. You can hook into functions, modify arguments, observe return values, and even call private methods – all while the application is running.
Why Frida for Android?
For Android app security analysis, Frida offers unparalleled flexibility:
- Dynamic Analysis: Inspect and modify app behavior at runtime.
- No Recompilation: Test modifications without needing the app’s source code or recompiling APKs.
- Versatility: Supports various architectures (ARM, ARM64, x86, x64) and platforms (Android, iOS, Windows, macOS, Linux).
- Powerful API: A rich JavaScript API allows for complex instrumentation tasks.
Setting Up Your Android Reverse Engineering Lab
Before we dive into bypassing security features, we need a properly configured environment.
1. Choose and Configure Your Emulator
For this lab, we recommend using an Android Studio AVD (Android Virtual Device) or Genymotion. Both offer good performance and ease of ADB access. Ensure your emulator has Google APIs installed for broader app compatibility.
Android Studio AVD Setup:
- Open Android Studio and navigate to
Tools > AVD Manager. - Create a new virtual device. Choose a Pixel device running a recent Android version (e.g., Android 10 or 11). Ensure it’s an image with Google APIs.
- Start the emulator. Make sure ADB (Android Debug Bridge) is working by running
adb devicesin your terminal. You should see your emulator listed.
Genymotion Setup (Alternative):
- Install Genymotion Desktop and VirtualBox.
- Create a new virtual device. Choose a device with a recent Android version.
- Start the emulator.
- Ensure ADB is connected. Genymotion often has ADB configured by default, but you might need to specify its path in VirtualBox settings or use
adb connect <emulator_ip>.
2. Install Frida on Your Host Machine
Frida’s client tools are Python-based and easily installed via pip.
pip install frida-tools
Verify the installation:
frida --version
Deploying Frida Server to the Emulator
Frida operates with a client-server architecture. The Frida client runs on your host machine, and the Frida server runs on the target device (your emulator).
1. Download Frida Server
You need to download the correct Frida server binary for your emulator’s architecture. First, identify your emulator’s CPU architecture:
adb shell getprop ro.product.cpu.abi
Common outputs are x86_64, x86, arm64-v8a, or armeabi-v7a. Based on this, download the corresponding frida-server-<version>-android-<arch> from the Frida releases page on GitHub.
For example, if your emulator is x86_64, download frida-server-<latest_version>-android-x86_64.
2. Push to Emulator and Execute
Rename the downloaded file to something simpler, like frida-server, and then push it to the emulator’s /data/local/tmp/ directory:
mv ~/Downloads/frida-server-<version>-android-<arch> frida-serveradb push frida-server /data/local/tmp/
Now, connect to the emulator shell, make the binary executable, and run it:
adb shellsu --set-context u:r:su:s0 /system/bin/sh (if your emulator is rooted with magisk)chmod 755 /data/local/tmp/frida-server/data/local/tmp/frida-server &
The `&` detaches the process, allowing it to run in the background. If your emulator is not rooted, you might need to push the server to a writable directory like `/data/local/tmp` and run it from there directly.
To allow the Frida client on your host machine to communicate with the server, forward the default Frida port (27042):
adb forward tcp:27042 tcp:27042
3. Verify Frida Server is Running
On your host machine, run:
frida-ps -U
If Frida is set up correctly, you should see a list of processes running on your emulator. If not, troubleshoot the previous steps.
Bypassing Root Detection with Frida
Many Android applications implement root detection to prevent their execution on compromised devices, often to deter piracy or enhance security. Frida can bypass these checks by hooking into the functions responsible for detecting root.
Understanding Root Detection Mechanisms
Common root detection methods include:
- Checking for common root files (e.g.,
/system/bin/su,/system/xbin/su). - Checking for installed root management apps (e.g., Magisk Manager, SuperSU).
- Checking for sensitive paths in the
PATHenvironment variable. - Testing for read/write access to typically restricted areas.
- Checking for specific system properties (e.g.,
ro.build.tags=test-keys).
Frida Script for Generic Root Bypass
Here’s a generic Frida script (root_bypass.js) that targets common root detection indicators:
Java.perform(function() { var RootPackages = [ "com.noshufou.android.su", "com.noshufou.android.su.elite", "eu.chainfire.supersu", "com.koushikdutta.superuser", "com.zacharyronse.frick.superuser", "com.topjohnwu.magisk", "com.chengfu.rootkit" ]; var RootBinaries = [ "su", "busybox", "magisk" ]; var RootProperties = [ "ro.build.selinux", "ro.debuggable", "service.adb.root" ]; var RootCommands = [ "/system/xbin/which su", "/system/bin/which su", "which su" ]; // Bypass for checking root binaries and packages var Runtime = Java.use("java.lang.Runtime"); var File = Java.use("java.io.File"); var String = Java.use("java.lang.String"); Runtime.exec.overload('java.lang.String').implementation = function(cmd) { for (var i = 0; i < RootBinaries.length; i++) { if (cmd.indexOf(RootBinaries[i]) != -1) { console.log("[+] Root command detected: " + cmd); return; // Prevent execution } } return this.exec(cmd); }; File.exists.implementation = function() { var path = this.getAbsolutePath(); for (var i = 0; i < RootBinaries.length; i++) { if (path.indexOf(RootBinaries[i]) != -1) { console.log("[+] Root binary path detected: " + path); return false; // Pretend it doesn't exist } } for (var i = 0; i < RootPackages.length; i++) { if (path.indexOf(RootPackages[i].replace(/./g, "/")) != -1) { console.log("[+] Root package path detected: " + path); return false; } } return this.exists(); }; // Bypass for checking system properties var System = Java.use("java.lang.System"); System.getProperty.overload('java.lang.String').implementation = function(prop) { for (var i = 0; i < RootProperties.length; i++) { if (prop == RootProperties[i]) { console.log("[+] Root property detected: " + prop); return null; } } return this.getProperty(prop); }; // Bypass for checking build tags (e.g., "test-keys") var Build = Java.use("android.os.Build"); Build.TAGS.value = "release-keys"; // Bypass for PackageManager.getPackageInfo() var PackageManager = Java.use("android.content.pm.PackageManager"); PackageManager.getPackageInfo.overload('java.lang.String', 'int').implementation = function(packageName, flags) { for (var i = 0; i < RootPackages.length; i++) { if (packageName == RootPackages[i]) { console.log("[+] Root package lookup detected: " + packageName); throw Java.use("android.content.pm.PackageManager$NameNotFoundException").$new(); // Simulate not found } } return this.getPackageInfo(packageName, flags); }; console.log("[+] Root detection bypass loaded!");});
To inject this script into an application (e.g., com.example.app), ensuring it starts with the bypass loaded:
frida -U -l root_bypass.js -f com.example.app --no-pause
The --no-pause flag ensures the app starts immediately after Frida attaches. If the app is already running, use frida -U -l root_bypass.js -n com.example.app.
Bypassing SSL Pinning with Frida
SSL pinning is a security mechanism where an application hardcodes or ‘pins’ the expected SSL certificate or public key for its communication with specific domains. This prevents man-in-the-middle (MITM) attacks, even if a trusted root CA (like Burp Suite’s CA) is installed on the device.
What is SSL Pinning?
When an app connects to a server, it checks the server’s certificate against its pinned certificate(s). If they don’t match, the connection is terminated, regardless of whether the certificate is otherwise valid and trusted by the OS. This makes it difficult for proxies like Burp Suite or OWASP ZAP to intercept and inspect traffic.
Frida Script for SSL Pinning Bypass
Bypassing SSL pinning involves hooking into the application’s network security libraries (e.g., OkHttp, TrustManager, WebView) and modifying their behavior to trust all certificates or to ignore the pinning logic. A comprehensive script often targets multiple common pinning implementations.
Save the following as ssl_bypass.js:
Java.perform(function() { console.log("[+] SSL pinning bypass started"); var CertificateFactory = Java.use("java.security.cert.CertificateFactory"); var FileInputStream = Java.use("java.io.FileInputStream"); var BufferedInputStream = Java.use("java.io.BufferedInputStream"); var X509Certificate = Java.use("java.security.cert.X509Certificate"); var KeyStore = Java.use("java.security.KeyStore"); var TrustManagerFactory = Java.use("javax.net.ssl.TrustManagerFactory"); var SSLContext = Java.use("javax.net.ssl.SSLContext"); // Bypass TrustManagerImpl.checkTrusted() for Android 7+ (NPE workaround) try { var TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl'); TrustManagerImpl.verifyChain.implementation = function(untrustedChain, trustAnchor, host, clientAuth, ocspData, tlsSrpLogin) { console.log("[+] Bypassing TrustManagerImpl.verifyChain"); return untrustedChain; }; } catch (err) { console.log("[-] TrustManagerImpl hook not applicable/found"); } // Android < 7, OkHTTP & other common libraries var TrustManager = Java.use('javax.net.ssl.X509TrustManager'); var TrustManagerArray = Java.array('javax.net.ssl.X509TrustManager', [Java.cast(Trustma, TrustManager)]); var SSLContext_init = SSLContext.init.overload('[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom'); SSLContext_init.implementation = function(km, tm, sr) { console.log("[+] Bypassing SSLContext.init with custom TrustManager"); SSLContext_init.call(this, km, TrustManagerArray, sr); }; // OkHttp3 try { var OkHttpClient = Java.use('okhttp3.OkHttpClient'); OkHttpClient.Builder.overload('okhttp3.OkHttpClient').implementation = function(builder) { console.log("[+] Bypassing OkHttpClient builder"); builder.hostnameVerifier.implementation = function(hostname, session) { return true; }; return this.Builder(builder); }; OkHttpClient.Builder.overload().implementation = function() { console.log("[+] Bypassing OkHttpClient builder (no args)"); var builder = this.Builder(); builder.hostnameVerifier.implementation = function(hostname, session) { return true; }; return builder; }; var CertificatePinner = Java.use('okhttp3.CertificatePinner'); CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function() { console.log("[+] Bypassing OkHttp3 CertificatePinner.check"); return; // NOP }; CertificatePinner.check.overload('java.lang.String', '[Ljava.security.cert.Certificate;').implementation = function() { console.log("[+] Bypassing OkHttp3 CertificatePinner.check (array)"); return; // NOP }; } catch (err) { console.log("[-] OkHttp3 hooks not applicable/found"); } // WebView pinning (Android 7+) try { var WebViewClient = Java.use('android.webkit.WebViewClient'); WebViewClient.onReceivedSslError.overload('android.webkit.WebView', 'android.webkit.SslErrorHandler', 'android.net.http.SslError').implementation = function(view, handler, error) { console.log("[+] Bypassing WebViewClient.onReceivedSslError"); handler.proceed(); }; } catch (err) { console.log("[-] WebViewClient hooks not applicable/found"); } // TrustManagerFactory (general approach) TrustManagerFactory.checkServerTrusted.overload('[Ljava.security.cert.X509Certificate;', 'java.lang.String').implementation = function(chain, authType) { console.log("[+] Bypassing TrustManagerFactory checkServerTrusted (chain, authType)"); return; }; TrustManagerFactory.checkServerTrusted.overload('[Ljava.security.cert.X509Certificate;', 'java.lang.String', 'java.lang.String').implementation = function(chain, authType, host) { console.log("[+] Bypassing TrustManagerFactory checkServerTrusted (chain, authType, host)"); return; }; console.log("[+] SSL pinning bypass loaded!"); function Trustma() { this.$init = function() {}; this.checkClientTrusted = function(chain, authType) { // console.log("[+] checkClientTrusted: " + authType); }; this.checkServerTrusted = function(chain, authType) { // console.log("[+] checkServerTrusted: " + authType); }; this.getAcceptedIssuers = function() { // console.log("[+] getAcceptedIssuers"); return []; }; }}`);
To run this script with your target application (e.g., com.target.app):
frida -U -l ssl_bypass.js -f com.target.app --no-pause
After running this, you should be able to intercept the application's HTTPS traffic using a proxy like Burp Suite, provided you have installed Burp's CA certificate in the emulator's user or system trust store. For Android 7+ apps targeting API 24+, you might also need to configure a network security configuration XML file within the app to trust user-added certificates or use Magisk's `Move Certificates` module to move the Burp certificate to the system trust store.
Conclusion
Frida provides an incredibly powerful and flexible platform for dynamic analysis of Android applications. By following the steps outlined in this guide, you can set up a robust reverse engineering lab on an emulator and effectively bypass common security mechanisms like root detection and SSL pinning. Remember to use these techniques responsibly and ethically for security research and legitimate penetration testing purposes only.
Android Mobile Specs & Compare Directory
Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!
Compare Devices Specs →