关于 java:10. Java 逆向分析基础

一、APK 中的 Java 层代码分析

1.1 Java 层逆向的整体目标

在逆向一个 APK 的 Java 层时,我们主要关注:

关注点说明
应用启动流程包括 ApplicationActivity 启动时做了哪些初始化
加密/签名函数getSign()encrypt()generateToken() 等方法
网络请求构造URL、Headers、参数构造、是否签名等
检测函数是否有反调试、root 检测、环境检查
动态加载逻辑是否动态加载 dex、class 文件或反射调用

1.2 核心工具说明

1)jadx

开源免费,支持 .apk.dex 转 Java 源码,带 GUI。

  • 安装:
brew install jadx
# 或直接从 release 下载 jadx-gui
  • 使用:
jadx-gui target.apk

功能:

  • 类/包结构清晰

  • 支持搜索关键字(如 encrypt、sign)

  • 可直接查看字符串、十六进制资源等

2)JEB Decompiler

商业级逆向工具,支持 Java + native(.so)联动分析。

  • 功能优势:

    • 精准反编译 dex → Java;

    • 支持动态分析 obfuscation(混淆);

    • 能解析 .so 文件,与 Java 调用关系关联;

    • 插件丰富,支持 Python 脚本自动化。

3)jeb2py(JEB 插件脚本)

用 Python 脚本操控 JEB,提取方法、类、分析报告等。

示例功能:

  • 批量导出所有类名、方法名;

  • 批量提取网络相关方法;

  • 自动标记 nativeencryptsign 等方法;

  • 输出为 markdown/html 分析文档。

示例脚本(提取类名 + 方法):

from com.pnfsoftware.jeb.core.units.code.android import DexClass

for unit in RuntimeProject.getCurrentProject().getLiveArtifacts()[0].getUnits():
    if isinstance(unit, DexClass):
        print("[Class]", unit.getName(True))
        for method in unit.getMethods():
            print("  [Method]", method.getName())

1.3 逆向分析实战流程

步骤一:APK 解包与预处理

apktool d app.apk -o app_decoded
  • 提取 AndroidManifest.xml 观察主启动组件和权限;

  • 提取 classes.dex 用于后续反编译。

步骤二:使用 jadx 查看结构和关键逻辑

打开 jadx GUI,加载 APK 或 .dex 文件:

搜索建议:

搜索内容目的
signtokenencrypt定位加密函数
System.loadLibrary定位调用 native 代码
Retrofit.BuilderOkHttpClient定位网络请求
Class.forNameinvoke定位反射调用
ApplicationattachBaseContext定位启动初始化

示例关键函数定位:

public String getSign(String param) {
    String token = md5(param + salt);
    return Base64.encodeToString(token.getBytes(), Base64.DEFAULT);
}

步骤三:使用 JEB 交叉分析 Java 与 native

常用视角:

  • Java 方法中带 native 修饰 ➜ 对应 .so 中实现;

  • 查找 RegisterNatives() ➜ 动态注册逻辑;

  • 使用 xref 查看函数调用关系;

  • 快速跳转 Java → native → Java 方法。

步骤四:结合 jeb2py 自动标记分析点

  • 批量输出关键类、方法(混淆名也可);

  • 与自己写的加密函数还原表做匹配;

  • 自动提取网络请求类和 JSON 构造函数。

1.4 重点分析目标分类

类/模块作用逆向策略
Application 子类全局初始化看是否注册 SDK、反调试
Activity/Fragment启动流程看启动参数、页面加载逻辑
Utils/Sign参数处理搜索加密逻辑、构造流程
网络模块 Retrofit/OkHttp请求结构分析 headers、参数来源
Loader/DexClassLoader动态加载搜索 loadClass/dexPath
反射/隐式调用隐藏调用逻辑搜索 Class.forName, invoke
检测模块root, adb 检测搜索关键词如 isDebuggerConnected, Build.TAGS

1.5 混淆/壳处理方案

1)混淆命名恢复

  • 使用 jeb2py 导出方法,手动或基于签名重命名;

  • 结合运行时 Frida 获取真实类名;

  • 部分函数名(如 getSign, encrypt)不会混淆;

  • 构造函数系统类继承关系 不可混淆,照样能追到逻辑。

2)代码壳(壳 Dex)

  • 使用 dexdumpdex2jar 分析 classesN.dex;

  • 分析 attachBaseContext() ➜ 多 dex 加载逻辑;

  • 查找 loadDex, DexClassLoader, BaseDexClassLoader 实例化点;

  • 动态调试下断点配合 dump Dex(可配合 Frida 或 Xposed 插件如 DexDump)。

1.6 实战技巧

技巧方法
查字符串使用处右键字符串 → xref → 所在函数
网络 headers 分析addHeader, setHeader, put("token", ...)
dump 混淆类结构配合 jeb2py 或 jadx 导出树结构
快速找加密函数StringBuilder, Base64, Cipher, MessageDigest 连续组合出现的函数

1.7 小结

工具用途
jadx-gui快速查看结构,初步分析类、函数
JEB深度分析、混淆处理、Java-native 联动
jeb2py自动提取关键函数、混淆函数名,还原逻辑
apktool解包资源、提取 smali 可修改逻辑
Frida动态验证分析,后续 Hook 解密流程

二、smali ↔ Java 结构对应

2.1 类结构对应

Java 代码

package com.example;

public class Demo extends Object {
    private int count;
}

对应 smali

.class public Lcom/example/Demo;
.super Ljava/lang/Object;

.field private count:I

说明:

Javasmali
packageL包名/类名;
extends Object.super Ljava/lang/Object;
int count.field count:I
String str.field str:Ljava/lang/String;

2.2 方法结构(构造函数、参数、返回)

Java 示例

public Demo(int c) {
    this.count = c;
}

对应 smali

.method public constructor <init>(I)V
    .registers 2
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
    iput p1, p0, Lcom/example/Demo;->count:I
    return-void
.end method

对照表

项目Javasmali
构造函数Demo(int c)<init>(I)V
参数intIp1 为第 1 参数
this隐式p0 代表 this
赋值this.count = ciput p1, p0, Lxx;->count:I

2.3 基本操作与运算指令

Java 示例

int a = 2 + 3;
return a;

对应 smali

.const/4 v0, 0x2
.const/4 v1, 0x3
add-int v2, v0, v1
return v2

2.4 方法调用

Java 示例

Helper h = new Helper();
h.work("Hello");

对应 smali

new-instance v0, Lcom/example/Helper;
invoke-direct {v0}, Lcom/example/Helper;-><init>()V

const-string v1, "Hello"
invoke-virtual {v0, v1}, Lcom/example/Helper;->work(Ljava/lang/String;)V

方法调用指令对照表

指令含义示例
invoke-virtual实例方法obj.method()
invoke-static静态方法Class.method()
invoke-direct构造方法或私有方法newthis.private()
invoke-super调用父类方法super.method()
invoke-interface接口调用List.add()
move-result获取返回值move-result v0

2.5 控制结构映射(if、while、switch)

if 示例(Java)

if (a > b) {
    return a;
} else {
    return b;
}

smali

if-gt p1, p2, :greater
move v0, p2
goto :end
:greater
move v0, p1
:end
return v0

对照

Javasmali
if (a > b)if-gt p1, p2, :label
goto跳转到某标签
label:跳转位置标签,如 :end:greater

2.6 数组操作

Java 示例

int[] arr = new int[3];
arr[0] = 42;
return arr[0];

smali

const/4 v0, 0x3
new-array v1, v0, [I

const/16 v2, 0x2A        # 42
aput v2, v1, 0

aget v3, v1, 0
return v3
指令功能
new-array分配数组 [I 表示 int[]
aput数组赋值
aget数组读取

2.7 异常处理结构

Java

try {
    int a = 1 / b;
} catch (Exception e) {
    return -1;
}

smali

    .try_start :start
    const/4 v0, 0x1
    div-int v1, v0, p1
    goto :end
    .try_end :start :end
    .catch Ljava/lang/Exception; {:start .. :end} :catch

:catch
    const/4 v1, -0x1

:end
    return v1

2.8 常见类型描述符表

Java 类型smali 描述符
intI
longJ
floatF
doubleD
booleanZ
byteB
shortS
charC
voidV
StringLjava/lang/String;
int[][I
String[][Ljava/lang/String;

2.9 反射映射识别

Java

Method m = SomeClass.class.getDeclaredMethod("sign", String.class);
m.setAccessible(true);
String result = (String) m.invoke(null, "data");

对应 smali(大致逻辑)

const-string v0, "sign"
const-class v1, Ljava/lang/String;

invoke-static {v0, v1}, Ljava/lang/Class;->getDeclaredMethod(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;

invoke-virtual {v2, v3}, Ljava/lang/reflect/Method;->invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;

2.10 常见情况总结

目标对应技巧
找构造函数逻辑<init>
看加密参数搜含有 invoke-static 且有 signtokenmd5 的函数
看网络请求类Retrofit, OkHttpClient, setHeader, put
看动态加载DexClassLoader, loadClass
跳转关系图用 jadx GUI 点右键 ➜ 交叉引用(xref)
判断 native 注册native 关键字或 System.loadLibrary

三、hook Java 方法

3.1 什么是 Hook?

Hook 是指“劫持/拦截”目标函数的执行过程,插入我们自己的逻辑代码。

适用于以下场景:

场景Hook 目的
登录验证函数劫持参数、返回 true、绕过验证
token/sign 生成函数抓加密原始参数 + 结果
检测函数劫持 isDebuggerConnected() 返回 false
防护逻辑拦截“崩溃点”防止关闭应用

3.2 工具对比:Xposed vs Frida

项目XposedFrida
是否需要 root 需要 可免 root(推荐)
修改方式系统级模块注入动态注入,实时修改
支持范围Java 方法、系统 APIJava、native、JNI、socket 等
注入时机重启后持续 Hook启动后动态 attach

3.3 使用 Xposed Hook Java 方法

1)基础写法(Java)

findAndHookMethod("com.xxx.LoginManager", lpparam.classLoader, "checkLogin", new XC_MethodHook() {
    @Override
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
        Log.d("Xposed", "Login 参数: " + param.args[0]);
    }

    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        param.setResult(true);  // 强制返回 true(绕过验证)
    }
});

关键点说明:

部位功能
lpparam.classLoader当前 app 的类加载器
"com.xxx.LoginManager"要 hook 的类
"checkLogin"要 hook 的方法名
param.args方法参数(数组)
param.setResult修改方法返回值

2)实战定位建议:

  • 配合 Log.d() 打印日志,记录参数 / 返回值;

  • 可以搭配 Xposed 框架中的 LSPosed 使用,更加稳定;

  • 如果函数名是混淆的,可以通过 jadx 找目标函数名。

3.4 使用 Frida Hook Java 方法

启动方式

frida -U -n com.target.app -l hook.js

或先运行:

frida-trace -U -n com.target.app -m "com.xxx.LoginManager.checkLogin"

1)基础 Hook 写法(Frida)

Java.perform(function () {
    var Cls = Java.use("com.xxx.LoginManager");

    Cls.checkLogin.implementation = function () {
        console.log("[*] checkLogin 被调用");
        var result = this.checkLogin();
        console.log("[*] 原始返回值: " + result);
        return true;  // 强制登录通过
    };
});

2)Hook 有参数方法

Cls.getSign.overload("java.lang.String", "java.lang.String").implementation = function (str1, str2) {
    console.log("[*] getSign 被调用");
    console.log("参数1:", str1);
    console.log("参数2:", str2);

    var ret = this.getSign(str1, str2);
    console.log("返回值:", ret);
    return ret;
};

使用 .overload(...) 精确指定重载函数参数。

3)动态遍历 & 泛用 Hook 工具

Java.enumerateLoadedClasses({
  onMatch: function(className) {
    if (className.indexOf("Sign") !== -1) {
      console.log("发现类:", className);
    }
  },
  onComplete: function() {}
});
// Hook 所有方法名为 "encrypt" 的函数
Java.perform(function () {
    var classList = ["com.xxx.Security", "com.xxx.TokenUtil"];
    classList.forEach(function (clsName) {
        var Cls = Java.use(clsName);
        Cls.encrypt.overloads.forEach(function (over) {
            over.implementation = function () {
                console.log("[*] 调用了 " + clsName + ".encrypt");
                for (var i = 0; i < arguments.length; i++) {
                    console.log("参数 " + i + ": " + arguments[i]);
                }
                var res = over.apply(this, arguments);
                console.log("返回值:", res);
                return res;
            }
        });
    });
});

3.5 常见 Frida 类型转换技巧

Java 类型Frida 传参写法
intJava.use("java.lang.Integer").valueOf(123)
byte[]Java.array('byte', [0x1, 0x2, 0x3])
String"abc"
ContextJava.use("android.app.ActivityThread").currentApplication().getApplicationContext()

3.6 实战典型 Hook 案例总结

目标函数Hook 操作
isDebuggerConnected()返回 false,绕过调试检测
getToken(String)打印参数、返回值,观察加密行为
DexClassLoader.loadClass()查看是否动态加载 dex
invoke()识别反射函数调用,打印调用栈
Base64.encodeToString()查看原始加密数据

3.7 Frida 报错处理小技巧

错误信息原因解决方案
JavaScriptException: No such overload函数参数不匹配.overloads 遍历查看
Java is not defined未附加 Java VMJava.perform() 包裹
Failed to attachApp 启动中 / 已退出重启 App 再 -n 附加
Permission denied无权限访问进程开启 root,或用模拟器测试

3.8 小结

项目XposedFrida
是否动态 ✘重启注入 ✔动态 attach
是否支持 native Hook ✘ ✔(包括 JNI)
hook 稳定性稳定持久动态易断开
推荐场景长期拦截、永久生效快速调试、绕过、数据抓取
学习曲线稍高,但功能强大

四、通过 Java 调用 native 层 JNI 分析

4.1 目标与适用场景

在逆向中,很多关键逻辑(如加密签名、反调试、root 检测)会藏在 native 层(.so 文件)中,Java 层只做简单封装。

需要分析:

  • Java 是如何调用 .so 中的方法?

  • native 方法名称、参数如何映射?

  • .so 内部具体做了什么(加密逻辑、检测、通信)?

4.2 Java → native 调用的 3 种方式

1)静态注册

Java 声明 native 方法,方法名自动映射到 .so 中的函数名。

Java 示例:

public class SignUtil {
    static {
        System.loadLibrary("native-lib");
    }

    public static native String getSign(String data);
}

C 层函数:

JNIEXPORT jstring JNICALL Java_com_example_SignUtil_getSign(JNIEnv *env, jclass clazz, jstring data) {
    // native 层处理逻辑
}

函数命名遵循规则:

Java_包名_类名_方法名

2)动态注册

Java 中调用 native 方法,但不通过函数名对应,而通过 RegisterNatives() 映射。

Java 示例(同上):

public static native String getSign(String data);

C 层:

static JNINativeMethod methods[] = {
    {"getSign", "(Ljava/lang/String;)Ljava/lang/String;", (void *) native_getSign}
};

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env;
    (*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6);
    jclass clazz = (*env)->FindClass(env, "com/example/SignUtil");
    (*env)->RegisterNatives(env, clazz, methods, 1);
    return JNI_VERSION_1_6;
}

3)反射 + native(少见)

Method m = Class.forName("com.example.SignUtil").getDeclaredMethod("getSign", String.class);
m.setAccessible(true);
String sign = (String) m.invoke(null, "data");

本质还是调用 native 函数,只是方法通过反射调用,防调试手段常这样隐藏。

4.3 如何定位 Java 调用 native 的地方?

Step 1:搜索 native 关键字

用 jadx 搜索:

native
System.loadLibrary

可以快速定位所有 Java 层的 native 方法声明。

Step 2:确认加载了哪些 .so 文件

static {
    System.loadLibrary("secure");  // 说明会加载 libsecure.so
}

也可能是动态加载:

Runtime.getRuntime().load("/data/data/xx/lib/libnative.so");

Step 3:寻找调用逻辑

看哪些 native 方法会被:

  • Activity 调用

  • getToken/getSign 加密函数调用

  • 网络请求构造中调用

4.4 .so 中 native 函数分析方法

工具准备:

工具用途
IDA Pro / Ghidra静态分析 .so,查看函数名
nm / readelf / strings查看符号表、导出函数
Frida动态 Hook native 函数
objdump反汇编

静态分析:使用 IDA 打开 .so 文件

常见函数名格式(静态注册):

Java_com_example_SignUtil_getSign

常见结构:

JNIEXPORT jstring JNICALL Java_com_example_SignUtil_getSign(JNIEnv *env, jclass clazz, jstring data) {
    const char *str = (*env)->GetStringUTFChars(env, data, 0);
    char result[32];
    md5(str, result);  // 调用内部加密逻辑
    return (*env)->NewStringUTF(env, result);
}

可以:

  • 追踪关键函数,如:md5, aes, sha1, encrypt, sign

  • 还原 C 层的加密流程

  • 尝试重写逻辑、跳过校验

动态分析:使用 Frida hook native 函数

Hook 所有导出函数:

Module.enumerateExportsSync("libnative.so").forEach(function(exp) {
    if (exp.type === 'function') {
        console.log("发现导出函数:", exp.name);
    }
});

Hook 某 native 函数(如 getSign):

Interceptor.attach(Module.findExportByName("libnative.so", "Java_com_example_SignUtil_getSign"), {
    onEnter: function (args) {
        console.log("getSign 参数: " + Java.vm.getEnv().getStringUtfChars(args[2], null).readCString());
    },
    onLeave: function (retval) {
        console.log("返回结果:", Java.vm.getEnv().getStringUtfChars(retval, null).readCString());
    }
});

4.5 RegisterNatives 动态注册的逆向技巧

1)在 smali 中搜索 RegisterNatives:

grep -rn RegisterNatives smali/

2)使用 Frida Hook RegisterNatives 获取映射表:

Interceptor.attach(Module.findExportByName(null, 'RegisterNatives'), {
    onEnter: function (args) {
        var env = args[0];
        var clazz = args[1];
        var methods = args[2];
        var count = args[3].toInt32();

        console.log("注册了 " + count + " 个方法");

        for (var i = 0; i < count; i++) {
            var fnPtr = methods.add(i * Process.pointerSize * 3);
            var name = fnPtr.readPointer().readCString();
            var sig = fnPtr.add(Process.pointerSize).readPointer().readCString();
            var addr = fnPtr.add(Process.pointerSize * 2).readPointer();
            console.log("name:", name, "sig:", sig, "addr:", addr);
        }
    }
});

4.6 实战定位 Java → native 的完整流程总结

步骤工具目的
1. 用 jadx 搜索 native 方法jadx找出 Java 层声明
2. 搜 System.loadLibraryjadx找出加载的 .so
3. 用 IDA 打开 .so 分析函数名IDA/Ghidra确定静态注册 or 动态注册
4. 搜 RegisterNativessmali / Frida动态注册就抓注册表
5. 用 Frida Hook native 函数Frida抓加密过程、返回值
6. 如果用 JNI 加密,尝试还原流程C 分析找算法、key、流程

4.7 小结

示例
native 方法声明public native String getSign(String s)
System.loadLibrary("xx")加载 libxx.so
Java_com_xx_yy_method静态注册
RegisterNatives()动态注册
JNIEXPORT, JNIEnv *envC 层函数标志
GetStringUTFChars, NewStringUTF字符串转换
Frida Interceptor.attach动态 hook native

五、动态注册类和反射绕过检查识别

5.1 动态注册类识别(JNI)

背景:开发者为了防止 Java_com_xxx_xxx_func() 被静态工具(如 IDA)直接看到,会使用 RegisterNatives() 在运行时动态绑定 Java 方法和 native 实现,绕过函数名暴露

Java 代码(示例):

public class NativeUtils {
    static {
        System.loadLibrary("native-lib");
    }

    public static native String getToken(String str);
}

C/C++ 动态注册代码:

jstring native_getToken(JNIEnv *env, jobject thiz, jstring str) {
    // native 层逻辑
}

JNINativeMethod gMethods[] = {
    {"getToken", "(Ljava/lang/String;)Ljava/lang/String;", (void *)native_getToken},
};

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env;
    (*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6);
    jclass clazz = (*env)->FindClass(env, "com/example/NativeUtils");
    (*env)->RegisterNatives(env, clazz, gMethods, 1);
    return JNI_VERSION_1_6;
}

如何识别?

方法 1:grep RegisterNatives in smali

grep -r RegisterNatives ./smali

如果在 JNI_OnLoad() 附近找到调用 RegisterNatives,就说明启用了动态注册。

方法 2:Frida 动态 Hook RegisterNatives

Interceptor.attach(Module.findExportByName(null, "RegisterNatives"), {
  onEnter: function (args) {
    var env = args[0];
    var clazz = args[1];
    var methods = args[2];
    var count = args[3].toInt32();

    console.log("[*] RegisterNatives 被调用,注册 " + count + " 个方法");

    for (var i = 0; i < count; i++) {
      var base = methods.add(i * Process.pointerSize * 3);
      var name = base.readPointer().readCString();
      var sig = base.add(Process.pointerSize).readPointer().readCString();
      var fn = base.add(Process.pointerSize * 2).readPointer();

      console.log("name:", name, "sig:", sig, "addr:", fn);
    }
  }
});

这段脚本可输出所有动态绑定的方法名称、签名和地址。

5.2 反射调用方法识别

背景:反射是一种运行时获取类、方法、字段并调用的方式。广泛用于:

  • 动态构造类调用链

  • 躲避静态分析

  • 实现插件化、热修复等框架

Java 反射代码示例

Class clazz = Class.forName("com.example.Encrypt");
Method m = clazz.getDeclaredMethod("getSign", String.class);
m.setAccessible(true);
String result = (String) m.invoke(null, "abc");

这段逻辑在 Java 静态代码中根本无法看到方法调用点,必须动态识别。

如何识别反射调用?

方法 1:jadx 搜索关键词

在 jadx 中全局搜索:

  • Class.forName

  • getDeclaredMethod

  • invoke

  • setAccessible

  • getMethod

常见用法:

Method m = Class.forName("xxx").getMethod("xxx", xxx);
Object res = m.invoke(...);

方法 2:hook invoke() 追踪调用者

Java.perform(function () {
  var Method = Java.use("java.lang.reflect.Method");

  Method.invoke.overload("java.lang.Object", "[Ljava.lang.Object;").implementation = function (obj, args) {
    console.log("\n[+] Method.invoke 被调用");
    console.log("  -> method: " + this.getName());
    console.log("  -> class: " + this.getDeclaringClass().getName());
    console.log("  -> args: " + args);

    var res = this.invoke(obj, args);
    console.log("  -> result: " + res);
    return res;
  };
});

该脚本能输出所有反射调用的方法名、所属类、参数和返回值。

5.3 结合动态注册 + 反射 的复杂案例识别

开发者经常混合使用反射 + 动态注册 + 多层封装来加密逻辑。

常见特征:

类型特征行为
隐藏类名Class.forName("com.xx.NativeEntry")
隐藏方法名getDeclaredMethod("getSign")
动态加载 dexDexClassLoader(...)
加载 native 库System.loadLibrary("xxx")
Native 层映射RegisterNatives() 注册 native 方法

5.4 Frida Hook 所有反射类方法

Java.perform(function () {
    var c = Java.use("java.lang.Class");
    c.forName.overload('java.lang.String').implementation = function(name) {
        console.log("[forName] -> " + name);
        return this.forName(name);
    };

    var m = Java.use("java.lang.reflect.Method");
    m.invoke.implementation = function(obj, args) {
        console.log("[invoke] method: " + this.getName() + " from class: " + this.getDeclaringClass().getName());
        return this.invoke(obj, args);
    };
});

5.5 小结

类型静态识别方式动态识别方式工具推荐
动态注册 JNIRegisterNatives、查看 JNI_OnLoadFrida hook RegisterNativesFrida / IDA
反射调用Class.forName, getMethod, invokehook Method.invokeClass.forNameFrida
native 函数映射查看 C 层绑定函数数组Frida dump 映射表IDA / Frida
混淆代码识别函数名极短、参数为字符串方法名仍可通过动态输出方法名识别Frida

六、解密/参数生成函数识别

6.1 常见加密参数场景

场景参数名举例
登录接口sign, token, signature, auth, s, _t
数据接口ts, checksum, encrypt_params, body_sign
验证码/滑块w, payload, verify_token
SDK安全参数X-Security, x-bogus, x-tt-token

这些参数都不会直接明文暴露,需要识别生成函数,或者还原解密逻辑

6.2 定位加密函数的常规套路(Java 层)

1)搜索关键词法(静态分析)

jadx JEB 中搜索关键词:

  • sign

  • token

  • md5, sha1, aes, rsa, hmac

  • Base64.encode, URLEncoder, cipher.init

  • MessageDigest, Mac, SecretKeySpec

示例:

MessageDigest md = MessageDigest.getInstance("MD5");
md.update(input.getBytes());
return md.digest();

2)看网络请求前的构造函数

找出网络请求类(如 Retrofit, OkHttp, HttpClient)前的构造逻辑:

String sign = SignUtil.getSign(map);
request.addHeader("sign", sign);

配合动态调试,就能抓到真实参数。

3)使用调用链追踪(jadx 功能)

在 jadx 中右键 getSign() → Show Usage → 追踪调用链→ 追踪到调用点 → 看参数来自哪里(request、map、拼接)

6.3 动态定位(Frida)

1)使用 Frida hook 可疑函数

可以直接 hook 类似 getSign, signData, encryptParams 等函数:

Java.perform(function () {
    var SignUtil = Java.use("com.xx.SignUtil");
    SignUtil.getSign.overload('java.util.Map').implementation = function (m) {
        console.log("[*] 调用 getSign()");
        console.log("[+] 参数: " + m.toString());
        var res = this.getSign(m);
        console.log("[+] 返回: " + res);
        return res;
    };
});

2)Hook 常用加密函数(通杀型)

也可以 Hook Java 标准库加密函数,抓底层真实数据:

Hook MD5 / SHA:

Java.perform(function () {
    var md = Java.use("java.security.MessageDigest");
    md.digest.overload().implementation = function () {
        console.log("调用 digest()");
        return this.digest();
    };
    md.update.overload("[B").implementation = function (b) {
        console.log("调用 update() 参数:", Java.use("java.lang.String").$new(b));
        this.update(b);
    };
});

Hook HMAC:

Java.use("javax.crypto.Mac").doFinal.overload("[B").implementation = function (b) {
    console.log("[HMAC] doFinal 参数: ", Java.use("java.lang.String").$new(b));
    return this.doFinal(b);
};

3)Hook 所有 encode/base64 类函数(抓加密前后数据)

Java.use("android.util.Base64").encodeToString.overload('[B', 'int').implementation = function (b, flag) {
    var str = Java.use("java.lang.String").$new(b);
    console.log("Base64.encode 参数:", str);
    var res = this.encodeToString(b, flag);
    console.log("Base64.encode 返回:", res);
    return res;
};

6.4 native 层加密识别方法

有时 Java 层只是简单调用 native 方法,真正的参数处理都藏在 .so 里。

1)搜索 native 方法定义(如:getToken, getSign

public native String getToken(String data);

2)Frida Hook JNI native 函数

Interceptor.attach(Module.findExportByName("libnative-lib.so", "Java_com_example_SignUtil_getToken"), {
    onEnter: function (args) {
        var data = Java.vm.getEnv().getStringUtfChars(args[2], null).readCString();
        console.log("[JNI] getToken 参数:", data);
    },
    onLeave: function (retval) {
        console.log("[JNI] 返回值:", Java.vm.getEnv().getStringUtfChars(retval, null).readCString());
    }
});

6.5 特征总结:如何判断一个函数是加密/参数生成函数?

特征说明
函数名含有 sign, token, encrypt, encode常见命名模式
函数调用后立即参与请求多用于 header/body/sign 生成
参数是 Map, String, JSONObject多为动态拼接参数
函数内部用到了 digest, Mac, AES, RSA常见加密组件
函数被反射调用故意混淆处理逻辑
函数跳转到 native 层利用 JNI 隐藏逻辑

6.6 自动追踪推荐

  • 打开 jadx,定位网络请求代码 → 找参数拼接位置

  • 标记所有调用 getSign / getToken 的地方

  • 用 Frida 写 hook 脚本打印这些函数参数与返回值

  • 如果找不到 Java 加密逻辑,找 native 方法

  • 若为 native → 用 IDA/Ghidra 分析加密逻辑

  • 若为动态注册 JNI → 用 Frida hook RegisterNatives 提取映射表

6.7 Frida 参数追踪推荐模板

Java.perform(function () {
    var classes = ["com.xxx.SignUtils", "com.xxx.Encrypt", "com.xxx.Security"];

    classes.forEach(function (name) {
        try {
            var Cls = Java.use(name);
            Cls.getSign.overloads.forEach(function (overload) {
                overload.implementation = function () {
                    console.log("[*] Hooked " + name + ".getSign");
                    for (var i = 0; i < arguments.length; i++) {
                        console.log("arg[" + i + "]:", arguments[i]);
                    }
                    var result = overload.apply(this, arguments);
                    console.log("result:", result);
                    return result;
                };
            });
        } catch (e) {
            // 忽略未找到类
        }
    });
});

6.8 小结

方法工具用法
搜函数名关键词jadx / jebgetSign / encrypt / token
找网络请求前参数构造jadx请求构造处看 header/body
动态 hook 方法FridaHook sign/token 函数输出参数
Hook 加密类FridaMessageDigest / Mac / AES
native 层分析IDA / Frida找 native 函数名 / 参数分析
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值