Android App Penetration Testing & Frida Hooks

Reverse Engineering Lab: Defeating Android Integrity Checks with Advanced Frida Hooks

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction: The Battle Against Tampering

Android applications often employ sophisticated integrity checks to prevent tampering, unauthorized modification, and protect sensitive data. These anti-tampering mechanisms range from simple root detection to complex signature verification and debugger checks. For penetration testers and reverse engineers, bypassing these checks is a critical skill for assessing an application’s true security posture. This guide will deep dive into using Frida, a dynamic instrumentation toolkit, to defeat advanced Android integrity checks.

Understanding Android Integrity Checks

Before we bypass them, let’s understand common integrity checks:

  • Root Detection: Checks for the presence of su binaries, common root files/folders, or known insecure properties.
  • Debugger Detection: Identifies if a debugger is attached (e.g., checking `android.os.Debug.isDebuggerConnected()`).
  • Signature Verification: Ensures the application’s installed signature matches its original, expected signature. Prevents re-signing.
  • Checksums/Hash Checks: Verifies the integrity of internal files or code segments against known hashes.
  • Emulator Detection: Identifies if the app is running in an emulator environment.

Setting Up Your Reverse Engineering Lab

Prerequisites

To follow along, you’ll need:

  • A rooted Android device or an emulator (e.g., Genymotion, Android Studio AVD) with root access.
  • ADB (Android Debug Bridge) installed on your host machine.
  • Python 3 and pip.
  • Jadx-GUI for static analysis (or any other decompiler like Ghidra, JEB).
  • Frida tools installed on your host machine (`pip install frida-tools`).

Frida Server Installation

First, download the correct Frida server for your device’s architecture (ARM, ARM64, x86, x86_64) from the Frida releases page. You can determine your device’s architecture using `adb shell getprop ro.product.cpu.abi`.

# Download the server (replace with your version/arch)adb push frida-server-16.1.4-android-arm64 /data/local/tmp/# Make it executableadb shell "chmod 755 /data/local/tmp/frida-server"# Run the server in the backgroundadb shell "/data/local/tmp/frida-server &"

Case Study 1: Bypassing Root Detection

Many applications check for root by looking for specific files or properties. Let’s assume an app has a simple root check like this in its Java code:

public class RootChecker {    public boolean isDeviceRooted() {        String[] paths = {            "/system/app/Superuser.apk",            "/sbin/su",            "/system/bin/su",            "/system/xbin/su",            "/data/local/su"        };        for (String path : paths) {            if (new File(path).exists()) {                return true;            }        }        return false;    }}

Identifying the Target Method

Using Jadx, we’d decompile the APK, search for strings like “/system/bin/su”, and identify the `isDeviceRooted()` method.

Crafting the Basic Frida Hook

Our goal is to force `isDeviceRooted()` to always return `false`.

Java.perform(function() {    var RootChecker = Java.use('com.example.app.RootChecker'); // Replace with actual package/class name    RootChecker.isDeviceRooted.implementation = function() {        console.log('Hooking isDeviceRooted() - Returning false');        return false;    };    console.log('Root detection bypass loaded!');});

To run this, save it as `root_bypass.js` and execute: `frida -U -l root_bypass.js com.example.app` (replace `com.example.app` with the target app’s package name).

Case Study 2: Advanced Integrity Check – Signature Verification Bypass

Signature verification ensures an app hasn’t been repackaged. Apps typically retrieve their own signing certificate and compare it against a hardcoded expected value. A common pattern involves `PackageManager.getPackageInfo()` with `PackageManager.GET_SIGNATURES` flag.

The Challenge of Signature Checks

The app will typically:

  1. Get its own package name.
  2. Call `getPackageManager().getPackageInfo(packageName, PackageManager.GET_SIGNATURES)`.
  3. Extract signatures from the `PackageInfo` object.
  4. Compare these signatures (or their hashes) to known good values.

Dynamic Analysis with Frida Tracing

First, let’s use `frida-trace` to identify potential methods involved in signature retrieval:

frida-trace -U -i "*getPackageInfo*" -f com.example.app

This might reveal calls to `android.app.ApplicationPackageManager.getPackageInfo`. We’d then refine our search or set deeper hooks.

Developing the Signature Spoofing Hook

We need to hook `getPackageInfo` and modify the returned `PackageInfo` object before it reaches the application’s verification logic. Our goal is to replace the actual signature with a known, valid signature (e.g., from an original, untampered APK, or simply an empty/dummy signature if that bypasses the check).

Java.perform(function() {    var PackageManager = Java.use('android.content.pm.PackageManager');    var ApplicationPackageManager = Java.use('android.app.ApplicationPackageManager');    var PackageInfo = Java.use('android.content.pm.PackageInfo');    var Signature = Java.use('android.content.pm.Signature');    var ContextWrapper = Java.use('android.content.ContextWrapper');    var ActivityThread = Java.use('android.app.ActivityThread');    // Helper to get the current package name, useful for targeted hooks    function getCurrentPackageName() {        var currentApplication = ActivityThread.currentApplication();        if (currentApplication) {            return currentApplication.getPackageName();        }        return null;    }    ApplicationPackageManager.getPackageInfo.overload('java.lang.String', 'int').implementation = function(packageName, flags) {        var currentPackage = getCurrentPackageName();        if (currentPackage && packageName === currentPackage && (flags & PackageManager.GET_SIGNATURES) !== 0) {            console.log('Hooking getPackageInfo for ' + packageName + ' with GET_SIGNATURES flag.');            var originalPackageInfo = this.getPackageInfo(packageName, flags);            // Create a dummy signature (you can replace this with a valid, original signature)            // For example, if you have the SHA1 of the original signature, you can construct a byte array.            // byte[] goodSigBytes = { 0x30, 0x82, 0x02, ... };            // var newSignature = Signature.$new(Java.array('byte', goodSigBytes));            // For demonstration, let's create a placeholder or return original if no modification needed.            // In a real scenario, you'd carefully construct or obtain the *correct* signature.            var dummySignature = Signature.$new("308202300C06072A8648CE3D020106082A8648CE3D03010704820220308201E730820150A003020102020438EA64B7300D06092A864886F70D01010505003021311F301D06035504030C16416E64726F6964204465627567301E170D3139303531383135333534345A170D34393035313135333534345A3021311F301D06035504030C16416E64726F696420446562756730819F300D06092A864886F70D010101050003818D0030818902818100A0CC4205B01456182D0911A056322A2D9D25B32D21B78B75F390885233D22416EF091B01B112B10B45C0B746E985A9B4B821F2B0B5E052D12E9B4E38EB2D52F877EB00845F5B738018330D766C20377484B05C72295D3E332616E5A8E5C1B85D81F4B845C992EBE805A128B45B3A1346CD736025BA298F296410203010001300D06092A864886F70D0101050500038181005C55C3E211E11E935AE718873E4749E5D49C52A20E6714D96792377A6C5892D5F2F7600867F5B6081E57A0A9F9C0112A9E66F48DF6E32A0210E6F05EC07B9D2C6A30E6C7C1D185C6615EC822558509F5E6B47C1052219717C9482F81B8246AEB9B9A6F45888E021F8705F19D3F8226019A149BF833D2E2D4BE2782A9C5");            // Assign the new signature array to the PackageInfo object            originalPackageInfo.signatures.value = [dummySignature];            return originalPackageInfo;        }        return this.getPackageInfo(packageName, flags);    };    console.log('Signature verification bypass loaded!');});

In a real scenario, you would extract the legitimate signature bytes from an original APK and use `Signature.$new(Java.array(‘byte’, goodSigBytes))` to construct a valid `Signature` object for spoofing. This hook ensures that whenever the app requests its package info with the `GET_SIGNATURES` flag, it receives the

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 →
Google AdSense Inline Placement - Content Footer banner