Frida|Frida Hook

- Hook Dlopen

//第一种方式(针对较老的系统版本) var dlopen = Module.findExportByName(null, "dlopen"); console.log(dlopen); if(dlopen != null){ Interceptor.attach(dlopen,{ onEnter: function(args){ var soName = args[0].readCString(); console.log(soName); if(soName.indexOf("libc.so") != -1){ this.hook = true; } }, onLeave: function(retval){ if(this.hook) { dlopentodo(); }; } }); }//第二种方式(针对新系统版本) var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext"); console.log(android_dlopen_ext); if(android_dlopen_ext != null){ Interceptor.attach(android_dlopen_ext,{ onEnter: function(args){ var soName = args[0].readCString(); console.log(soName); if(soName.indexOf("libc.so") != -1){ this.hook = true; } }, onLeave: function(retval){ if(this.hook) { dlopentodo(); }; } }); } function dlopentodo(){ //todo ... }

- Java堆栈打印
function show_java_trace(){ Java.perform(function(){ var MessageDigest = Java.use("java.security.MessageDigest"); MessageDigest.digest.overload().implementation = function(){ //var stack = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()); var stack = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()); console.log(stack); return this.digest(); } }); }

- Native堆栈打印
function show_native_trace(){ var func = Module.findBaseAddress("libil2cpp.so").add(0x56FCA8); Interceptor.attach(func, { onEnter: function(args){ console.log("called from:\n"+ Thread.backtrace(this.context,Backtracer.ACCURATE) .map(DebugSymbol.fromAddress).join("\n")); }, onLeave: function(retval){} }); }

- HookJava中的loadLibrary并打印堆栈
function hook_library(){ Java.perform(function() { const System = Java.use('java.lang.System'); const Runtime = Java.use('java.lang.Runtime'); const VMStack = Java.use('dalvik.system.VMStack'); System.loadLibrary.implementation = function(library) { try { console.log('System.loadLibrary("' + library + '")'); console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new())); const loaded = Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), library); return loaded; } catch(ex) { console.log(ex); } }; System.load.implementation = function(library) { try { console.log('System.load("' + library + '")'); console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new())); const loaded = Runtime.getRuntime().load0(VMStack.getCallingClassLoader(), library); return loaded; } catch(ex) { console.log(ex); } }; }); }

- String转Byte
function stringToBytes(str) { var ch, st, re = []; for (var i = 0; i < str.length; i++ ) { ch = str.charCodeAt(i); st = []; do { st.push( ch & 0xFF ); ch = ch >> 8; } while ( ch ); re = re.concat( st.reverse() ); } return re; }

- hexToBytes
function hexToBytes(str) { var pos = 0; var len = str.length; if (len % 2 != 0) { return null; } len /= 2; var hexA = new Array(); for (var i = 0; i < len; i++) { var s = str.substr(pos, 2); var v = parseInt(s, 16); hexA.push(v); pos += 2; } return hexA; }

- bytes2Hex
function bytes2Hex(arr) { var str = "["; for (var i = 0; i < arr.length; i++) { var z = parseInt(arr[i]); if (z < 0) z = 255 + z; var tmp = z.toString(16); if (tmp.length == 1) { tmp = "0" + tmp; } str = str + " " + tmp; } return (str + " ]").toUpperCase(); }

- ArrayBuffer 转换
function ab2Hex(buffer) { var arr = Array.prototype.map.call(new Uint8Array(buffer), function (x) {return ('00' + x.toString(16)).slice(-2)}).join(" ").toUpperCase(); return "[" + arr + "]"; } function ab2Str(buffer) { return String.fromCharCode.apply(null, new Uint8Array(buffer)); }

- jstring, jbytearray显示
function jstring2Str(jstring) { var ret; Java.perform(function() { var String = Java.use("java.lang.String"); ret = Java.cast(jstring, String); }); return ret; } function jbyteArray2Array(jbyteArray) { var ret; Java.perform(function() { var b = Java.use('[B'); var buffer = Java.cast(jbyteArray, b); ret = Java.array('byte', buffer); }); return ret; }

- 主线程调用
function RunOnMain(){ Java.perform(function(){ var cls_main = null //获取Context Java.choose("com.lzy.ndk.MainActivity",{ onMatch:function(clazz){ cls_main = clazz }, onComplete:function(){} }) //动态注册一个类实现Runnable方法 var cls_run = Java.registerClass({ name:"com.lzy.frida.runnable", implements:[Java.use("java.lang.Runnable")], //创建类成员变量 fields:{ description: 'java.lang.String', limit: 'int' }, //创建方法以及重载方法的用法 methods:{ run:function(){ Java.use("android.widget.Toast").makeText(cls_main,Java.use("java.lang.String").$new("this is a test Toast"),1).show()}, add:[{ returnType:'java.lang.String', argumentTypes:['java.lang.String','java.lang.String'], implementation:function(str1,str2){ return str1+"+++"+str2 } }, { returnType:'java.lang.String', argumentTypes:['java.lang.String'], implementation:function(str1){ return str1+"===" } } ] } }) //这里的实现主线程调用方法很多,这里举例一种 //1.随便在MainActivity找一个View,View.post(Runnable) cls_main.bt1.value.post(cls_run.$new()) //2.Activity的方法runOnUiThread() cls_main.runOnUiThread(cls_run.$new()) //3.new Handler(getMainLooper()).post() Java.use("android.os.Handler").$new(cls_main.getMainLooper()).post(cls_run.$new()) //4.Java.scheduleOnMainThread(function(){}) 不推荐,不好用总是出问题 Java.scheduleOnMainThread(function(){ console.log(Java.isMainThread()) }) }) }

- 写文件(需要存储卡读写权限)
function write_file1() { //frida 的api来写文件 var file = new File("/sdcard/a.txt", "w") file.write("123123123") file.flush() file.close() }function write_file2() { //把C函数定义为NativeFunction来写文件 var addr_fopen = Module.findExportByName("libc.so", "fopen") var addr_fputs = Module.findExportByName("libc.so", "fputs") var addr_fclose = Module.findExportByName("libc.so", "fclose")console.log("addr_fopen:", addr_fopen, "addr_fputs:", addr_fputs, "addr_fclose:", addr_fclose) var fopen = new NativeFunction(addr_fopen, "pointer", ["pointer", "pointer"]) var fputs = new NativeFunction(addr_fputs, "int", ["pointer", "pointer"]) var fclose = new NativeFunction(addr_fclose, "int", ["pointer"])var filename = Memory.allocUtf8String("/sdcard/a.txt") var open_mode = Memory.allocUtf8String("w+") var file = fopen(filename, open_mode) console.log("fopen file:", file); var buffer = Memory.allocUtf8String("123123123") var ret = fputs(buffer, file) console.log("fputs ret:", ret)fclose(file) }

- 批量断点定位调用
function add_native_break_points(){// 结合Il2CppDumper使用,用于批量快速下断点,跟踪native函数调用 // frida -U -f -l C:\Users\lzy\utils\bpoints.js --no-pauseconst soName = "libil2cpp.so"const arrayAddr = ['0x71541c', '0x715b38', '0x715be4', '0x715c61']const arrayName = ['GameManager$$Awake', 'GameManager$$GetParam', 'GameManager$$SaveParam', 'GameManager$$ActivatePrivacyButton']function breakPoints(){const soAddr = Module.findBaseAddress(soName); console.error('\nsoAddr:' + soAddr + "\n"); Java.perform(function(){ arrayAddr .map(function(temp){return soAddr.add(temp)}) .forEach(function(value,index,array){ console.log("-------------------------"); console.log('currentAddr:' + value); try{ funcTmp(value,index,arrayName); }catch(e){ //Thumb指令集地址要加一 funcTmp(value.add(1),soAddr,index,arrayName); } console.log("\t\t---->"+index,value+" is prepared "); }) console.log("\n") })function funcTmp(currentAddr,index,arrayName){ Interceptor.attach(currentAddr, { onEnter: function(args){ console.log("called : "+arrayName[index]+"----- addr : " + currentAddr.sub(soAddr) +"\n"); this.temp = currentAddr.sub(soAddr); if(this.temp === 0xef3080) { console.log('CCCryptorCreate called from:\n' + Thread.backtrace(this.context, Backtracer.ACCURATE) .map(DebugSymbol.fromAddress).join('\n') + '\n'); } }, onLeave: function(retval){} }); } }function hook_dlopen() { // const dlopen = Module.findExportByName(null, "dlopen"); const dlopen = Module.findExportByName(null, "android_dlopen_ext"); if (dlopen != null) { Interceptor.attach(dlopen, { onEnter: function (args) { var l_soName = args[0].readCString() console.log(l_soName) if (l_soName.indexOf(soName) != -1) { this.hook = true } }, onLeave: function (retval) { if (this.hook) { console.warn("\nLoaded "+soName + " add break points") breakPoints() } } }) } }setImmediate(hook_dlopen()) }

配合Il2CppDumper生成的script.json食用更香
import jsonif __name__ == '__main__':# json地址 f = open('C:\\Users\\lzy\\Desktop\\Il2CppDumper-v6.2.1\\script.json',encoding='utf-8') # 查找的字符串 searchStr = "Network"j = json.load(f) ScriptMethod = j['ScriptMethod'] ScriptMetadataMethod = j['ScriptMetadataMethod']temp_name = [] temp_addr = []for temp in ScriptMethod: if searchStr in temp["Name"]: temp_name.append(temp["Name"]) temp_addr.append(hex(temp["Address"]))print('Found : '+str(len(temp_name))+' Function') print('--------------------------------') print(temp_addr) print('--------------------------------') print(temp_name) print('--------------------------------')

- TracerPid fgets 反调试
var anti_fgets = function () { show_log("anti_fgets"); var fgetsPtr = Module.findExportByName("libc.so", "fgets"); var fgets = new NativeFunction(fgetsPtr, 'pointer', ['pointer', 'int', 'pointer']); Interceptor.replace(fgetsPtr, new NativeCallback(function (buffer, size, fp) { var retval = fgets(buffer, size, fp); var bufstr = Memory.readUtf8String(buffer); if (bufstr.indexOf("TracerPid:") > -1) { Memory.writeUtf8String(buffer, "TracerPid:\t0"); // dmLogout("tracerpid replaced: " + Memory.readUtf8String(buffer)); } return retval; }, 'pointer', ['pointer', 'int', 'pointer'])); };

- 日志打印
function log(str){ var threadid = Process.getCurrentThreadId() var date = new Date() var month = date.getMonth() + 1 var strDate = date.getDate() var hour = date.getHours() var Minutes = date.getMinutes() var Seconds = date.getSeconds() if (month >= 1 && month <= 9) { month = "0" + month } if (strDate >= 0 && strDate <= 9) { strDate = "0" + strDate } if (hour >= 0 && hour <= 9) { hour = "0" + hour } if (Minutes >= 0 && Minutes <= 9) { Minutes = "0" + Minutes } if (Seconds >= 0 && Seconds <= 9) { Second = "0" + Seconds } var currentDate = date.getFullYear() + "-" + month + "-" + strDate + " " + hour + ":" + Minutes + ":" + Seconds var log = "["+threadid+"][" + currentDate + "] --- " + str console.log('\x1b[3' + '6; 01' + 'm', log, '\x1b[39; 49; 00m') }--------------------------------------------------------//native日志函数 var nativeLogF var nativeLogPossiblefunction initNativeLog() { try { var native_log_function = Module.findExportByName(null, "__android_log_print") native_log_function ? (nativeLogPossible = true, nativeLogF = new NativeFunction(native_log_function, "int", ["int", "pointer", "pointer", "...", "pointer"])) : LOG("initNativeLog Cound not find export") } catch (e) { LOG(e.message) } }function NLOG(msg) { try { if (!nativeLogPossible) return; var str_tip = Memory.allocUtf8String("Native"), str_formart = Memory.allocUtf8String("%s"), str_msg = Memory.allocUtf8String(msg) nativeLogF(6, str_tip, str_formart, str_msg) } catch (e) { console.error(e.message) } }function JLOG(msg) { try { Java.available && Java.perform(function () { Java.use("android.util.Log").e("Native", msg) }) } catch (e) { } }

- frida api 翻译指定内存位置的代码汇编
function toAssembler(pointer,length){ length = arguments[1] ? arguments[1] : 10 var baseAddr = Module.findBaseAddress(soName) console.error("-----------------------------------") console.warn("Module Addr = \t" + baseAddr) console.error("------------------") var target = ptr(pointer) for (var t = 0; t < length; t++) { var t_addr = target.add(Process.pointerSize * t) console.log(t_addr + " -> " + t_addr.sub(baseAddr) + "\t" + Instruction.parse(t_addr)) } console.error("-----------------------------------") }

- 内存dump so(脱壳upx壳)
function dump_so(so_name) { Java.perform(function () { var currentApplication = Java.use("android.app.ActivityThread").currentApplication(); var dir = currentApplication.getApplicationContext().getFilesDir().getPath(); var libso = Process.getModuleByName(so_name); console.error("------------------------------"); console.warn("[name]:", libso.name); console.warn("[base]:", libso.base); console.warn("[size]:", libso.size); console.warn("[path]:", libso.path); console.error("------------------------------"); var file_path = dir + "/" + libso.name + "_" + libso.base + "_" + ptr(libso.size) + ".so"; var file_handle = new File(file_path, "wb"); if (file_handle && file_handle != null) { Memory.protect(ptr(libso.base), libso.size, 'rwx'); var libso_buffer = ptr(libso.base).readByteArray(libso.size); file_handle.write(libso_buffer); file_handle.flush(); file_handle.close(); console.log("[dump]:", file_path); } }); }

- 动态加载Dex / SO
function loadDex(){ //这里只是提一下可以使用Frida提供的Api加载dex,你也可以解包再打包,但是显然这个方便得多 //手动去加载一些工具类(Gson,AndroidUtilCode,自己写的工具类等等) Java.openClassFile("/data/local/tmp/helper.dex").load() var gson = Java.use("com.google.gson.Gson").$new() ... }const m = Module.load("libnative.so');

- 计划任务
function ScheduledTask(){ //用在Spawn启动的时候 setImmediate(function(){ console.log("立即执行,只执行一次") }) setTimeout(function(){ console.log("一秒后执行,只执行一次") },1000) //Frida Api setInterval(function(){ console.log("每隔一秒执行一次") },1000) // Java Api Java.perform(function(){ Java.registerClass({ name:"com.lzy.frida.tsk", superClass:Java.use("java.util.TimerTask"), methods:{ run:function(){ console.log("等待两秒后每隔一秒调用一次") } } }) Java.use("java.util.Timer").$new().schedule(Java.use("com.lzy.frida.tsk").$new(),2000,1000) }) }

- JNI函数Trace Demo
function TraceJni(){ Java.perform(function(){var pSize = Process.pointerSize var env = Java.vm.getEnv()//JNI函数相对env偏移位置参考: //https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#NewStringUTF var GetStaticMethodID = 113,findclass = 6,RegisterNatives = 215; function getNativeAddress(idx) { return env.handle.readPointer().add(idx * pSize).readPointer() }Interceptor.attach(getNativeAddress(findclass),{ onEnter:function(args){ console.error("-------------findClass-------------") console.warn("env\t--->\t"+args[0]) console.warn("class\t--->\t"+args[1].readCString()) }, onLeave:function(retval){} })Interceptor.attach(getNativeAddress(GetStaticMethodID),{ onEnter:function(args){ console.error("\n-------------GetStaticMethodID-------------") console.warn(args[0]) console.warn(args[1]) console.warn(args[2].readCString()) }, onLeave:function(retval){} })//RegisterNative结构体参照: //https://android.googlesource.com/platform/libnativehelper/+/master/include_jni/jni.h#129 Interceptor.attach(getNativeAddress(RegisterNatives), { onEnter: function(args) { console.log(parseInt(args[3])) for (var i = 0,nMethods = parseInt(args[3]); i < nMethods; i++) { var structSize = pSize * 3; // = sizeof(JNINativeMethod) var methodsPtr = ptr(args[2]); var signature = methodsPtr.add(i * structSize + pSize).readPointer(); var fnPtr = methodsPtr.add(i * structSize + (pSize * 2)).readPointer(); // void* fnPtr var jClass = jclassAddress2NameMap[args[0]].split('/'); var methodName = methodsPtr.add(i * structSize).readPointer().readCString(); console.log('\x1b[3' + '6; 01' + 'm', JSON.stringify({ module: DebugSymbol.fromAddress(fnPtr)['moduleName'], // https://www.frida.re/docs/javascript-api/#debugsymbol package: jClass.slice(0, -1).join('.'), class: jClass[jClass.length - 1], method: methodName, // methodsPtr.readPointer().readCString(), // char* name signature: signature.readCString(), // char* signature TODO Java bytecode signature parser { Z: 'boolean', B: 'byte', C: 'char', S: 'short', I: 'int', J: 'long', F: 'float', D: 'double', L: 'fully-qualified-class; ', '[': 'array' } https://github.com/skylot/jadx/blob/master/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java address: fnPtr }), '\x1b[39; 49; 00m'); } } }); }) }

- Native函数断点/调用/替换
//主动调用native函数 var soAddr = Module.findBaseAddress("libil2cpp.so"); new NativeFunction(soAddr.add(0x4c33b0),"void",['pointer'])(Java.vm.tryGetEnv())//替换native函数 //支持的类型:void,pointer,int,uint,long,ulong,char,uchar,float,double,int8,uint8,int16,uint16,int32,uint32,int64,uint64,bool Interceptor.replace(new NativeFunction(soAddr.add(0x58F0F4),'void', ['pointer']), new NativeCallback(function (arg) { console.log("called from:\n"+ Thread.backtrace(this.context,Backtracer.FUZZY) .map(DebugSymbol.fromAddress).join("\n")); }, 'void', ['pointer'])); //拦截native函数 Interceptor.attach(soAddr.add(0xb7a93c),{ onEnter:function(arg){ console.log("called 0xb7a93c") }, onLeave:function(retval){ console.warn(retval) } })

- 获取类型
function getParamType(obj) { return obj == null ? String(obj) : Object.prototype.toString.call(obj).replace(/\[object\s+(\w+)\]/i, "$1") || "object"; }

- hook 所有重载函数
function hookAllOverloads(targetClass, targetMethod) { Java.perform(function () { var targetClassMethod = targetClass + '.' + targetMethod; var hook = Java.use(targetClass); var overloadCount = hook[targetMethod].overloads.length; for (var i = 0; i < overloadCount; i++) { hook[targetMethod].overloads[i].implementation = function() { var retval = this[targetMethod].apply(this, arguments); //这里可以打印结果和参数 return retval; } } }); }

/** * 这里记录一下杠精日常(durk不必这么操作):用java反射来实现frida的函数调用 * 详情见下代码,归纳一下来说就是: * 获取反射方法的时候传递的是基本数据类型,调用反射方法的时候参数用基础类型的封装类型 */ function java_invoke(){/** * java code clas ==> com.lzy.dobbytest.test * *public void showLog(int a){ System.out.println("this is a string showLog! "+a); }public void showLog(String a){ System.out.println("this is a string showLog! "+a + "String"); }public void showLog(char a){ System.out.println("this is a string showLog! "+a + "Int"); }public void showLog(byte a){ System.out.println("this is a string showLog! "+a + "Byte"); }public void showLog(boolean a,intb){ System.out.println("this is a string showLog! "+a + b + "boolean int"); } } */Java.perform(function(){var cls_utils = Java.use("com.lzy.dobbytest.MD5Utils") var newCls = cls_utils.$new() var str_obj = "java.lang.Object"//拿到基本数据类型 var type_int = Java.use("java.lang.Class").getPrimitiveClass("int") var type_boolean = Java.use("java.lang.Class").getPrimitiveClass("boolean") var type_byte = Java.use("java.lang.Class").getPrimitiveClass("byte")//String 参数的方法 var String = Java.use("java.lang.String"); var method = cls_utils.class.getDeclaredMethod("showLog",Java.array(str_obj,[String.class])) method.invoke(newCls,Java.array(str_obj,["test"]))//这里有自动的类型转换 String.$new("test") == "test"//Int 参数的方法 var method = cls_utils.class.getDeclaredMethod("showLog",Java.array(str_obj,[type_int])) method.invoke(newCls,Java.array(str_obj,[Java.use("java.lang.Integer").$new('12')])) //等价于Java.use("java.lang.Integer").valueOf('12')//Boolean 参数的方法 var method = cls_utils.class.getDeclaredMethod("showLog",Java.array(str_obj,[type_boolean])) method.invoke(newCls,Java.array(str_obj,[Java.use("java.lang.Boolean").$new('true')]))//Byte 参数的方法 var method = cls_utils.class.getDeclaredMethod("showLog",Java.array(str_obj,[type_byte])) method.invoke(newCls,Java.array(str_obj,[Java.use("java.lang.Byte").$new(String.$new('92'))]))//Boolean 和 Int 参数的方法 var method = cls_utils.class.getDeclaredMethod("showLog",Java.array(str_obj,[type_boolean,type_int])) method.invoke(newCls,Java.array(str_obj,[Java.use("java.lang.Boolean").$new('true'),Java.use("java.lang.Integer").$new('12')]))}) }

pip的源码安装在site-packages目录下
frida 使用小技巧
  • 免root使用frida → 重打包
    1. 反编译后重写将 libfrida-gadget.so 打包进app
      并在smali代码中加载
      • 注意
        这里去下载的so一定得是和你本地当前的frida版本一致
        使用pip list 查看本地的版本
    const-string v1, "frida-gadget" invoke-static {v1}, Ljava/lang/System; ->loadLibrary(Ljava/lang/String; )V

    1. 为app添加网络访问权限,以便frida-gadget可以打开套接字

    1. 出现日志:Frida: Listening on TCP port 27042,这时候是处于一个等待frida连接状态
    2. frida-ps -U可以查看到当前程序PID 以及默认的名字Gadget
    3. 使用以上的PID或者是默认的名字Gadget连接frida
  • 免root使用frida → ptrace注入 (这里我是用的是别人对其封装一层方便使用的 脚本 , 原脚本在这里 )
    1. 首先是在linux(kali)环境下完成
    2. git clone请在linux下完成(坑!别再windows下拷贝过去,可能出现编码问题)
    3. adb shell am start -D -n com.lzy.xxxx/.MainActivity以调试模式启动
      除了命令启动,也可以再手机开发者选项中找到调试应用并开启等待调试器也行
      (这里的话仅再app中debuggable=“true“时候能这么启动)
    4. 再就是参照作者写的,一句话就搞定注入即可
      ./jdwp-lib-injector.sh frida-gadget.so
  • root使用frida → root 身份运行 frida_server 【Frida|Frida Hook】(这也算是我们使用最频繁的场景)
    1. 首先还是去官网搞一个frida_server
    2. push 到 /data/local/tmp/frida/目录之下
    3. 在上面目录给android_server权限 chmod 777 ./*
    4. ./frida_server 跑起来 或者是 cmd 中使用
      adb shell "su -c ./data/local/tmp/frida/frida_server"
      (后面这条命令不是所有手机都适用)
    https://www.jianshu.com/p/d2d7da75990a
  • 重打包或者root,LIEF改so,实现对frida-gadget.so的加载 参考原文 原文Demo
    这种操作也是非常的方便实用,前提也是需要重打包apk,
    当然你手机有root的时候直接copy替换原so也是可行的
    (这种操作也有弊端,执行不到jni_onload,只会执行到init_array的函数)
    1. 首先还是拿到我们的工具LIEF
      (不建议使用windows踩坑,心态容易崩,直接上linux,用pip安装上就是)
    2. 然后写一个贼简单的脚本
    import lief libnative = lief.parse("libnative-lib.so")//待修改的so libnative.add_library("libfrida-gadget.so")//在frida官网搞来的和本地版本一致gadget libnative.write("libfrida-gadget1.so")//输出的文件名称

    1. 然后用我们新的so改回原名称,替换原so,并同时把gadget.so放回lib重打包即可
  • 使用Xpatch实现免Root的Frida功能
  • root使用frida → 本地化脚本调用
    1. 重打包部分和上文 [免root使用frida → 重打包] 一样
    2. 新增一步操作,创建一个和lib同名但是后缀为conf.so的文本文件
      libfrida-gadget.so → libfrida-gadget.config.so
      libfrida-gadget.config.so的文本内容为以下
    { "interaction": { "type": "script", "path": "/sdcard/script.js", "on_change": "reload" } }

    1. 在上述位置放上我们的hook脚本即可
      参考文章:文章 官网教程
实现frida的注入有以下方法
  • 重打包手动注入frida-gadget.so(使用方法一)
  • 改机重刷系统,实现全局可调式(使用方法二)
  • 使用xposed的插件BDOpener,实现全局可调式(使用方法二)
  • 修改app清单文件重打包(使用方法二)
  • Root身份运行frida_server(使用方法三)

    推荐阅读