白乐天

道阻且长,行则将至。

Frida逆向知识点总结

Java层hook

hook方法

  • Java.perform: 用于在 Java 虚拟机中运行 Hook 代码。

  • Java.use: 获取目标类的引用。

  • implementation: hook 方法实现。

  • Overloaded methods: 对于存在重载的方法,需要使用 .overload() 指定参数类型。

hook构造方法

Java 构造方法的名称固定为 $init,因此需要 hook $init 方法。

获取函数的参数和返回值

可以直接通过 implementation 中的参数获取函数的入参。

调用原始方法,返回值由原始方法的调用结果提供。

修改函数参数和返回值

在 Hook 的方法实现中,直接赋值给参数变量即可改变传入参数。

调用目标方法后,直接更改返回值,并返回新的结果给调用方。

主动调用静态方法和非静态方法

静态方法调用
直接通过类名调用静态方法,无需实例化对象。

非静态方法调用
必须先创建或获取目标类的实例对象,然后通过实例对象调用非静态方法。(可以通过Java.choose()获取实例)

获取静态变量和成员变量

通过.value属性来访问变量的实际值。

静态变量
静态变量属于类本身,直接通过类访问。

非静态变量
非静态变量属于类的实例,必须通过实例对象来访问。

当变量的名字和类的方法名相同是,需要在变量的名字前面加上一个下划线_加以区分。

hook内部类

外部类与内部类之间通过$符号分隔。

hook匿名类

匿名类没有直接的名称,通常包含外部类名并以$数字结尾。

hook动态加载dex

枚举所有类加载器
遍历 ClassLoader 的实例,列出每个类加载器加载的类。

判断类加载器是否包含目标类
检查类加载器中是否可以加载目标类。

修改类加载器行为
在找到目标类的类加载器后,动态 Hook 目标类的方法或属性。

确保动态加载支持
针对动态加载的类文件(如由 DexClassLoader 加载),在加载完成后再进行 Hook。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Java.perform(function () {
console.log("[*] Starting enumeration of class loaders...");

Java.enumerateClassLoaders({
onMatch: function (loader) {
console.log("[*] Found ClassLoader: " + loader);

try {
// 尝试加载目标类
var targetClass = loader.loadClass('com.example.target.MyClass');

console.log("[*] Found target class in loader: " + loader);

// Hook 目标类的方法
Java.perform(function () {
var MyClass = Java.use('com.example.target.MyClass');

MyClass.myMethod.implementation = function () {
console.log("[*] Hooked myMethod of MyClass!");
return this.myMethod(); // 调用原始方法
};
});

} catch (e) {
console.log("[!] Target class not found in this loader.");
}
},
onComplete: function () {
console.log("[*] Completed enumeration of class loaders.");
}
});
});

image

通过spawn方式启动app

1
frida -U -f <packageName> -l hook.js

hook系统函数

image

frida主动加载dex

1
2
3
4
5
6
7
8
function hook(){
Java.perform(function () {
var ddexfile = Java.openClassFile("/data/local/tmp/ddex.dex");
ddexfile.load();
var ddex = Java.use("com.example.demo.ddex")
console.log("ddex:",ddex)
})
}

frida注册接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Java.perform(function () {
console.log("Registering MyCallback implementation...");

// 模拟接口类的实现
var MyCallbackImpl = Java.registerClass({
name: 'com.example.MyCallbackImpl',
implements: [Java.use('com.example.MyCallback')], // 实现的接口
methods: {
onEvent: function (message) {
console.log("onEvent called with message: " + message);
}
}
});

console.log("MyCallback implementation registered!");

// 将模拟的实现注入目标应用逻辑
var myObject = Java.use('com.example.SomeClass');
myObject.setCallback(MyCallbackImpl.$new());
console.log("Callback set successfully!");
});

frida打印调用栈

image

frida复杂参数打印

对于某些复杂参数类型,如数组或者对象,可能打印出来的结果是[Object object]。

  • Java层的对象

    可以通过调用frida的toString()方法,或者调用Java对象的toString()方法,如果对象没有实现toString()方法,可以使用JSON.stringify(arg)方式。

  • 数组类型

    手动迭代数组,或者将数组转化为字符串

  • JSON对象

    使用JSON.stringify(arg)

Native层hook

hook RegisterNatives

通过 JNI 动态注册的函数不会直接导出到符号表中,而是通过 JNIEnv->RegisterNatives 注册到 JVM 中。

hook RegisterNatives 函数的调用可以获取注册信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Interceptor.attach(Module.findExportByName(null, "RegisterNatives"), {
onEnter: function (args) {
// 打印注册信息
var env = args[0]; // JNIEnv
var jclass = args[1]; // Java 类
var methods = args[2]; // 函数注册表
var method_count = args[3].toInt32(); // 函数数量

console.log("RegisterNatives called:");
console.log("Number of methods: " + method_count);

// 遍历注册的每个函数
for (var i = 0; i < method_count; i++) {
var method = methods.add(i * Process.pointerSize * 3); // 每个方法结构体的偏移量
var name = method.readPointer().readUtf8String(); // 函数名
var signature = method.add(Process.pointerSize).readPointer().readUtf8String(); // 函数签名
var fnPtr = method.add(Process.pointerSize * 2).readPointer(); // Native 函数指针

console.log("Method " + i + ": Name = " + name + ", Signature = " + signature + ", Function Address = " + fnPtr);
}
},
onLeave: function (retval) {
console.log("RegisterNatives finished.");
}
});

hook Native层函数

hook Native层函数首先定位目标函数所在的动态库及其导出符号地址。

使用Interceptor.attach()对函数进行hook。

打印Native层的调用栈

1
console.log(Thread.backtrace(this.contex,Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n')+'\n')

通过frida读写文件

通过frida调用C函数

打印内存地址的值

1
console.log(hexdump(ptr))