frida安装配置及其使用方法
Frida安装
pip install frida-tools -i https://pypi.tuna.tsinghua.edu.cn/simplefrida --version
ADB(Android Debug Bridge) 环境搭建
https://googledownloads.cn/android/repository/platform-tools-latest-windows.zip
解压缩后配置环境变量
adb --version
逍遥模拟器虚拟设备设置
检测root权限是否开启
开启开发者模式并开启USB调试
-
打开设置
-
找到关于平板电脑
-
连续点击版本号
-
返回一下点击开发者选项
-
打开USB选项
开始搭建Frida环境
1. push fs(Frida-server) 文件至虚拟设备
逍遥模拟器的虚拟设备默认端口为 21503 每增加一个虚拟设备,其端口号“加10”,如第一个虚拟设备的端口是21503,那么第二个虚拟设备的端口就是21513
-
先终止一下ADB服务
adb kill-server
-
通过指令链接到逍遥模拟器
adb connect 127.0.0.1:21503
-
列出已知设备
adb devices
-
链接设备(注意!请确保已按照上文方法开启【USB调试】。如果显示已知设备不唯一,则可以通过 -s <设备> 参数指定设备,如:
adb -s 127.0.0.1:21503 shell
)adb shell获取设备架构信息(用于确认Frida-server版本)指令在 adb shell 成功后,在shell对话中输入 getprop ro.product.cpu.abi 即可获取设备架构信息获取架构信息后,再与安装的Frida版本结合,便可前去Github下载其Frida-server文件命名格式如下:frida-server-<Frida版本>-android-<设备架构版本>.xz
-
push fs(Frida-server)文件至
/data/local/tmp
-
先
exit
退出shell会话 -
再输入
adb push <Frida-server 文件绝对路径> /data/local/tmp
-
2. 给予 Frida-server 最高权限
实际操作
adb shellcd /data/local/tmpchmod 777 fs
3. 运行 Frida-server
验证Frida-server工作
-
新开启一个CMD
-
输入进行端口转发(一般不转发也没事)
adb forward tcp:27042 tcp:27042adb forward tcp:27043 tcp:27043adb shell am start -D -n com.example.app/.MainActivityadb jdwp 查看可以转发的端口
-
再输入
frida-ps -U
,令其列出所有进程
配置证书
按网上步骤即可,遇到不能上传执行下述命令
adb rootadb remount
常用Frida tools命令
frida-ls-devices 查看连接设备frida-ps -U 通过USB或者WIFI列出正在运行的进程frida-ps -Ua 列出正在运行的应用frida-ps -Uai 列出安装的应用frida-kill -D device-id pid
注入的两种方法
python脚本交互
import frida# 定义要注入的 JavaScript 脚本js_code = """console.log("[*] Script loaded successfully.");"""# 定义目标进程名称target_process = "medical"# 获取 USB 设备device = frida.get_usb_device()# 附加到目标进程session = device.attach(target_process)# 创建脚本并加载script = session.create_script(js_code)script.load()
js代码
// Hook 模板Java.perform(function () {// 查找目标类var TargetClass = Java.use("com.example.target.ClassName");// Hook 目标方法TargetClass.targetMethod.implementation = function (arg1, arg2) {console.log("[*] Hooked targetMethod called!");console.log(" arg1: " + arg1);console.log(" arg2: " + arg2);// 调用原始方法var result = this.targetMethod(arg1, arg2);console.log(" Original result: " + result);// 修改返回值var modifiedResult = "Modified Result";console.log(" Modified result: " + modifiedResult);return modifiedResult;};});
-
将此代码保存为 hook.js。
-
使用 Frida 命令行工具加载脚本:
frida -U -n <目标进程名> -s hook.jsfrida -l hook.js -U 目标名称
-
替换
com.example.target.ClassName
和targetMethod
为实际的目标类和方法名称。
// Hook Native 函数Interceptor.attach(Module.findExportByName("libtarget.so", "target_function"), {onEnter: function (args) {console.log("[*] Hooked target_function called!");console.log(" arg0: " + args[0].toInt32());console.log(" arg1: " + args[1].readUtf8String());},onLeave: function (retval) {console.log(" Original return value: " + retval.toInt32());// 修改返回值retval.replace(12345);console.log(" Modified return value: " + retval.toInt32());}});
-
onEnter在函数执行第一条指令前执行,可以读取或修改参数数据
-
onLeave在函数返回时执行,可以读取或修改函数的返回值
-
Module.findExportByName查找符号对应的函数地址
HOOK重载方法
1. 使用 .overload()
指定方法签名
Java.perform(function() {var targetClass = Java.use("com.example.ClassName");// Hook test(String)targetClass.test.overload('java.lang.String').implementation = function(s) {console.log("Called test(String): " + s);return this.test(s); // 调用原方法};// Hook test(int)targetClass.test.overload('int').implementation = function(i) {console.log("Called test(int): " + i);return this.test(i);};// Hook test(String, int)targetClass.test.overload('java.lang.String', 'int').implementation = function(s, i) {console.log("Called test(String, int): " + s + ", " + i);return this.test(s, i);};});
2. 获取所有重载方法
Java.perform(function() {var targetClass = Java.use("com.example.ClassName");// 获取方法的所有重载版本var overloads = targetClass.test.overloads;for (var i = 0; i < overloads.length; i++) {overloads[i].implementation = function() {console.log("test called with args: " + Array.prototype.slice.call(arguments));return this.test.apply(this, arguments); // 调用原方法};}});
HOOK对象参数
Java.perform(function() {// Hook一个方法并构造参数const TargetClass = Java.use('com.example.TargetClass');TargetClass.targetMethod.implementation = function(arg1, arg2) {// 构造新参数const newArg1 = Java.use('java.lang.String').$new('Hooked!');const newArg2 = Java.use('java.util.ArrayList').$new();newArg2.add(Java.use('java.lang.Integer').$new(123));// 调用原方法return this.targetMethod(newArg1, newArg2);};});
枚举所有类和类方法
Java.perform(function() {// 获取所有已加载的类var classes = Java.enumerateLoadedClassesSync();// 遍历所有类classes.forEach(function(className) {try {// 获取类引用var targetClass = Java.use(className);// 获取类所有方法var methods = targetClass.class.getDeclaredMethods();console.log("\n[+] Class: " + className);// 遍历类方法for (var i = 0; i < methods.length; i++) {var method = methods[i];var methodName = method.getName();var returnType = method.getReturnType().getName();var parameterTypes = method.getParameterTypes();// 构建参数类型字符串var params = [];for (var j = 0; j < parameterTypes.length; j++) {params.push(parameterTypes[j].getName());}console.log(" |- Method: " + returnType + " " + methodName + "(" + params.join(", ") + ")");// Hook 方法并修改对象属性hookMethod(targetClass, methodName, params);}} catch (e) {// 忽略无法访问的类// console.log("Error with class: " + className + " - " + e);}}); });function hookMethod(targetClass, methodName, paramTypes) {// 根据参数类型数量生成实现函数var overloads = targetClass[methodName].overloads;for (var i = 0; i < overloads.length; i++) {overloads[i].implementation = function() {// 打印调用信息console.log("\n*** Called: " + targetClass.$className + "." + methodName);// 打印参数for (var j = 0; j < arguments.length; j++) {var arg = arguments[j];console.log(" |- arg[" + j + "]: " + arg);// 如果是对象,尝试修改其属性//if (arg && arg.getClass) {//try {modifyObjectProperties(arg);//} catch (e) {console.log(" |- Failed to modify object properties: " + e);//}//}}// 调用原始方法return this[methodName].apply(this, arguments);};} }function modifyObjectProperties(obj) {// 获取对象类var objClass = obj.getClass();// 获取所有字段var fields = objClass.getDeclaredFields();for (var i = 0; i < fields.length; i++) {var field = fields[i];field.setAccessible(true);var fieldName = field.getName();var fieldType = field.getType().getName();var originalValue = field.get(obj);console.log(" |- Field: " + fieldType + " " + fieldName + " = " + originalValue);// 修改字符串字段if (fieldType === "java.lang.String" && originalValue !== null) {var newValue = originalValue + "_modified";field.set(obj, newValue);console.log(" | Changed to: " + newValue);}// 修改int字段if (fieldType === "int") {var newValue = originalValue + 1000;field.set(obj, newValue);console.log(" | Changed to: " + newValue);}// 修改boolean字段if (fieldType === "boolean") {var newValue = !originalValue;field.set(obj, newValue);console.log(" | Changed to: " + newValue);}} }
主动调用方法
Java.perform(function() {// 导入需要的Java类var RSA = Java.use("com.null.fridaapp.RSA"); // RSA加密类var String = Java.use("java.lang.String"); // Java字符串类var Base64 = Java.use("android.util.Base64"); // Base64编码类// 准备要加密的明文var plaintext = "syj";// 将字符串转换为字节数组var plaintextBytes = String.$new(plaintext).getBytes();console.log("原始字符串字节表示: " + JSON.stringify(plaintextBytes));try {// 调用RSA类的静态encrypt方法进行加密var encryptedBytes = RSA.encrypt(plaintextBytes);console.log("加密后的字节长度: " + encryptedBytes.length);// 将加密结果进行Base64编码以便查看var base64Result = Base64.encodeToString(encryptedBytes, 0);console.log("Base64编码后的加密结果: " + base64Result);} catch (e) {console.log("加密过程中出现错误: " + e);} }); public static byte[] encrypt(byte[] plaintext) throws Exception {PublicKey publicKey = getPublicKey(PublicKey);Cipher cipher = Cipher.getInstance("RSA/None/NoPadding", "BC");cipher.init(Cipher.ENCRYPT_MODE, publicKey); // 1对应Cipher.ENCRYPT_MODEreturn cipher.doFinal(plaintext); }