未被混淆或者自定义方法
HashMap 是 Java 中用于存储键值对的类,一般参数的组成都会通过这个方法,hook 代码如下:
function showStacks() {
Java.perform(function () {
// Log.getStackTraceString():将异常堆栈转换为可读字符串,便于输出调试
console.log(Java.use("android.util.Log").getStackTraceString(
// Throwable.$new():创建一个新的异常对象,其构造函数会自动记录当前线程的调用栈
Java.use("java.lang.Throwable").$new()
));
})
}
// HashMap.put 方法
function hook_hashMap(){
var hashMap = Java.use("java.util.HashMap");
// implementation 属性:替换 put 方法的具体逻辑,拦截所有调用
hashMap.put.implementation = function (a,b){
// 当 App 执行 map.put("username", value) 时,if (a === "username") 条件成立,触发调用栈打印。
if (a === "username"){
// 查看调用栈
showStacks()
}
console.log('hook_hashMap输出-->',a,b)
return this.put(a,b)
}
}
Java.perform(function() {
hook_hashMap()
});
打印日志信息:
java.lang.Throwable
at java.util.HashMap.put(Native Method)
at com.dodonew.online.ui.LoginActivity.login(LoginActivity.java:127)
at com.dodonew.online.ui.LoginActivity.onClick(LoginActivity.java:103)
at android.view.View.performClick(View.java:6597)
at android.view.View.performClickInternal(View.java:6574)
at android.view.View.access$3100(View.java:778)
at android.view.View$PerformClick.run(View.java:25885)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6718)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
a: username b: 123354
通过com.dodonew.online.ui.LoginActivity.login(LoginActivity.java:127)可以定位到hashMap调用的入口函数,从而得到关键信息代码
混淆并自定义HashMap 方法
场景描述
某 App 在提交请求时,使用自定义的 MyHashMap 类存储参数,但该类被混淆为 a.a.b.c。目标是通过动态分析找到该类的真实名称,并 Hook 其 put() 方法以捕获参数。
步骤 1:枚举所有已加载的类
使用 Frida 的 Java.enumerateLoadedClasses() 遍历当前内存中所有类,筛选出可能实现 Map 接口的候选类。
Java.perform(function() {
Java.enumerateLoadedClasses({
onMatch: function(className) {
// 筛选包名包含应用专属前缀的类(如 com.example.xxx)
if (className.startsWith("com.example")) {
console.log("[候选类] ", className);
}
},
onComplete: function() {}
});
});
输出示例:
[候选类] com.example.a.a.b.c
[候选类] com.example.d.e.f.g
...
步骤 2:分析类的继承关系和方法签名
对候选类进行深度检查,判断其是否继承或实现了 Map 接口,并验证是否包含 put() 方法
function analyzeClass(targetClass) {
Java.perform(function() {
var clazz = Java.use(targetClass);
// 检查是否实现 java.util.Map 接口
var interfaces = clazz.class.getInterfaces(); // 获取当前类直接实现的所有接口(不包含父类实现的接口)
interfaces.forEach(function(intf) {
if (intf.getName().contains("java.util.Map")) {
console.log("[实现Map接口] ", targetClass);
}
});
// 检查是否存在 put(Object, Object) 方法
var methods = clazz.class.getDeclaredMethods(); // 获取当前类声明的所有方法(包括 private/protected 方法,但不包含继承的方法)
methods.forEach(function(method) {
if (method.getName() === "put" &&
method.getParameterTypes().length === 2) {
console.log("[包含put方法] ", targetClass);
}
});
});
}
// 调用示例
analyzeClass("com.example.a.a.b.c");
输出关键信息:
[实现Map接口] com.example.a.a.b.c
[包含put方法] com.example.a.a.b.c
步骤 3:动态 Hook 候选类的 put() 方法
对筛选出的类进行动态 Hook,观察参数传递和调用栈。
function hookCandidateClass(className) {
Java.perform(function() {
var targetClass = Java.use(className);
targetClass.put.implementation = function(key, value) {
console.log(`[${className}] put被调用: key=${key}, value=${value}`);
// 打印调用栈
var stackTrace = Java.use("android.util.Log").getStackTraceString(
Java.use("java.lang.Throwable").$new()
);
console.log(stackTrace);
return this.put(key, value);
};
});
}
// 调用示例
hookCandidateClass("com.example.a.a.b.c");
输出示例
[com.example.a.a.b.c] put被调用: key=sign, value=5c8d7a3b
android.util.Log.getStackTraceString(Native Method)
com.example.a.a.b.c.put(Native Method)
com.example.network.RequestBuilder.buildParams(RequestBuilder.java:20)
...
步骤 4:验证目标类
通过以下特征确认目标类:
- 参数内容匹配:key 为业务关键词(如 sign、token)。
- 调用栈合理:堆栈显示参数构建逻辑(如 RequestBuilder.buildParams())。
- 高频调用:仅在关键操作(如网络请求)时触发。
步骤 5:静态分析辅助验证(可选)
若动态分析结果模糊,可结合 Jadx/Ghidra 反编译 APK:
- 搜索 put( ) 方法调用,定位参数构建代码。
- 查找交叉引用,确认调用链中的类名。
- 对比动态分析结果,确认最终目标类。