A preliminary study on App reverse engineering

📚 This article will take you from "statically viewing APK" to "dynamically changing behavior" to get started with mobile security analysis and crawler pre-requisite skills.


Course Objectives

Take 3 minutes to clarify the implementable results of this learning:

  • Quickly disassemble the core components of APK in 30 seconds
  • 1 minute to select the scene and start the decompilation tool
  • 3 types of Frida Hook scripts (SSL/Root/general functions), just copy and paste to use
  • A complete 4-step process for encrypted app packet capture + restoration of signature logic

1. APK parsing basics

1.1 APK core structure "Tear it down first and then look at it"

Many novices think that APK is a "special binary file", but in fact it is a standard ZIP compressed package with a signature. You can scan directly using decompression tools or Python native libraries:

# Windows:用 WinRAR / 7-Zip 解压即可(解压前记得先备份)
# Mac / Linux:
unzip -d ./test_apk example.apk

The most common 7 core directories/files after decompression:

example.apk/
├── AndroidManifest.xml  # 二进制清单 → 包名、权限、四大组件(Activity/Service/Broadcast/ContentProvider)
├── classes*.dex         # Dalvik/ART 字节码 → Java/Kotlin 代码编译出的可执行文件
├── lib/                 # 原生库目录 → arm64-v8a/armeabi-v7a/x86 等 ABI 对应的 .so 文件(高性能/底层加密常用)
├── res/                 # 编译资源 → 布局、图片、字符串的索引版本
├── resources.arsc       # 资源索引表 → 把 res 里的索引映射为内存地址
└── META-INF/            # 签名校验区 → 确保 APK 未被篡改

Novice-friendly "one-click scanning script" (Python native)

Quickly locate sensitive files without installing additional tools:

import zipfile

def quick_scan_apk(apk_path: str):
    try:
        with zipfile.ZipFile(apk_path, "r") as z:
            all_files = z.namelist()
            print(f"✅ APK 解析成功!总文件数: {len(all_files)}\n")

            # 1. 找 DEX 文件(有多个说明代码量可能大/混淆拆分过)
            dex_list = [f for f in all_files if f.endswith(".dex")]
            print(f"📝 DEX 文件: {dex_list}\n")

            # 2. 找原生库 ABI(选设备对应 ABI 的 so 分析,手机一般选 arm64-v8a)
            abi_list = list(set([f.split("/")[1] for f in all_files if f.startswith("lib/")]))
            print(f"🔧 支持的原生库 ABI: {abi_list}\n")

            # 3. 找签名文件(后续重打包需要替换)
            sign_list = [f for f in all_files if f.startswith("META-INF/")]
            print(f"🔒 签名文件: {sign_list}")
    except Exception as e:
        print(f"❌ 解析失败: {e}")

if __name__ == "__main__":
    quick_scan_apk("example.apk")  # 换成你的 APK 路径

1.2 Decompile the Three Musketeers "Select as needed"

Choosing the right tool can save 90% of your time. Sort by recommendation priority:

Tool nameCore capabilitiesRecommended scenarios
jadx-guiDEX → Java code with syntax highlighting; automatically removes common lightweight obfuscated shellsPreferred in most scenarios, quickly locates encryption/network core code
apktoolComplete decoding/encoding of smali code; lossless modification of resources/layout; basic repackagingUsed when you need to change smali logic, add debugging switches, and replace certificates
dex2jar + JD-GUILightweight DEX → JAR; quickly browse the code of third-party libraries (such as okhttp/glide)Temporarily view a certain third-party logic and do not want to install jadx-gui

jadx-gui core usage (super simple)

  1. Download: jadx GitHub Release 页, select the compressed package corresponding to the system
  2. Run:
    # Mac / Linux 终端
    ./jadx/bin/jadx-gui example.apk
    
    # Windows 双击 jadx-gui.bat,选择 APK
  3. Search skills: Select "Method Name" "Class Name" "Text" in the search bar at the top and entersign/encrypt/OkHttpClientQuick positioning

2. Hook Technology God: Frida

Frida is a "dynamic behavior injection tool" that requires no repackaging, no Root (optional), and is cross-platform. Simply put, it inserts a piece of JS code into the app when it is running and modifies its function logic.

2.1 Quickly set up the environment in 5 minutes

PC side (Python3+ already installed)

pip install frida-tools   # 自动安装 frida 和 frida-tools
  1. View device ABI: Terminal executionadb shell getprop ro.product.cpu.abi
  2. Download the corresponding version of frida-server: frida GitHub Release 页 (file name format:frida-server-版本号-android-ABI
  3. Push and start:
    # 推送到临时目录(Root 机专用)
    adb push frida-server-16.4.11-android-arm64 /data/local/tmp/frida
    
    # 赋予执行权限
    adb shell chmod 755 /data/local/tmp/frida
    
    # 后台启动(Root 机)
    adb shell su -c "/data/local/tmp/frida &"
  4. Verify the connection:
    frida-ps -U   # 列出 USB 设备上的所有进程(能看到说明连接成功)

2.2 Type 3 Frida Hook scripts that can be used by copying and pasting

Script 1: General Java method Hook (print input parameters/return value/call stack)

Quickly locate the input and output and call source of encryption functions, a must for novices.

// hook_common.js
Java.perform(function () {
  // 1. 替换成目标类的完整包名
  var TargetClass = Java.use("com.example.app.utils.SignUtils");
  // 2. 替换成目标方法名(支持重载:["getSign", "[B", "java.lang.String"])
  var TargetMethod = TargetClass["getSign"];

  // 重写目标方法
  TargetMethod.implementation = function () {
    console.log("\n========== 目标方法被调用 ==========");
    console.log(`🔍 调用类: ${this.getClass().getName()}`);

    // 打印所有入参
    console.log(`📥 入参数量: ${arguments.length}`);
    for (let i = 0; i < arguments.length; i++) {
      let arg = arguments[i];
      // 自动识别类型并转成字符串
      let argType = arg ? arg.getClass().getName() : "null";
      let argStr = arg
        ? argType === "[B"
          ? "Hex: " + byteArrayToHex(arg)
          : arg.toString()
        : "null";
      console.log(`  参数${i}[${argType}]: ${argStr}`);
    }

    // 调用原方法获取返回值
    let result = this.getSign.apply(this, arguments);

    // 打印返回值
    let resType = result ? result.getClass().getName() : "null";
    let resStr = result
      ? resType === "[B"
        ? "Hex: " + byteArrayToHex(result)
        : result.toString()
      : "null";
    console.log(`📤 返回值[${resType}]: ${resStr}`);

    // 打印调用栈(想查看是谁调用了目标方法时启用)
    // console.log("\n📌 调用栈:");
    // console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()));

    console.log("=====================================\n");
    return result; // 必须返回原方法的结果,否则 App 会崩溃
  };

  // 辅助函数:字节数组转 Hex 字符串
  function byteArrayToHex(byteArray) {
    var hex = "";
    for (var i = 0; i < byteArray.length; i++) {
      var b = byteArray[i] & 0xff;
      if (b < 0x10) hex += "0";
      hex += b.toString(16);
    }
    return hex;
  }
});

How to run:

# 附加到已启动的 App(App 需在前台)
frida -U com.example.app -l hook_common.js

# 或者从 Frida 启动 App(更常用,不会错过启动时的 Hook)
frida -U -f com.example.app -l hook_common.js --no-pause

Script 2: SSL Pinning bypass (general version, covering 90%+ scenarios)

The first hurdle before capturing the package. This script covers OkHttp3, Android 7+ system certificate, and WebView's Pinning:

// ssl_pinning_bypass.js
console.log("[.] 开始绕过 SSL Pinning...");

Java.perform(function () {
  // 1. 绕过 OkHttp3/4 CertificatePinner
  try {
    var CertificatePinner = Java.use("okhttp3.CertificatePinner");
    CertificatePinner.check.overload("java.lang.String", "java.util.List").implementation = function () {};
    CertificatePinner.check.overload(
      "java.lang.String",
      "java.security.cert.Certificate",
      "java.util.List"
    ).implementation = function () {};
    console.log("[+] OkHttp3/4 CertificatePinner 绕过成功");
  } catch (e) {}

  // 2. 绕过 Android 7+ Conscrypt TrustManagerImpl
  try {
    var TrustManagerImpl = Java.use("com.android.org.conscrypt.TrustManagerImpl");
    TrustManagerImpl.verifyChain.implementation = function () {
      return arguments[0]; // 直接返回证书链,不校验
    };
    console.log("[+] Android 7+ Conscrypt TrustManagerImpl 绕过成功");
  } catch (e) {}

  // 3. 绕过 Android 6+ X509TrustManager 扩展
  try {
    var X509ExtendedTrustManager = Java.use("javax.net.ssl.X509ExtendedTrustManager");
    var EmptyTrustManager = Java.registerClass({
      name: "com.example.EmptyTrustManager",
      implements: [X509ExtendedTrustManager],
      methods: {
        checkClientTrusted: function () {},
        checkServerTrusted: function () {},
        getAcceptedIssuers: function () {
          return [];
        },
        checkClientTrustedSSLEngine: function () {},
        checkServerTrustedSSLEngine: function () {},
      },
    });
    var SSLContext = Java.use("javax.net.ssl.SSLContext");
    SSLContext.init
      .overload(
        "[Ljavax.net.ssl.KeyManager;",
        "[Ljavax.net.ssl.TrustManager;",
        "java.security.SecureRandom"
      )
      .implementation = function (km, tm, sr) {
        var emptyTm = Java.array("Ljavax.net.ssl.TrustManager;", [EmptyTrustManager.$new()]);
        return this.init
          .overload(
            "[Ljavax.net.ssl.KeyManager;",
            "[Ljavax.net.ssl.TrustManager;",
            "java.security.SecureRandom"
          )
          .call(this, km, emptyTm, sr);
      };
    console.log("[+] X509ExtendedTrustManager 绕过成功");
  } catch (e) {}

  // 4. 绕过 WebView SSL 错误(可选,抓 H5 包时用)
  try {
    var WebViewClient = Java.use("android.webkit.WebViewClient");
    WebViewClient.onReceivedSslError.implementation = function (view, handler, error) {
      handler.proceed();
    };
    console.log("[+] WebView SSL 错误 绕过成功");
  } catch (e) {}

  console.log("[✅] 所有 SSL Pinning 绕过完成!");
});

Script 3: Root detection bypass (universal basic version)

The second hurdle before packet capture + Hook. This script covers common file checks, system property checks, and Build field checks:

// root_bypass.js
console.log("[.] 开始绕过 Root 检测...");

Java.perform(function () {
  // 1. 隐藏常见的 Root 相关文件
  try {
    var File = Java.use("java.io.File");
    var rootFiles = [
      "/sbin/su",
      "/system/bin/su",
      "/system/xbin/su",
      "/data/local/xbin/su",
      "/data/local/bin/su",
      "/system/sd/xbin/su",
      "/system/bin/failsafe/su",
      "/data/local/su",
      "/su/bin/su",
      "/sbin/which",
      "/system/bin/which",
      "/system/xbin/which",
      "/system/app/Superuser.apk",
      "/system/app/SuperSU.apk",
      "/system/app/Magisk.apk",
    ];

    // 重写 File 构造函数
    File.$init.overload("java.lang.String").implementation = function (path) {
      if (rootFiles.indexOf(path) !== -1) path = "/nonexistent_file_xyz";
      return this.$init.overload("java.lang.String").call(this, path);
    };

    // 重写 File.exists()
    File.exists.implementation = function () {
      var path = this.getAbsolutePath().toString();
      if (rootFiles.indexOf(path) !== -1) return false;
      return this.exists.call(this);
    };

    console.log("[+] Root 相关文件隐藏成功");
  } catch (e) {}

  // 2. 修改 Build.TAGS 为 release-keys
  try {
    var Build = Java.use("android.os.Build");
    Build.TAGS.value = "release-keys";
    console.log("[+] Build.TAGS 修改成功");
  } catch (e) {}

  // 3. 修改系统属性 ro.debuggable/ro.secure(可选)
  try {
    var SystemProperties = Java.use("android.os.SystemProperties");
    SystemProperties.get.overload("java.lang.String").implementation = function (key) {
      if (key === "ro.debuggable") return "0";
      if (key === "ro.secure") return "1";
      if (key === "ro.build.type") return "user";
      return this.get.overload("java.lang.String").call(this, key);
    };
    console.log("[+] 系统属性修改成功");
  } catch (e) {}

  console.log("[✅] 所有 Root 检测绕过完成!");
});

3. Practical ideas: 4 steps to quickly analyze encryption apps

Assume that the target App has sign signature parameters and SSL Pinning, combined with the above tools and scripts, the complete process is as follows:

  1. Static positioning: Use jadx-gui to open APK and searchsign/encrypt/OkHttpClient,positionSignUtils.getSign()and network request logic
  2. Environment preparation: Configure the Burp Suite agent and install the CA certificate to the device User Certificate Directory
  3. Dynamic Launch: Launch the App from Frida and inject dual scripts at the same time:
    frida -U -f com.example.app -l ssl_pinning_bypass.js -l hook_common.js --no-pause
  4. Restore logic: operate the App to trigger the request, Burp captures the packet to view the actual value of the sign, and Frida prints it.getSignEnter the parameters, and finally manually write a Python script to restore the signature logic

The contents of this article are only used for legitimate security research, self-owned App vulnerability troubleshooting, and teaching demonstrations and may not be used for illegal purposes such as unauthorized App reverse engineering, data theft, and commercial profit. Violators shall bear all legal liability themselves.


Summary of this chapter

  • Static Analysis: APK is a ZIP compressed package, jadx-gui is the first choice for quickly viewing Java code
  • Dynamic Analysis: Frida does not need to be repackaged, and universal scripts cover 90%+ of entry-level scenarios
  • Getting started: First capture the packet (bypass SSL), then Hook (check the return value of the entry), and finally restore the signature logic

You can learn in depth in the future: smali syntax modification, so layer Hook (Frida Interceptor), Frida advanced hiding (against anti-debugging).