1.什么是Frida
Frida 是一款开源的动态插桩工具,可以插入一些代码到原生App的内存空间去动态地监视和修改其行为,支持Windows、Mac、Linux、Android或者iOS,从安卓层面来讲,可以实现Java层和Native层Hook操作。
项目地址
官网及使用文档
2.Frida原理及重要组件
rida注入的原理就是找到目标进程,使用ptrace跟踪目标进程获取mmap,dlpoen,dlsym等函数库的偏移获取mmap在目标进程申请一段内存空间将在目标进程中找到存放frida-agent-32/64.so的空间启动执行各种操作由agent去实现
| 组件名称 | 功能描述 |
|---|---|
| frida-gum | 提供了inline-hook的核心实现,还包含了代码跟踪模块Stalker,用于内存访问监控的MemoryAccessMonitor,以及符号查找、栈回溯实现、内存扫描、动态代码生成和重定位等功能 |
| frida-core | fridahook的核心,具有进程注入、进程间通信、会话管理、脚本生命周期管理等功能,屏蔽部分底层的实现细节并给最终用户提供开箱即用的操作接口。包含了frida-server、frida-gadget、frida-agent、frida-helper、frida-inject等关键模块和组件,以及之间的互相通信底座 |
| frida-gadget | 本身是一个动态库,可以通过重打包修改动态库的依赖或者修改smali代码去实现向三方应用注入gadget,从而实现Frida的持久化或免root |
| frida-server | 本质上是一个二进制文件,类似于前面学习到的android_server,需要在目标设备上运行并转发端口,在Frida hook中起到关键作用 |
两种启动方式
| 方式名称 | 方式说明 | CLI下启动方式 |
|---|---|---|
| spawn | 将启动APP的权利交由Frida来控制。不管APP是否启动,都会重新启动APP。 | -f 参数指定包名 |
| attach | 建立在目标APP已经启动的情况下,Frida通过 ptrace 注入程序从而执行Hook的操作 |
不加 -f 参数 |
3.Frida与Xposed的对比
| 工具 | 优点 | 缺点 |
|---|---|---|
| Xposed | 直接编写Java代码,Java层hook方便,可打包模块持久化hook | 环境配置繁琐,兼容性较差,难以Hook底层代码。 |
| Frida | 配置简单,免重启hook。支持Java层和Native层的hook操作 | 持久化hook相对麻烦 |
4.Frida环境配置
pip install frida==16.3.1
pip install frida-tools
adb push
Frida 12.0.0到12.1.0 对应 Frida-tools 1.0.0到1.2.2
Frida 12.3.0到12.4.0 对应 Frida-tools 1.3.0到1.3.2
Frida 12.5.3到12.6.21 对应 Frida-tools 2.0.0到4.1.0
Frida 12.7.3到12.8.12 对应 Frida-tools 5.0.1到7.2.2
Frida 12.10.4到13.0.0 对应 Frida-tools 8.0.0到8.2.0
Frida 14.0.0到15.0.0 对应 Frida-tools 9.0.0到9.2.5
Frida 15.0.0到16.0.0 对应 Frida-tools 10.0.0到10.8.0
Frida 16.0.0到17.0.0 对应 Frida-tools 12.0.0到12.3.0
5.Frida常用Api
| API名称 | 描述 |
|---|---|
Java.use(className) |
获取指定的Java类并使其在JavaScript代码中可用。 |
Java.perform(callback) |
确保回调函数在Java的主线程上执行。 |
Java.choose(className, callbacks) |
枚举指定类的所有实例。 |
Java.cast(obj, cls) |
将一个Java对象转换成另一个Java类的实例。 |
Java.enumerateLoadedClasses(callbacks) |
枚举进程中已经加载的所有Java类。 |
Java.enumerateClassLoaders(callbacks) |
枚举进程中存在的所有Java类加载器。 |
Java.enumerateMethods(targetClassMethod) |
枚举指定类的所有方法。 |
Hook普通方法,打印参数和修改返回值
hook这个a方法

function hookTest1(){
var utils = Java.use("com.zj.wuaipojie.Demo");
utils.a.overload("java.lang.String").implementation = function(str){
// a = 123;
// b = 456;
//接收返回值
var retval = this.a(str);
console.log(str,retval);
return retval;
}
}
function main(){
Java.perform(function(){
hookTest1()
});
}
setImmediate(main);
Hook重载函数
使用overload(“java.lang.String”),指定参数类型
涉及到复杂参数例如如下图

需要看对应的smali代码

utils.Inner.overload('com.zj.wuaipojie.Demo$Animal','java.lang.String').implementation
function hookTest1(){
var utils = Java.use("com.zj.wuaipojie.Demo");
utils.Inner.overload("com.zj.wuaipojie.Demo$Animal","java.lang.String").implementation = function(Animal,str){
// a = 123;
// b = 456;
//接收返回值
var retval = this.Inner(Animal,str);
console.log(Animal,str);
// return retval;
}
}
function main(){
Java.perform(function(){
hookTest1()
});
}
setImmediate(main);
Hook构造函数
function hookTest1(){
var utils = Java.use("com.zj.wuaipojie.Demo");
utils.$init.overload("java.lang.String").implementation = function(str){
str='hooked by frida'
var retval = this.$init(str);
console.log(str);
// return retval;
}
}
function main(){
Java.perform(function(){
hookTest1()
});
}
setImmediate(main);
Hook字段
修改静态变量
function hookTest1(){
var utils = Java.use("com.zj.wuaipojie.Demo");
utils.staticField.value = "我是被修改的静态变量";
console.log(utils.staticField.value);
}
修改非静态变量
function hookTest1(){
var utils = Java.use("com.zj.wuaipojie.Demo");
Java.choose("com.zj.wuaipojie.Demo",{
onMatch: function (obj) {
// obj._privateInt.value = "123456"; //字段名与函数名相同 前面加个下划线
obj.privateInt.value = 9999;
console.log("hookTest1 obj: ", obj.privateInt.value);
},
onComplete: function () {
}
})
}
function main(){
Java.perform(function(){
hookTest1()
});
}
setImmediate(main);
Hook内部类
function hookTest6(){
Java.perform(function(){
//内部类
var innerClass = Java.use("com.zj.wuaipojie.Demo$innerClass");
console.log(innerClass);
innerClass.$init.implementation = function(){
console.log("eeeeeeee");
}
});
}
构造数组
Log.d("SimpleArraysToString", Arrays.toString(arr[i]));
替换整行
var charArray = Java.array('char',['一','二','三','四','五'])
var result = this.toString(charArray);
强制类型转换
当前有两个类
Water和Juice,Juice继承于Water
public class Water {
public static String flow(Water W) { // 水 的方法
// SomeSentence
Log.d("2Object", "water flow: I`m flowing");
return "water flow: I`m flowing";
}
public String still(Water W) { // 水 的方法
// SomeSentence
Log.d("2Object", "water still: still water runs deep!");
return "water still: still water runs deep!";
}
}
public class Juice extends Water { // 果汁 类 继承了水类
public String fillEnergy(){
Log.d("2Object", "Juice: i`m fillingEnergy!");
return "Juice: i`m fillingEnergy!";
}
public static void main() {
Water w1 = new Water();
flow(w1) ; //
Juice J = new Juice(); // 实例化果汁类对象
flow(J) ; // 调用水的方法 向上转型 J → W
Water w2 = new Juice();
((Juice) w2).fillEnergy();
}
}
类型转换代码 子类可转父类
function hookTest(){
let juiceHandle = null;
Java.choose("com.example.lesson5.Juice",{
onMatch: function (instance) {
console.log("instance:",instance);
console.log("call fillEnergy ",instance.fillEnergy());
console.log("Juice still",instance.still(instance))
juiceHandle = instance;
},
onComplete: function () {
console.log("completed")
setTimeout(() => {
if (juiceHandle !== null) {
let waterHandle = Java.cast(juiceHandle, Java.use("com.example.lesson5.Water"));
console.log("call still", waterHandle.still(waterHandle));
} else {
console.log("No Juice instance found, skipping Java.cast");
}
}, 100); // 等待 100ms
},
})
}
核心
let waterHandle = Java.cast(juiceHandle, Java.use("com.example.lesson5.Water"));
实现类
当前有如下类,继承Water,实现liquid接口
public class Milk extends Water implements liquid{
@Override
public String flow(int time) {
return "flowing"+time;
}
}
现在在js中实现一个Beer类
function hookTest(){
const Water = Java.use('com.example.lesson5.Water');
const liquid = Java.use('com.example.lesson5.liquid');
const Beer = Java.registerClass({
name: 'com.example.lesson5.Peer',
superClass:Water,
implements: [liquid],
methods: {
flow: function (time) {
let result = "Beer flowing"+time;
return Java.use("java.lang.String").$new(result);
}
}
});
let BeerInstance = Beer.$new();
console.log("beer flow", BeerInstance.flow(3));
console.log("beer-water-still",BeerInstance.still(BeerInstance))
}

枚举所有的类与类的所有方法
function hookTest7(){
Java.perform(function(){
//枚举所有的类与类的所有方法,异步枚举
Java.enumerateLoadedClasses({
onMatch: function(name,handle){
//过滤类名
if(name.indexOf("com.zj.wuaipojie.Demo") !=-1){
console.log(name);
var clazz =Java.use(name);
console.log(clazz);
var methods = clazz.class.getDeclaredMethods();
console.log(methods);
}
},
onComplete: function(){}
})
})
}
枚举所有方法
function hookTest8(){
Java.perform(function(){
var Demo = Java.use("com.zj.wuaipojie.Demo");
//getDeclaredMethods枚举所有方法
var methods =Demo.class.getDeclaredMethods();
for(var j=0; j < methods.length; j++){
var methodName = methods[j].getName();
console.log(methodName);
for(var k=0; k<Demo[methodName].overloads.length;k++){
Demo[methodName].overloads[k].implementation = function(){
for(var i=0;i<arguments.length;i++){
console.log(arguments[i]);
}
return this[methodName].apply(this,arguments);
}
}
}
})
}
主动调用
静态方法
var ClassName=Java.use("com.zj.wuaipojie.Demo");
ClassName.privateFunc("传参");
非静态
var ret = null;
Java.perform(function () {
Java.choose("com.zj.wuaipojie.Demo",{ //要hook的类
onMatch:function(instance){
ret=instance.privateFunc("aaaaaaa"); //要hook的方法
},
onComplete:function(){
//console.log("result: " + ret);
}
});
})
//return ret;
打印日志
adb shell
logcat | grep "D.zj2595"
打印调用栈
console.log(Java.user("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
打印map
Map<String, String> mapr0ysue = new HashMap<>(); // 创建Map集合对象
mapr0ysue.put("ISBN 978-7-5677-8742-1", "Android项目开发实战入门"); // 向Map集合中添加元素
mapr0ysue.put("ISBN 978-7-5677-8741-4", "C语言项目开发实战入门");
mapr0ysue.put("ISBN 978-7-5677-9097-1", "PHP项目开发实战入门");
mapr0ysue.put("ISBN 978-7-5677-8740-7", "Java项目开发实战入门");
Log.d("5map", "key值toString"+mapr0ysue.toString());
frida打印
function hookTest(){
Java.choose("java.util.HashMap",{
onMatch: function (instance) {
if (instance.toString().indexOf("ISBN") !== -1) {
console.log("found instance", instance);
console.log("hashMap result ", instance.toString());
}
},
onComplete: function () {
console.log("completed");
},
});
}
frida-Hook
function hookTest(){
Java.use("java.util.HashMap").put.implementation = function (x, y) {
var result = this.put(x, y);
if (x.toString().indexOf("ISBN")!== -1) {
console.log("x,y,result", x.toString(), y.toString(), result);
}
return result;
};
}

生成字符串
Java.use('java.lang.String').$new("NIHAO");
6.objection
objection是基于frida的命令行hook集合工具, 可以让你不写代码, 敲几句命令就可以对java函数的高颗粒度hook, 还支持RPC调用。可以实现诸如内存搜索、类和模块搜索、方法hook打印参数返回值调用栈等常用功能,是一个非常方便的,逆向必备、内存漫游神器。
6.1 环境安装
pip install frida==15.2.2
pip install frida-tools==10.3.0
pip3 install -U objection
#启动frida,查找包名
frida-ps -Uia
#启动Objection
objection -g <包名> explore
● 指定IP端口
objection -N -h <手机ip地址> -p <端口> -g <包名> explore
● spawn 启动前 hook
objection -N -h 192.168.1.3 -p 9999 -g 包名 explore --startup-command "android hooking watch class '包名.类名'"
● spawn 启动前 Hook 并打印参数、返回值、函数调用栈
objection -N -h 192.168.1.3 -p 9999 -g 包名 explore --startup-command "android hooking watch class_method '包名.类名.方法' --dump-args --dump-return --dump-backtrace"
6.2 内存漫游
hook:objection -g com.android.settings explore

6.2.1 提取内存信息
查看内存中加载的库:memory list modules

查看库的导出函数:memory list exports libbase.so

保存结果到json,memory list exports libbase.so --json ./libbase.json
6.2.2 内存堆搜索与执行
在堆上搜索实例
查看AOSP源码关于设置里显示系统设置的部分,发现存在着DisplaySettings类。

android heap search instances com.android.settings.DisplaySettings

调用实例方法
在DisplaySettings.java中有如下方法

调用该方法:android heap execute 7019706 getMetricsCategory

在实例上执行js代码
android heap evaluate 7019706进入编辑器环境
输入console.log("evaluate result:"+clazz.getMetricsCategory()),按ESC退出编辑器,然后按回车,即会开始执行这串脚本,输出结果

6.2.3 启动activity或service
直接启动activity
进入显示设置android intent launch_activity com.android.settings.DisplaySettings

查看当前可用的activity
android hooking list activities

直接启动service
android hooking list services查看可供开启的服务
android intent launch_service com.android.settings.bluetooth.BluetoothPairingService命令来开启服务
6.3 hook
hook类的所有方法
android hooking watch class android.bluetooth.BluetoothDevice

使用蓝牙功能会跟踪到调用的方法

hook方法的参数、返回值和调用栈
android hooking watch class_method android.bluetooth.BluetoothDevice.getName --dump-args --dump-return --dump-backtrace

最后加上的三个选项--dump-args --dump-return --dump-backtrace,为我们成功打印出来了我们想要看的信息,其实返回值Return Value就是getName()方法的返回值,我的蓝牙耳机的型号名字OnePlus Bullets Wireless 2;从调用栈可以反查如何一步一步调用到getName()这个方法的
hook 类的构造方法
android hooking watch class_method com.android.settings.bluetooth.BluetoothPairingService.$init
hook 方法的所有重载
android hooking watch class_method 类名.方法名
6.4 Wallbreaker
安装和加载
git clone https://github.com/hluwa/Wallbreaker
#加载插件
#方法1
objection -g com.example.androiddemo explore -P ~/.objection/plugins/Wallbreaker(插件路径)
#方法2
objection -g com.example.androiddemo explore
plugin load ~/.objection/plugins/Wallbreaker(插件路径)
windows注意路径:plugin load F:/Penetration/android/objection/Wallbreaker
搜索类
` plugin wallbreaker classdump –fullname com.android.settings.bluetooth.BluetoothPairingService `

6.4 FRIDA-DEXDump
git clone https://github.com/hluwa/frida-dexdump
plugin load F:/Penetration/android/objection/frida-dexdump
or
pip3 install frida-dexdump
打开待脱壳的App

获取包名com.cz.babySister

frida-dexdump -U -f com.cz.babySister
合并dex
import os
import zipfile
import argparse
def rename_class(path):
files = os.listdir(path)
dex_index = 0
if path.endswith('/'):
path = path[:-1]
print(path)
for i in range(len(files)):
if files[i].endswith('.dex'):
old_name = path + '/' + files[i]
if dex_index == 0:
new_name = path + '/' + 'classes.dex'
else:
new_name = path + '/' + 'classes%d.dex' % dex_index
dex_index += 1
if os.path.exists(new_name):
continue
os.rename(old_name, new_name)
print('[*] 重命名完毕')
def extract_META_INF_from_apk(apk_path, target_path):
r = zipfile.is_zipfile(apk_path)
if r:
fz = zipfile.ZipFile(apk_path, 'r')
for file in fz.namelist():
if file.startswith('META-INF'):
fz.extract(file, target_path)
else:
print('[-] %s 不是一个APK文件' % apk_path)
def zip_dir(dirname, zipfilename):
filelist = []
if os.path.isfile(dirname):
if dirname.endswith('.dex'):
filelist.append(dirname)
else:
for root, dirs, files in os.walk(dirname):
for dir in dirs:
# if dir == 'META-INF':
# print('dir:', os.path.join(root, dir))
filelist.append(os.path.join(root, dir))
for name in files:
# print('file:', os.path.join(root, name))
filelist.append(os.path.join(root, name))
z = zipfile.ZipFile(zipfilename, 'w', zipfile.ZIP_DEFLATED)
for tar in filelist:
arcname = tar[len(dirname):]
if ('META-INF' in arcname or arcname.endswith('.dex')) and '.DS_Store' not in arcname:
# print(tar + " -->rar: " + arcname)
z.write(tar, arcname)
print('[*] APK打包成功,你可以拖入APK进行分析啦!')
z.close()
if __name__ == '__main__':
args = {
'dex_path': '脱壳后dex路径',
'apk_path': '原始带壳apk路径',
'output': '脱壳后apk路径'
}
rename_class(args['dex_path'])
extract_META_INF_from_apk(args['apk_path'], args['dex_path'])
zip_dir(args['dex_path'], args['output'])
6.4 zentrace
pip install PyQt5
git clone https://github.com/hluwa/ZenTracer
cd ZenTracer
python ZenTracer.py
action->Match RegEx,输入要跟踪的包名


点击start

6.5 r0trace
frida -U 大 -l r0tracer.js -o Log.txt
7.frida-rpc
7.1 远程调用
已此安卓app为例
package com.example.lees4;
import android.os.Bundle;
import com.google.android.material.snackbar.Snackbar;
import androidx.appcompat.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;
import com.example.lees4.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
private static String total = "@@@@####@@@@";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int m = fun(50,80);
Log.d("kanxue m = ", String.valueOf(m));
Log.d("kanxue tolowercase", fun("LOWERCASEME!"));
}
}
String fun(String x ){
total +=x ;
return x.toLowerCase();
}
int fun(int x ,int y){
Log.d("kanxue", String.valueOf((x+y)));
return x+y;
}
String secret(){
return total;
}
public static String secret2(){
return total;
}
}
hook.js
function invoke(){
Java.perform(function (){
Java.choose("com.example.lees4.MainActivity",{
onMatch:function(instance){
console.log("found instance ",instance);
console.log("invoke instance.secret ",instance.secret());
},onComplete:function(){console.log("search completed !")}
})
})
}
rpc.exports = {
invokefunc:invoke
};
rpc.py
# 错误处理
import frida
def my_message_handler(message, payload):
print(message)
print(payload)
device = frida.get_usb_device()
session = device.attach("lees4")
with open("hook.js") as f:
script = session.create_script(f.read())
script.on("message", my_message_handler)
script.load()
command = " "
while True:
command = input("input command:")
if command == "2":
script.exports_sync.invokefunc()
7.2 多端混连
以非标准端口启动fr
./fr16 -l 0.0.0.0:6666
验证是否连接成功
device = frida.get_device_manager().add_remote_device('192.168.0.100:6666')
print(device.enumerate_applications())
7.3 动态修改
示例app如下
package com.example.myapplication;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.util.Base64;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
EditText username_et;
EditText password_et;
TextView message_tv;
@SuppressLint("MissingInflatedId")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
password_et = (EditText) this.findViewById(R.id.editText2);
username_et = (EditText) this.findViewById(R.id.editText);
message_tv = ((TextView) findViewById(R.id.textView));
this.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (username_et.getText().toString().compareTo("admin") == 0) {
message_tv.setText("You cannot login as admin");
return;
}
//hook target
message_tv.setText("Sending to the server :" + Base64.encodeToString((username_et.getText().toString() + ":" + password_et.getText().toString()).getBytes(), Base64.DEFAULT));
}
});
}
}

目的是绕过代码中对admin的检查,将账号admin,以及对应密码发到server,需要hook的函数为setText
首先要确定setText用的是哪个重载

android.widget.TextView.setText(java.lang.CharSequence)
进行hook
Java.perform(function (){
Java.use("android.widget.TextView").setText.overload("java.lang.CharSequence").implementation =
function (x){
let recv_msg ;
let send_msg = x.toString();
send(send_msg);
recv_msg(function (data){
recv_msg = data.res;
}).wait();
let Java_recv_msg = Java.use("java.lang.String").$new(recv_msg);
let result = this.setText(Java_recv_msg);
return result;
}
})
py
# 错误处理
import base64
import frida
def my_message_handler(message, payload):
print(message)
print(payload)
if message["type"] == "send":
print(message["payload"])
data = message["payload"].split(":")[1].strip()
data = str(base64.b64decode(data))
print(data)
usr, pw = data.split(":")
print('pw', pw)
data = str(base64.b64encode(("admin" + ":" + pw).encode()))
print("encode data", data)
script.post({"res": data})
print("modified send")
device = frida.get_usb_device()
session = device.attach("My Application")
with open("hook.js") as f:
# 读取 JavaScript 脚本 lesson7sec.js
script = session.create_script(f.read())
# 注册 my_message_handler 作为消息处理函数,并加载 Frida 脚本。
script.on("message", my_message_handler)
script.load()
input()
8.综合案例
拿到app,界面如下

8.1 登录
问题很好解决,直接r0trace一把梭

b5224c158c901c0d730ff02bf5ab721363099fcac5af372f6724790134cb6e25
从静态代码分析也可以得知,a(obj,0bj)返回的结果和输入的密码一致才进入下一关,那么trace得到的返回值就是密码。

8.2 第一关
点击进入下一关会返回check failed

trace调用栈如下

这里的a方法返回值如下,如静态代码中的密钥不一致


所以hook该方法,修改返回值
let FridaActivity1 = Java.use("com.example.androiddemo.Activity.FridaActivity1");
FridaActivity1["a"].implementation = function (bArr) {
console.log(`FridaActivity1.a is called: bArr=${bArr}`);
let result = this["a"](bArr);
console.log(`FridaActivity1.a result=${result}`);
return "R4jSLLLLLLLLLLOrLE7/5B+Z6fsl65yj6BgC6YWz66gO6g2t65Pk6a+P65NK44NNROl0wNOLLLL=";
};

8.3 第二关

从代码得知,一开始static_bool_var和bool_var都是false,需要调用对应的set方法才可以
let FridaActivity2 = Java.use("com.example.androiddemo.Activity.FridaActivity2");
let static_bool_var = FridaActivity2.static_bool_var.value;
console.log(`static_bool_var=${static_bool_var}`);
FridaActivity2.setStatic_bool_var();
Java.choose("com.example.androiddemo.Activity.FridaActivity2",{
onMatch: function (instance) {
let bool_var = instance.bool_var.value;
console.log(`bool_var=${bool_var}`);
instance.setBool_var();
},
onComplete: function () {
console.log("completed")
},
})

8.4 第三关

与上一关不同的是没有修改变量的方法,需要我们自己修改

如果变量名与函数同名需要加_
function hookTest3(){
let FridaActivity3 = Java.use("com.example.androiddemo.Activity.FridaActivity3");
let static_bool_var = FridaActivity3.static_bool_var.value;
console.log(`static_bool_var=${static_bool_var}`);
FridaActivity3.static_bool_var.value = true;
Java.choose("com.example.androiddemo.Activity.FridaActivity3",{
onMatch: function (instance) {
console.log(`bool_var=${instance.bool_var.value}`);
// 如果变量名与函数同名需要加_
console.log(`_same_name_bool_var=${instance._same_name_bool_var.value}`);
instance.bool_var.value = true;
instance._same_name_bool_var.value = true
},
onComplete: function () {
console.log("completed")
},
})
}
8.5 第四关

从静态代码看需要Hook内部类

function hookTest4(){
let InnerClasses = Java.use("com.example.androiddemo.Activity.FridaActivity4$InnerClasses");
InnerClasses.check1.implementation = function () {
return true;
};
InnerClasses.check2.implementation = function () {
return true;
};
InnerClasses.check3.implementation = function () {
return true;
};
InnerClasses.check4.implementation = function () {
return true;
};
InnerClasses.check5.implementation = function () {
return true;
};
InnerClasses.check6.implementation = function () {
return true;
};
}
8.6 第五关

首先getDynamicDexCheck!=null,然后check()为true
CheckInterface接口相关代码如下
public interface CheckInterface {
boolean check();
}
实现
private CheckInterface DynamicDexCheck = null;
public CheckInterface getDynamicDexCheck() {
if (this.DynamicDexCheck == null) {
loaddex();
}
return this.DynamicDexCheck;
}
声明一个变量,调用getDynamicDexCheck进行实例化,首先使用loaddex()加载dex文件,如果文件不存在,则调用copyFiles,将写好的汇编代码放入文件中,然后利用反射获取实例。
private void loaddex() {
File filesDir = getFilesDir();
if (!filesDir.exists()) {
filesDir.mkdir();
}
String str = filesDir.getAbsolutePath() + File.separator + "DynamicPlugin.dex";
File file = new File(str);
try {
if (!file.exists()) {
file.createNewFile();
copyFiles(this, "DynamicPlugin.dex", file);
}
} catch (IOException e) {
e.printStackTrace();
}
try {
this.DynamicDexCheck = (CheckInterface) new DexClassLoader(str, filesDir.getAbsolutePath(), null, getClassLoader()).loadClass("com.example.androiddemo.Dynamic.DynamicCheck").newInstance();
if (this.DynamicDexCheck == null) {
Toast.makeText(this, "loaddex Failed!", 1).show();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
其加载的类如下
package com.example.androiddemo.Dynamic;
/* loaded from: assets/DynamicPlugin.dex */
public class DynamicCheck implements CheckInterface {
@Override // com.example.androiddemo.Dynamic.CheckInterface
public boolean check() {
return false;
}
}
这里涉及一个接口多态的知识:接口本身不能直接实例化,但可以通过实现接口的类来创建对象,然后用接口类型的变量引用这个对象。在这段代码中使用接口多态而不是具体类,这样支持动态加载。
那么这里通关的核心就是hook到动态加载的类的check方法,修改返回值为true
function hookTest5(){
Java.enumerateClassLoaders({
onMatch: function (loader) {
try {
// noinspection JSConstantReassignment
if (loader.findClass("com.example.androiddemo.Dynamic.DynamicCheck")) {
Java.classFactory.loader = loader;
console.log("this loader",loader)
}
} catch (e){
console.log(e);
}
}, onComplete: function () {},
})
let app = Java.use("com.example.androiddemo.Dynamic.DynamicCheck");
console.log("class", app);
app.check.implementation = function () {
let result = this.check();
console.log(result);
return true;
};
}
Java.enumerateClassLoaders()用于遍历所有的类加载器,尝试找到能够加载com.example.androiddemo.Dynamic.DynamicCheck这个类的加载器。- 对于每个加载器
loader,尝试使用loader.findClass("com.example.androiddemo.Dynamic.DynamicCheck")查找目标类。 - 修改 Frida 的类加载器:
Java.classFactory.loader = loader,使用这个loader来加载 Java 类,否则可能找不到目标类
8.7 第六关


这关很简单,Hook类直接调用静态方法即可
function hootTest6(){
for (let i = 0; i < 3; i++) {
Java.use("com.example.androiddemo.Activity.Frida6.Frida6Class"+i).check.implementation = function (){return true};
}
}
但是还有一种方法,找已经加载好的类
//方法二
Java.enumerateLoadedClasses({
onMatch: function (name, handle) {
if (name.toString().indexOf("com.example.androiddemo.Activity.Frida6.Frida6")>=0) {
console.log("name", name);
Java.use(name).check.implementation = function (){return true};
}
},onComplete:function (){console.log("completed")}
})