验证码服务平台 谷歌人机 ReCaptcha（v2/v3 通用版）, 直接返回 token ReCaptcha（v2/v3 企业版）, 直接返回 token ReCaptcha（steam）, 直接返回 token CloudFlare 盾通用版, 返回 cf_clearance、__cf_bm 、正确响应源码 html、验证流程使用的 tls 指纹 Incapsula 盾 reese84 通用版, 返回 reese84 cookie 或 solution 参数 Incapsula 盾 __utmvc 通用版, 服务器直接无感验证 或 __utmvc cookie Hcaptcha 通用版, 直接返回 generated_pass_UUID 直接返回 _abck tls 转发接口, 针对校验 ja3、http2 等指纹（akamai/cloudflare）的接口 更多支持后续敬请期待..... 注册连接：https://3ii.cc/V2J 新用户注册加群即送10000点余额 新用户注册加群即送10000点余额 新用户注册加群即送10000点余额Frida 启动attach 启动直接附加到指定包名的应用中frida -U com.kevin.android -l hook.js直接附加到当前应用中frida -UF -l hook.jsimport sys import time import frida def on_message(message,data): print("message",message) print("data",data) device = frida.get_usb_device() session = device.attach("com.kevin.demo1") with open("./demo1.js","r") as f: script = session.create_script(f.read()) script.on("message",on_message) script.load() sys.stdin.read()spawn 启动frida -U -f com.kevin.android -l demo1.js --no-pauseimport sys import time import frida def on_message(message,data): print("message",message) print("data",data) device = frida.get_usb_device() pid = device.spawn(["com.kevin.demo1"]) device.resume(pid) session = device.attach(pid) with open("./rpc_demo.js",'r') as f: script = session.create_script(f.read()) script.on("message",on_message) script.load() sys.stdin.read()frida-server 自定义端口frida server更改 frida server 默认端口: 27042 并开启远程连接adb shell su - cd /data/local/tmp # 输入 wifiadb 对应的 ip 和自定义端口 ./frida-server -l 192.168.0.1:6666 # 也可以使用默认端口启动 ./frida-server -l 192.168.0.1fridafrida 远程连接自定义端口# 连接指定 6666 端口 frida -H 192.168.0.1:6666 com.demo1.app -l demo1.js # 默认使用端口 27042 frida -H 192.168.0.1 -l demo1.jspython# -*- coding: UTF-8 -*- import frida, sys jsCode = """ console.log("test"); """ def message(message, data): if message['type'] == 'send': print(f"[*] {message['payload']}") else: print(message) # ./fs120800 -l "0.0.0.0:6666" # adb wifi 10.0.0.23 process = frida.get_device_manager().add_remote_device('127.0.0.1:6666').attach('com.kevin.app') script = process.create_script(jsCode) script.on("message",message) script.load() input()Frida rpc 远程调用pythonimport frida import json from flask import Flask, jsonify, request def message(message, data): if message['type'] == 'send': print(f"[*] {message['payload']}") else: print(message) # ./fs120800 -l "0.0.0.0:6666" # adb wifi 10.0.0.123 # 远程 frida-server 路径 adb wifi 的 ip : frida-server 启动的端口 session = frida.get_device_manager().add_remote_device('10.0.0.123:6666').attach('com.example.demoso1') with open("/Users/zhangyang/codes/fridaProject/rpcDemo/hook.js") as f: jsCode = f.read() # print("加载代码", jsCode) script = session.create_script(jsCode) script.on("message",message) script.load() # print("加密","1213") # encodeResult = script.exports.invokemethod01("123") # decodeResult = script.exports.invokemethod02(encodeResult) # print(decodeResult) app = Flask(__name__) @app.route('/encrypt', methods=['POST'])#data解密 def decrypt_class(): data = request.get_data() json_data = json.loads(data.decode("utf-8")) postdata = json_data.get("data") res = script.exports.invokemethod01(postdata) return res @app.route('/decrypt', methods=['POST'])#url加密 def encrypt_class(): data = request.get_data() json_data = json.loads(data.decode("utf-8")) postdata = json_data.get("data") print(postdata) res = script.exports.invokemethod02(postdata) return res if __name__ == "__main__": app.run()js///<reference path='/Users/zhangyang/node_modules/@types/frida-gum/index.d.ts'/> // 先 hook 方法 method01 // function hookmethod1(){ // Java.perform(function(){ // var targetClass = Java.use("com.example.demoso1.MainActivity"); // targetClass.method01.implementation = function(str){ // console.log("str is ", str); // var result = this.method01(str); // console.log("result is ", result); // return result; // } // }) // }; // 主动调用 function fridamethod01(inputStr){ var result = null; Java.perform(function(){ var targetClass = Java.use("com.example.demoso1.MainActivity"); result = targetClass.method01(inputStr); }); return result; } function fridamethod02(inputStr){ var result = null; // public native String method02(String str); Java.perform(function(){ Java.choose("com.example.demoso1.MainActivity",{ onMatch: function(ins){ result = ins.method02(inputStr); }, onComplete: function(){} }) }); return result; } // 优先测试 js 中的主动调用 // function main(){ // console.log("你好 -> 结果为:", fridamethod01("你好")); // console.log("27cae29a0913f6791705ca10be31a3e0 -> 结果为", fridamethod02("27cae29a0913f6791705ca10be31a3e0")) // } // setImmediate(main); // 基于主动调用设置 rpc rpc.exports = { invokemethod01: fridamethod01, invokemethod02: fridamethod02, }压力测试tmp.json{"data": "62feb9a98a01945ab06c0dd7823adc57"}命令siege -c30 -r1 "<http://127.0.0.1:5000/encrypt> POST < tmp.json"nps 进行内网穿透nps server 启动mac: sudo nps start新建客户端安卓手机连接客户端 ./npc -server=10.0.0.124:8024 -vkey=hm40rtjpf2j3c1up -type=tcp给客户端添加和 frida server 的端口映射安卓手机启动 frida-server: ./fs12800 -l 0.0.0.0:6666将目标 frida-server 的端口映射到 56666 端口上python 脚本更改和 frida-server 的连接此时就可以将 frida-server 开放到公网了;session = frida.get_device_manager().add_remote_device('10.0.0.124:56666').attach('com.example.demoso1')Hook 普通方法function main(){ Java.perform(function(){ var UtilsClass = Java.use("com.kevin.app.Utils"); UtilsClass.getCalc.implementation = function (a,b){ // 打印信息 console.log('a:' + a + ' ' + 'b:' + b); // 调用原方法获取结果 var value = this.getCalc(a, b); console.log('result:',value); // 修改返回值 return 123456; } }) } setImmediate(main);Hook 重载方法function main(){ Java.perform(function(){ var UtilsClass = Java.use("com.kevin.app.Utils"); // 重载无参方法 UtilsClass.test.overload().implementation = function () { console.log("hook overload no args"); return this.test(); } // 重载有参方法 - 基础数据类型 UtilsClass.test.overload('int').implementation = function(num){ console.log("hook overload int args"); var myNum = 9999; var oriResult = this.test(num); console.log("oriResult is :" + oriResult); return this.test(myNum); } // 重载有参方法 - 引用数据类型 UtilsClass.test.overload('com.kevin.app.Money').implementation = function(money){ console.log("hook Money args"); return this.test(money); } // hook 指定方法的所有重载 var ClassName = Java.use("com.xiaojianbang.app.Utils"); var overloadsLength = ClassName.test.overloads.length; for (var i = 0; i < overloadsLength; i++){ ClassName.test.overloads[i].implementation = function () { // 遍历打印 arguments for (var a = 0; a < arguments.length; a++){ console.log(a + " : " + arguments[a]); } // 调用原方法 return this.test.apply(this,arguments); } } }) } setImmediate(main);Hook 构造方法function main(){ Java.perform(function (){ // hook 构造方法 $init var MoneyClass = Java.use("com.kevin.app.Money"); MoneyClass.$init.overload().implementation = function(){ console.log("hook Money $init"); this.$init(); } }) } setImmediate(main);Hook 对象通过 Java.choose找到指定对象通过Java.use找到对应的类, 在手动调用构造方法构造对象hook 动态方法, 此时的this就是对象本身;hook 以目标对象作为参数的方法, 此时该参数就是对象;使用 choose 查找对象function main(){ Java.perform(function(){ // hook instance Java.choose("com.xiaojianbang.app.Money",{ onMatch : function(instance){ console.log("find it!!", instance.getInfo()); // something to do... }, onComplete: function(){ console.log("compelete!!!"); } }) }) } setImmediate(main);使用 retain 保存对象Java.perform(() => { const Activity = Java.use('android.app.Activity'); let lastActivity = null; Activity.onResume.implementation = function () { lastActivity = Java.retain(this); this.onResume(); }; });Hook 动静态成员属性function main(){ Java.perform(function(){ var MoneyClass = Java.use("com.xiaojianbang.app.Money"); // get static properties // need to use .value var ori_property = MoneyClass.flag.value; console.log("ori_property: ", ori_property); // change static properties MoneyClass.flag.value = "change the value"; console.log("change to : ", MoneyClass.flag.value); // get dynamic properties Java.choose("com.xiaojianbang.app.Money",{ onMatch: function(instance){ instance.num.value = 50000; // 当对象的成员属性和成员方法名重复时,成员属性前加`_`,进行区分 instance._name.value = "ouyuan"; console.log(instance._name.value, instance.num.value, instance.flag.value); }, onComplete: function(){ console.log("complete!!") } }) }) } setImmediate(main);Hook 内部类function main(){ Java.perfor(function(){ // hook 内部类 // 内部类使用$进行分隔 不使用. var InnerClass = Java.use("com.xiaojianbang.app.Money$innerClass"); // 重写内部类的 $init 方法 InnerClass.$init.overload("java.lang.String","int").implementation = function(x,y){ console.log("x: ",x); console.log("y: ",y); this.$init(x,y); } }) } setImmediate(main)Hook 匿名类// 接口, 抽象类, 不可以被new // 接口, 抽象类 要使用必须要实例化, 实例化不是通过new, 而是通过实现接口方法, 继承抽象类等方式 // new __接口__{} 可以理解成 new 了一个实现接口的匿名类, 在匿名类的内部(花括号内),实现了这个接口 function main(){ Java.perform(function(){ // hook 匿名类 // 匿名类在 smail中以 $1, $2 等方式存在, 需要通过 java 行号去 smail 找到准确的匿名类名称 var NiMingClass = Java.use("com.xiaojianbang.app.MainActivity$1"); NiMingClass.getInfo.implementation = function (){ return "kevin change 匿名类"; } }) } setImmediate(main)Hook 类的所有方法Java.enumerateLoadedClasses()function main(){ Java.perform(function(){ Java.enumerateLoadedClasses({ onMatch: function(name,handle){ if (name.indexOf("com.xiaojianbang.app.Money") != -1){ console.log(name,handle); // 利用反射 获取类中的所有方法 var TargetClass = Java.use(name); // return Method Object List var methodsList = TargetClass.class.getDeclaredMethods(); for (var i = 0; i < methodsList.length; i++){ // Method Objection getName() console.log(methodsList[i].getName()); } } }, onComplete: function(){ console.log("complete!!!") } }) }) }Java.enumerateLoadedClassesSync()function main(){ Java.perform(function(){ // return String[] class name var classList = Java.enumerateLoadedClassesSync(); for (var i=0; i < classList.length; i++){ var targetClass = classList[i]; if (targetClass.indexOf("com.xiaojianbang.app.Money") != -1){ console.log("hook the class: ", targetClass); var TargetClass = Java.use(targetClass); // 利用反射获取类中的所有方法 var methodsList = TargetClass.class.getDeclaredMethods(); for (var k=0; k < methodsList.length; k++){ console.log(methodsList[k].getName()); } } } }) } setImmediate(main)Hook 类的所有方法及重载function main(){ Java.perform(function(){ // hook md5 class in app // 1. iterate classes var classList = Java.enumerateLoadedClassesSync(); for (var i = 0; i < classList.length; i++){ // 筛选过滤 只遍历 MD5 下面的方法 if (classList[i].indexOf("com.xiaojianbang.app.MD5") != -1){ var className = classList[i]; console.log("class name is :", className); // 2. get methods of the class // 返回一个 Methods对象的数组 var methodsList = Java.use(className).class.getDeclaredMethods(); for (var k=0; k<methodsList.length; k++){ // console.log("method is :",methodsList[k],typeof(methodsList[k])); // 3. Method object.getName() --> methodName and class[methodName] to hook method var methodName = methodsList[k].getName(); // // console.log('methodName',methodName); // 4. use apply and arguments to implementation var hookClass = Java.use(className); // 5. overloads for (var o = 0; o< hookClass[methodName].overloads.length; o++){ hookClass[methodName].overloads[o].implementation = function(){ for (var a=0; a<arguments.length; a++){ console.log('argument ',a,arguments[a]); } // return this[methodName].apply(this,arguments); return "fucking the md5" } } } } } }) }Hook 动态加载的 dexfunction main(){ Java.perform(function(){ Java.enumerateClassLoaders({ onMatch : function(loader){ try { // loadClass or findClass if (loader.loadClass("com.xiaojianbang.app.Dynamic")){ Java.classFactory.loader = loader; var hookClass = Java.use("com.xiaojianbang.app.Dynamic"); console.log("success hook it :", hookClass); // something to do; } } catch (error) { // pass } }, onComplete: function () { console.log("complete !!! ") } }) }) } setImmediate(main);经常在加壳的 app 中, 没办法正确找到正常加载 app 类的 classloader, 可以使用以下代码:function hook() { Java.perform(function () { Java.enumerateClassLoadersSync().forEach(function (classloader) { try { console.log("classloader", classloader); classloader.loadClass("com.kanxue.encrypt01.MainActivity"); Java.classFactory.loader = classloader; var mainActivityClass = Java.use("com.kanxue.encrypt01.MainActivity"); console.log("mainActivityClass", mainActivityClass); } catch (error) { console.log("error", error); } }); }) }Hook 主动构造数组function mainArray(){ Java.perform(function(){ var myCharList = Java.array("char",['一','去','二','三','里']); var myStringList = Java.array("java.lang.String",["一","二","三"]); var ArrayClass = Java.use("java.util.Arrays"); console.log(ArrayClass.toString(myCharList)); console.log(ArrayClass.toString(myStringList)); }) }Hook cast 强制类型转换// Java.cast() 子类可以强转成父类, 父类不能转成子类 // 可以使用Java.cast()将子类强转成父类, 再调用父类的动态方法 function castDemo(){ Java.perform(function(){ var JuiceHandle = null; // 用来存储内存中找到的Juice对象 var WaterClass = Java.use("com.r0ysue.a0526printout.Water"); Java.choose("com.r0ysue.a0526printout.Juice",{ onComplete: function(){}, onMatch: function(instance){ JuiceHandle = instance; console.log("instance:", instance); // 调用Juice对象的方法 console.log(JuiceHandle.fillEnergy()); // 子类Juice转父类Water 并调用父类的动态方法 var WaterInstance = Java.cast(JuiceHandle,WaterClass); console.log(WaterInstance.still(WaterInstance)); } }) }) }Hook 打印类实现的接口function searchInterface(){ Java.perform(function(){ Java.enumerateLoadedClasses({ onComplete: function(){}, onMatch: function(name,handle){ if (name.indexOf("com.r0ysue.a0526printout") > -1) { // 使用包名进行过滤 console.log("find class"); var targetClass = Java.use(name); var interfaceList = targetClass.class.getInterfaces(); // 使用反射获取类实现的接口数组 if (interfaceList.length > 0) { console.log(name) // 打印类名 for (var i in interfaceList) { console.log("\t", interfaceList[i].toString()); // 直接打印接口名称 } } } } }) }) }Hook enum 枚举function enumPrint(){ Java.perform(function(){ Java.choose("com.r0ysue.a0526printout.Signal",{ onComplete: function(){}, onMatch: function(instance){ console.log('find it ,',instance); console.log(instance.class.getName()); } }) }) }Hook 获取 contextfunction getContext(){ Java.perform(function(){ var currentApplication = Java.use("android.app.ActivityThread").currentApplication(); console.log(currentApplication); var context = currentApplication.getApplicationContext(); console.log(context); var packageName = context.getPackageName(); console.log(packageName); console.log(currentApplication.getPackageName()); }) }Hook 主动调用构造方法function main(){ Java.perform(function(){ var StringClass = Java.use("java.lang.String"); var MoneyClass = Java.use("com.xiaojianbang.app.Money"); MoneyClass.$init.overload('java.lang.String','int').implementation = function(x,y){ console.log('hook Money init'); var myX = StringClass.new("Hello World!"); var myY = 9999; this.$init(myX,myY); } }) } setImmediate(main);Hook 主动调用静态方法function main_rsa(){ Java.perform(function(){ var RSA = Java.use("com.xiaojianbang.app.RSA"); var StringClass = Java.use("java.lang.String"); var base64Class = Java.use("android.util.Base64"); var myBytes = StringClass.$new("Hello World").getBytes(); var result = RSA.encrypt(myBytes); console.log("result is :", result); console.log("json result is: ",JSON.stringify(result)); console.log("base64 result is :", base64Class.encodeToString(result,0)); // console.log("new String is : ", StringClass.$new(result)); // 加密之后的内容有很多不可见字符, 不能直接 new String() }) } setImmediate(main_rsa);Hook 主动调用动态方法// 非静态方法的主动调用 自定义instance 并调用 非静态方法 function main_getInfo(){ Java.perform(function(){ var instance = Java.use("com.xiaojianbang.app.Money").$new("日元",300000); console.log(instance.getInfo()); }) } // 遍历所有的对象并调用 需要进行过滤 function main_instance_getInfo(){ Java.perform(function(){ Java.choose("com.xiaojianbang.app.Money",{ onComplete: function(){}, onMatch: function(instance){ console.log(instance.getInfo()); } }) }) }Hook frida 和 python 交互frida 传递参数 function main(){ Java.perform(function () { console.log("enter perform"); // 获取要hook的类 var TextViewClass = Java.use("android.widget.TextView"); // 要hook的方法 TextViewClass.setText.overload('java.lang.CharSequence').implementation = function (ori_input) { console.log('enter', 'java.lang.CharSequence'); console.log('ori_input',ori_input.toString()); // 定义用于接受python传参的data var receive_data; // 将原参数传递给python 在python中进行处理 send(ori_input.toString()); // recv 从python接收传递的内容 默认传过来的是个json对象 recv(function (json_data) { console.log('data from python ' + json_data.data); receive_data = json_data.data; console.log(typeof (receive_data)); }).wait(); //wait() 等待python处理 阻塞 // 转java字符串 receive_data = Java.use("java.lang.String").$new(receive_data); this.setText(receive_data); }; }) } setImmediate(main); python 处理收到的参数 # -*- coding: utf-8 -*- __author__ = "K" __time__ = "2020-08-06 09:48" import sys import time import base64 import frida from loguru import logger def on_message(message,data): logger.info(str(message)) # dict logger.info(str(data) if data else "None") if message['type'] == 'error': logger.error('error:' + str(message['description'])) logger.error('stack: ' + str(message['stack'])) if message['type'] == 'send': logger.info('get message [*] --> ' + message['payload']) payload = message['payload'] # 处理逻辑 sending to the server: YWFhOmJiYg== tmp = payload.split(':') sts = tmp[0] need_to_db64 = tmp[1] user_pass = base64.b64decode(need_to_db64.encode()).decode() mine_str = 'admin' + ':' + user_pass.split(':')[-1] mine_b64_str = base64.b64encode(mine_str.encode()).decode() mine_b64_str = sts + mine_b64_str logger.info(mine_b64_str) # python返回数据给js script.post script.post({'data':mine_b64_str}) logger.info('python complete') device = frida.get_usb_device() # pid = device.spawn(['com.kevin.demo04']) # time.sleep(1) session = device.attach('com.kevin.demo02') with open('./hulianhutong.js','r') as f: script = session.create_script(f.read()) script.on("message",on_message) script.load() input()Hook 打印 char// 打印char字符, 直接调用java.lang.Character toString()即可 function main(){ Java.perform(function(){ var CharClass = Java.use("java.lang.Character"); CharClass.toString.overload("char").implementation = function(inputChar){ var result = this.toString(inputChar); console.log("inputChar, result: ", inputChar, result); return result; } }) }Hook 打印 char 数组// 1. 使用 java.util.Arrays 的 toString 方法 打印 [C // 2. 使用 js 的 JSON.stringify 打印 [C function printCharArray(){ Java.perform(function(){ var ArrayClass = Java.use("java.util.Arrays"); ArrayClass.toString.overload('[C').implementation = function(charArray){ // 1. java.util.Arrays.toString() var result = this.toString(charArray); // 2. javascript JSON.stringify() var result1 = JSON.stringify(charArray); console.log('charArray, result : ', charArray, result); console.log('charArray, result :', charArray, result1); } }) }Hook 打印和修改 HashMap遍历打印 function main(){ Java.perform(function(){ var targetClass = Java.use("com.xiaojianbang.app.ShufferMap"); targetClass.show.implementation = function(map){ // 遍历 map var result = ""; var it = map.keySet().iterator(); while (it.hasNext()){ var keyStr = it.next(); var valueStr = map.get(keyStr); result += valueStr; } console.log("result :", result); // 修改 map map.put("pass","fxxk"); map.put("code","Hello World"); console.log(JSON.stringify(map)); this.show(map); return this.show(map); } }) } setImmediate(main); // cast打印 HashMap function main(){ Java.perform(function(){ var HashMapNode = Java.use("java.util.HashMap$Node"); var targetClass = Java.use("com.xiaojianbang.app.ShufferMap"); var targetClass.show.implementation = function(map){ var result = ""; var iterator = map.entrySet().iterator(); while (iterator.hasNext()) { console.log("entry", iterator.next()); var entry = Java.cast(iterator.next(), HashMapNode); console.log(entry.getKey()); console.log(entry.getValue()); return += entry.getValue(); } console.log("result is :", result); } }) } setImmediate(main);toString()打印function main(){ Java.perform(function(){ var targetClass = Java.use("com.xiaojianbang.app.ShufferMap"); targetClass.show.implementation = function(map){ // 直接调用 toString() console.log("打印hashmap: -> " + map.toString()); return this.show.apply(this,arguments); } }) } setImmediate(main); function printHashMap(flag, param_hm) { Java.perform(function () { var HashMap = Java.use('java.util.HashMap'); var args_map = Java.cast(param_hm, HashMap); send(flag +":" + args_map.toString()); }) }Hook 打印 byte 数组方法 1 function main(){ Java.perform(function(){ var StringClass = Java.use("java.lang.String"); var byteArray = StringClass.$new("Hello World").getBytes(); // load r0gson // openClassFile 返回 dex对象, dex对象.load()加载dex文件内容 Java.openClassFile("/data/local/tmp/r0gson.dex").load(); var gson = Java.use("com.r0ysue.gson.Gson"); console.log(gson.$new().toJson(byteArray)); // // console byte[] // var ByteString = Java.use("com.android.okhttp.okio.ByteString"); // console.log(ByteString.of(byteArray).hex()); // byte转16进制字符串 // // 创建自定义Java数组 并打印 // var MyArray = Java.array("byte",[13,4,4,2]); // console.log(gson.$new().toJson(MyArray)); var TargetClass = Java.use("com.xiaojianbang.app.ShufferMap"); TargetClass.show.implementation = function(map){ console.log(gson.$new().toJson(map)); return this.show(map); } }) } setImmediate(main); 方法 2 // 1. 使用 java.util.Arrays.toString() 打印 [B // 2. 使用 javascript JSON.stringify() 打印 [B function printByteArray(){ Java.perform(function(){ var ArrayClass = Java.use("java.util.Arrays"); ArrayClass.toString.overload('[B').implementation = function(byteArray){ // 1. 使用 java.util.Arrays.toString() 打印 [B var result = this.toString(byteArray); // 2. 使用 javascript JSON.stringify() 打印 [B var result1 = JSON.stringify(byteArray); console.log('byteArray,result: ', byteArray, result); console.log('byteArray,result1 :', byteArray, result1); return result } }) } 方法 3 function printByteArray(byteArray){ Java.perform(function(){ var ByteString = Java.use("com.android.okhttp.okio.ByteString"); console.log(ByteString.of(byteArray).hex()) }) }Hook 打印调用栈function printStacks(name){ console.log("====== printStacks start ====== " + name + "==============================") // sample 1 var throwable = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()); console.log(throwable); // sample 2 var exception = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()); console.log(exception); console.log("====== printStacks end ======== " + name + "==============================") }Hook gson 打印function main(){ Java.perform(function(){ var StringClass = Java.use("java.lang.String"); var byteArray = StringClass.$new("Hello World").getBytes(); // load r0gson // openClassFile 返回 dex对象, dex对象.load()加载dex文件内容 Java.openClassFile("/data/local/tmp/r0gson.dex").load(); var gson = Java.use("com.r0ysue.gson.Gson"); console.log(gson.$new().toJson(byteArray)); // // console byte[] // var ByteString = Java.use("com.android.okhttp.okio.ByteString"); // console.log(ByteString.of(byteArray).hex()); // byte转16进制字符串 // // 创建自定义Java数组 并打印 // var MyArray = Java.array("byte",[13,4,4,2]); // console.log(gson.$new().toJson(MyArray)); var TargetClass = Java.use("com.xiaojianbang.app.ShufferMap"); TargetClass.show.implementation = function(map){ console.log(gson.$new().toJson(map)); return this.show(map); } }) } setImmediate(main);Hook 打印 non-ascii 和特殊字符一些特殊字符和不可见字符, 可以先通过编码再解码的方式进行 hookint ֏(int x) { return x + 100; }针对上面的֏, 直接用js编码, 在通过类名[js解码的方法名]进行implementationJava.perform( function x() { var targetClass = "com.example.hooktest.MainActivity"; var hookCls = Java.use(targetClass); var methods = hookCls.class.getDeclaredMethods(); for (var i in methods) { console.log(methods[i].toString()); console.log(encodeURIComponent(methods[i].toString().replace(/^.*?\.([^\s\.\(\)]+)\(.*?$/, "$1"))); } hookCls[decodeURIComponent("%D6%8F")] .implementation = function (x) { console.log("original call: fun(" + x + ")"); var result = this[decodeURIComponent("%D6%8F")](900); return result; } } )简易 wallbreaker 内存打印内存漫游, 打印实例的字段和方法function main(){ Java.perform(function(){ var Class = Java.use("java.lang.Class"); function inspectObject(obj){ var obj_class = Java.cast(obj.getClass(), Class); var fields = obj_class.getDeclaredFields(); var methods = obj_class.getMethods(); console.log("Inspectiong " + obj.getClass().toString()); console.log("\t Fields:") for (var i in fields){ console.log("\t\t" + fields[i].toString()); } console.log("\t Methods:") for (var i in methods){ console.log("\t\t" + methods[i].toString()) } } Java.choose("com.baidu.lbs.waimai.WaimaiActivity",{ onComplete: function(){ console.log("complete!"); }, onMatch: function(instance){ console.log("find instance", instance); inspectObject(instance); } }) }) } setImmediate(main)hook frida 实现 runnableJava.perform(function() { // https://developer.android.com/reference/android/view/WindowManager.LayoutParams.html#FLAG_SECURE var FLAG_SECURE = 0x2000; var Runnable = Java.use("java.lang.Runnable"); var DisableSecureRunnable = Java.registerClass({ name: "me.bhamza.DisableSecureRunnable", implements: [Runnable], fields: { activity: "android.app.Activity", }, methods: { $init: [{ returnType: "void", argumentTypes: ["android.app.Activity"], implementation: function (activity) { this.activity.value = activity; } }], run: function() { var flags = this.activity.value.getWindow().getAttributes().flags.value; // get current value flags &= ~FLAG_SECURE; // toggle it this.activity.value.getWindow().setFlags(flags, FLAG_SECURE); // disable it! console.log("Done disabling SECURE flag..."); } } }); Java.choose("com.example.app.FlagSecureTestActivity", { "onMatch": function (instance) { var runnable = DisableSecureRunnable.$new(instance); instance.runOnUiThread(runnable); }, "onComplete": function () {} }); });Hook 监控控件 onClickvar jclazz = null; var jobj = null; function getObjClassName(obj) { if (!jclazz) { var jclazz = Java.use("java.lang.Class"); } if (!jobj) { var jobj = Java.use("java.lang.Object"); } return jclazz.getName.call(jobj.getClass.call(obj)); } function watch(obj, mtdName) { var listener_name = getObjClassName(obj); var target = Java.use(listener_name); if (!target || !mtdName in target) { return; } // send("[WatchEvent] hooking " + mtdName + ": " + listener_name); target[mtdName].overloads.forEach(function (overload) { overload.implementation = function () { //send("[WatchEvent] " + mtdName + ": " + getObjClassName(this)); console.log("[WatchEvent] " + mtdName + ": " + getObjClassName(this)) return this[mtdName].apply(this, arguments); }; }) } function OnClickListener() { Java.perform(function () { //以spawn启动进程的模式来attach的话 Java.use("android.view.View").setOnClickListener.implementation = function (listener) { if (listener != null) { watch(listener, 'onClick'); } return this.setOnClickListener(listener); }; //如果frida以attach的模式进行attch的话 Java.choose("android.view.View$ListenerInfo", { onMatch: function (instance) { instance = instance.mOnClickListener.value; if (instance) { console.log("mOnClickListener name is :" + getObjClassName(instance)); watch(instance, 'onClick'); } }, onComplete: function () { } }) }) } setImmediate(OnClickListener);Hook startActivityJava.perform(function () { var Activity = Java.use("android.app.Activity"); //console.log(Object.getOwnPropertyNames(Activity)); Activity.startActivity.overload('android.content.Intent').implementation=function(p1){ console.log("Hooking android.app.Activity.startActivity(p1) successfully,p1="+p1); console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new())); console.log(decodeURIComponent(p1.toUri(256))); this.startActivity(p1); } Activity.startActivity.overload('android.content.Intent', 'android.os.Bundle').implementation=function(p1,p2){ console.log("Hooking android.app.Activity.startActivity(p1,p2) successfully,p1="+p1+",p2="+p2); console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new())); console.log(decodeURIComponent(p1.toUri(256))); this.startActivity(p1,p2); } Activity.startService.overload('android.content.Intent').implementation=function(p1){ console.log("Hooking android.app.Activity.startService(p1) successfully,p1="+p1); console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new())); console.log(decodeURIComponent(p1.toUri(256))); this.startService(p1); } })Hook frida 实现 activity 跳转function jumpActivity() { Java.perform(function () { var context = Java.use("android.app.ActivityThread").currentApplication().getApplicationContext(); var intentClazz = Java.use("android.content.Intent"); var activityClazz = Java.use("ctrip.android.hotel.view.UI.inquire.HotelInquireActivity"); var intentObj = intentClazz.$new(context, activityClazz.class); intentObj.setFlags(0x10000000); context.startActivity(intentObj); console.log("startActivity"); }) }Hook frida 绕过 root 检测// $ frida -l antiroot.js -U -f com.example.app --no-pause // CHANGELOG by Pichaya Morimoto (p.morimoto@sth.sh): // - I added extra whitelisted items to deal with the latest versions // of RootBeer/Cordova iRoot as of August 6, 2019 // - The original one just fucked up (kill itself) if Magisk is installed lol // Credit & Originally written by: https://codeshare.frida.re/@dzonerzy/fridantiroot/ // If this isn't working in the future, check console logs, rootbeer src, or libtool-checker.so Java.perform(function() { var RootPackages = ["com.noshufou.android.su", "com.noshufou.android.su.elite", "eu.chainfire.supersu", "com.koushikdutta.superuser", "com.thirdparty.superuser", "com.yellowes.su", "com.koushikdutta.rommanager", "com.koushikdutta.rommanager.license", "com.dimonvideo.luckypatcher", "com.chelpus.lackypatch", "com.ramdroid.appquarantine", "com.ramdroid.appquarantinepro", "com.devadvance.rootcloak", "com.devadvance.rootcloakplus", "de.robv.android.xposed.installer", "com.saurik.substrate", "com.zachspong.temprootremovejb", "com.amphoras.hidemyroot", "com.amphoras.hidemyrootadfree", "com.formyhm.hiderootPremium", "com.formyhm.hideroot", "me.phh.superuser", "eu.chainfire.supersu.pro", "com.kingouser.com", "com.android.vending.billing.InAppBillingService.COIN","com.topjohnwu.magisk" ]; var RootBinaries = ["su", "busybox", "supersu", "Superuser.apk", "KingoUser.apk", "SuperSu.apk","magisk"]; var RootProperties = { "ro.build.selinux": "1", "ro.debuggable": "0", "service.adb.root": "0", "ro.secure": "1" }; var RootPropertiesKeys = []; for (var k in RootProperties) RootPropertiesKeys.push(k); var PackageManager = Java.use("android.app.ApplicationPackageManager"); var Runtime = Java.use('java.lang.Runtime'); var NativeFile = Java.use('java.io.File'); var String = Java.use('java.lang.String'); var SystemProperties = Java.use('android.os.SystemProperties'); var BufferedReader = Java.use('java.io.BufferedReader'); var ProcessBuilder = Java.use('java.lang.ProcessBuilder'); var StringBuffer = Java.use('java.lang.StringBuffer'); var loaded_classes = Java.enumerateLoadedClassesSync(); send("Loaded " + loaded_classes.length + " classes!"); var useKeyInfo = false; var useProcessManager = false; send("loaded: " + loaded_classes.indexOf('java.lang.ProcessManager')); if (loaded_classes.indexOf('java.lang.ProcessManager') != -1) { try { //useProcessManager = true; //var ProcessManager = Java.use('java.lang.ProcessManager'); } catch (err) { send("ProcessManager Hook failed: " + err); } } else { send("ProcessManager hook not loaded"); } var KeyInfo = null; if (loaded_classes.indexOf('android.security.keystore.KeyInfo') != -1) { try { //useKeyInfo = true; //var KeyInfo = Java.use('android.security.keystore.KeyInfo'); } catch (err) { send("KeyInfo Hook failed: " + err); } } else { send("KeyInfo hook not loaded"); } PackageManager.getPackageInfo.overload('java.lang.String', 'int').implementation = function(pname, flags) { var shouldFakePackage = (RootPackages.indexOf(pname) > -1); if (shouldFakePackage) { send("Bypass root check for package: " + pname); pname = "set.package.name.to.a.fake.one.so.we.can.bypass.it"; } return this.getPackageInfo.call(this, pname, flags); }; NativeFile.exists.implementation = function() { var name = NativeFile.getName.call(this); var shouldFakeReturn = (RootBinaries.indexOf(name) > -1); if (shouldFakeReturn) { send("Bypass return value for binary: " + name); return false; } else { return this.exists.call(this); } }; var exec = Runtime.exec.overload('[Ljava.lang.String;'); var exec1 = Runtime.exec.overload('java.lang.String'); var exec2 = Runtime.exec.overload('java.lang.String', '[Ljava.lang.String;'); var exec3 = Runtime.exec.overload('[Ljava.lang.String;', '[Ljava.lang.String;'); var exec4 = Runtime.exec.overload('[Ljava.lang.String;', '[Ljava.lang.String;', 'java.io.File'); var exec5 = Runtime.exec.overload('java.lang.String', '[Ljava.lang.String;', 'java.io.File'); exec5.implementation = function(cmd, env, dir) { if (cmd.indexOf("getprop") != -1 || cmd == "mount" || cmd.indexOf("build.prop") != -1 || cmd == "id" || cmd == "sh") { var fakeCmd = "grep"; send("Bypass " + cmd + " command"); return exec1.call(this, fakeCmd); } if (cmd == "su") { var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"; send("Bypass " + cmd + " command"); return exec1.call(this, fakeCmd); } if (cmd == "which") { var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"; send("Bypass which command"); return exec1.call(this, fakeCmd); } return exec5.call(this, cmd, env, dir); }; exec4.implementation = function(cmdarr, env, file) { for (var i = 0; i < cmdarr.length; i = i + 1) { var tmp_cmd = cmdarr[i]; if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd == "mount" || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd == "id" || tmp_cmd == "sh") { var fakeCmd = "grep"; send("Bypass " + cmdarr + " command"); return exec1.call(this, fakeCmd); } if (tmp_cmd == "su") { var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"; send("Bypass " + cmdarr + " command"); return exec1.call(this, fakeCmd); } } return exec4.call(this, cmdarr, env, file); }; exec3.implementation = function(cmdarr, envp) { for (var i = 0; i < cmdarr.length; i = i + 1) { var tmp_cmd = cmdarr[i]; if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd == "mount" || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd == "id" || tmp_cmd == "sh") { var fakeCmd = "grep"; send("Bypass " + cmdarr + " command"); return exec1.call(this, fakeCmd); } if (tmp_cmd == "su") { var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"; send("Bypass " + cmdarr + " command"); return exec1.call(this, fakeCmd); } } return exec3.call(this, cmdarr, envp); }; exec2.implementation = function(cmd, env) { if (cmd.indexOf("getprop") != -1 || cmd == "mount" || cmd.indexOf("build.prop") != -1 || cmd == "id" || cmd == "sh") { var fakeCmd = "grep"; send("Bypass " + cmd + " command"); return exec1.call(this, fakeCmd); } if (cmd == "su") { var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"; send("Bypass " + cmd + " command"); return exec1.call(this, fakeCmd); } return exec2.call(this, cmd, env); }; exec.implementation = function(cmd) { for (var i = 0; i < cmd.length; i = i + 1) { var tmp_cmd = cmd[i]; if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd == "mount" || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd == "id" || tmp_cmd == "sh") { var fakeCmd = "grep"; send("Bypass " + cmd + " command"); return exec1.call(this, fakeCmd); } if (tmp_cmd == "su") { var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"; send("Bypass " + cmd + " command"); return exec1.call(this, fakeCmd); } } return exec.call(this, cmd); }; exec1.implementation = function(cmd) { if (cmd.indexOf("getprop") != -1 || cmd == "mount" || cmd.indexOf("build.prop") != -1 || cmd == "id" || cmd == "sh") { var fakeCmd = "grep"; send("Bypass " + cmd + " command"); return exec1.call(this, fakeCmd); } if (cmd == "su") { var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"; send("Bypass " + cmd + " command"); return exec1.call(this, fakeCmd); } return exec1.call(this, cmd); }; String.contains.implementation = function(name) { if (name == "test-keys") { send("Bypass test-keys check"); return false; } return this.contains.call(this, name); }; var get = SystemProperties.get.overload('java.lang.String'); get.implementation = function(name) { if (RootPropertiesKeys.indexOf(name) != -1) { send("Bypass " + name); return RootProperties[name]; } return this.get.call(this, name); }; Interceptor.attach(Module.findExportByName("libc.so", "fopen"), { onEnter: function(args) { var path1 = Memory.readCString(args[0]); var path = path1.split("/"); var executable = path[path.length - 1]; var shouldFakeReturn = (RootBinaries.indexOf(executable) > -1) if (shouldFakeReturn) { Memory.writeUtf8String(args[0], "/ggezxxx"); send("Bypass native fopen >> "+path1); } }, onLeave: function(retval) { } }); Interceptor.attach(Module.findExportByName("libc.so", "fopen"), { onEnter: function(args) { var path1 = Memory.readCString(args[0]); var path = path1.split("/"); var executable = path[path.length - 1]; var shouldFakeReturn = (RootBinaries.indexOf(executable) > -1) if (shouldFakeReturn) { Memory.writeUtf8String(args[0], "/ggezxxx"); send("Bypass native fopen >> "+path1); } }, onLeave: function(retval) { } }); Interceptor.attach(Module.findExportByName("libc.so", "system"), { onEnter: function(args) { var cmd = Memory.readCString(args[0]); send("SYSTEM CMD: " + cmd); if (cmd.indexOf("getprop") != -1 || cmd == "mount" || cmd.indexOf("build.prop") != -1 || cmd == "id") { send("Bypass native system: " + cmd); Memory.writeUtf8String(args[0], "grep"); } if (cmd == "su") { send("Bypass native system: " + cmd); Memory.writeUtf8String(args[0], "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"); } }, onLeave: function(retval) { } }); /* TO IMPLEMENT: Exec Family int execl(const char *path, const char *arg0, ..., const char *argn, (char *)0); int execle(const char *path, const char *arg0, ..., const char *argn, (char *)0, char *const envp[]); int execlp(const char *file, const char *arg0, ..., const char *argn, (char *)0); int execlpe(const char *file, const char *arg0, ..., const char *argn, (char *)0, char *const envp[]); int execv(const char *path, char *const argv[]); int execve(const char *path, char *const argv[], char *const envp[]); int execvp(const char *file, char *const argv[]); int execvpe(const char *file, char *const argv[], char *const envp[]); */ BufferedReader.readLine.overload().implementation = function() { var text = this.readLine.call(this); if (text === null) { // just pass , i know it's ugly as hell but test != null won't work :( } else { var shouldFakeRead = (text.indexOf("ro.build.tags=test-keys") > -1); if (shouldFakeRead) { send("Bypass build.prop file read"); text = text.replace("ro.build.tags=test-keys", "ro.build.tags=release-keys"); } } return text; }; var executeCommand = ProcessBuilder.command.overload('java.util.List'); ProcessBuilder.start.implementation = function() { var cmd = this.command.call(this); var shouldModifyCommand = false; for (var i = 0; i < cmd.size(); i = i + 1) { var tmp_cmd = cmd.get(i).toString(); if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd.indexOf("mount") != -1 || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd.indexOf("id") != -1) { shouldModifyCommand = true; } } if (shouldModifyCommand) { send("Bypass ProcessBuilder " + cmd); this.command.call(this, ["grep"]); return this.start.call(this); } if (cmd.indexOf("su") != -1) { send("Bypass ProcessBuilder " + cmd); this.command.call(this, ["justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"]); return this.start.call(this); } return this.start.call(this); }; if (useProcessManager) { var ProcManExec = ProcessManager.exec.overload('[Ljava.lang.String;', '[Ljava.lang.String;', 'java.io.File', 'boolean'); var ProcManExecVariant = ProcessManager.exec.overload('[Ljava.lang.String;', '[Ljava.lang.String;', 'java.lang.String', 'java.io.FileDescriptor', 'java.io.FileDescriptor', 'java.io.FileDescriptor', 'boolean'); ProcManExec.implementation = function(cmd, env, workdir, redirectstderr) { var fake_cmd = cmd; for (var i = 0; i < cmd.length; i = i + 1) { var tmp_cmd = cmd[i]; if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd == "mount" || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd == "id") { var fake_cmd = ["grep"]; send("Bypass " + cmdarr + " command"); } if (tmp_cmd == "su") { var fake_cmd = ["justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"]; send("Bypass " + cmdarr + " command"); } } return ProcManExec.call(this, fake_cmd, env, workdir, redirectstderr); }; ProcManExecVariant.implementation = function(cmd, env, directory, stdin, stdout, stderr, redirect) { var fake_cmd = cmd; for (var i = 0; i < cmd.length; i = i + 1) { var tmp_cmd = cmd[i]; if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd == "mount" || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd == "id") { var fake_cmd = ["grep"]; send("Bypass " + cmdarr + " command"); } if (tmp_cmd == "su") { var fake_cmd = ["justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"]; send("Bypass " + cmdarr + " command"); } } return ProcManExecVariant.call(this, fake_cmd, env, directory, stdin, stdout, stderr, redirect); }; } if (useKeyInfo) { KeyInfo.isInsideSecureHardware.implementation = function() { send("Bypass isInsideSecureHardware"); return true; } } });Hook frida 强制在主线程运行针对使用一些方法的时候出现报错 on a thread that has not called Looper.prepare()强制让代码运行在主线程中Java.perform(function() { var Toast = Java.use('android.widget.Toast'); var currentApplication = Java.use('android.app.ActivityThread').currentApplication(); var context = currentApplication.getApplicationContext(); Java.scheduleOnMainThread(function() { Toast.makeText(context, "Hello World", Toast.LENGTH_LONG.value).show(); }) })Hook frida 指定方法中过滤打印function hook_lnf() { var activate = false; Java.perform(function(){ var hashmapClass = Java.use("java.util.HashMap"); hashmapClass.put.implementation = function(key,value){ if (activate){ console.log("key:", key, "value:", value); } return this.put(key,value); }; }); Java.perform(function () { var lnfClazz = Java.use("tb.lnf"); lnfClazz.a.overload('java.util.HashMap', 'java.util.HashMap', 'java.lang.String', 'java.lang.String', 'boolean').implementation = function (hashmap, hashmap2, str, str2, z) { printHashMap("hashmap", hashmap); printHashMap("hashmap2", hashmap2); console.log("str", str); console.log("str2", str2); console.log("boolean", z); activate = true; var result = this.a(hashmap, hashmap2, str, str2, z); activate = false printHashMap("result", result); return result; }; }) }Hook 禁止 app 退出function hookExit(){ Java.perform(function(){ console.log("[*] Starting hook exit"); var exitClass = Java.use("java.lang.System"); exitClass.exit.implementation = function(){ console.log("[*] System.exit.called"); } console.log("[*] hooking calls to System.exit"); }) } setImmediate(hookExit);Hook 修改设备参数// frida hook 修改设备参数 Java.perform(function() { var TelephonyManager = Java.use("android.telephony.TelephonyManager"); //IMEI hook TelephonyManager.getDeviceId.overload().implementation = function () { console.log("[*]Called - getDeviceId()"); var temp = this.getDeviceId(); console.log("real IMEI: "+temp); return "867979021642856"; }; // muti IMEI TelephonyManager.getDeviceId.overload('int').implementation = function (p) { console.log("[*]Called - getDeviceId(int) param is"+p); var temp = this.getDeviceId(p); console.log("real IMEI "+p+": "+temp); return "867979021642856"; }; //IMSI hook TelephonyManager.getSimSerialNumber.overload().implementation = function () { console.log("[*]Called - getSimSerialNumber(String)"); var temp = this.getSimSerialNumber(); console.log("real IMSI: "+temp); return "123456789"; }; ////////////////////////////////////// //ANDOID_ID hook var Secure = Java.use("android.provider.Settings$Secure"); Secure.getString.implementation = function (p1,p2) { if(p2.indexOf("android_id")<0) return this.getString(p1,p2); console.log("[*]Called - get android_ID, param is:"+p2); var temp = this.getString(p1,p2); console.log("real Android_ID: "+temp); return "844de23bfcf93801"; } //android的hidden API，需要通过反射调用 var SP = Java.use("android.os.SystemProperties"); SP.get.overload('java.lang.String').implementation = function (p1) { var tmp = this.get(p1); console.log("[*]"+p1+" : "+tmp); return tmp; } SP.get.overload('java.lang.String', 'java.lang.String').implementation = function (p1,p2) { var tmp = this.get(p1,p2) console.log("[*]"+p1+","+p2+" : "+tmp); return tmp; } // hook MAC var wifi = Java.use("android.net.wifi.WifiInfo"); wifi.getMacAddress.implementation = function () { var tmp = this.getMacAddress(); console.log("[*]real MAC: "+tmp); return tmp; } })Hook 打印请求调用栈var class_Socket = Java.use("java.net.Socket"); class_Socket.getOutputStream.overload().implementation = function(){ send("getOutputSteam"); var result = this.getOutputStream(); var bt = Java.use("android.util.Log").getStackTraceString( Java.use("java.lang.Exception").$new(); ) console.log("Backtrace:" + bt); send(result); return result; }Hook UI thread 注入Java.perform(function() { var Toast = Java.use('android.widget.Toast'); var currentApplication = Java.use('android.app.ActivityThread').currentApplication(); var context = currentApplication.getApplicationContext(); Java.scheduleOnMainThread(function() { Toast.makeText(context, "Hello World", Toast.LENGTH_LONG.value).show(); }) })常用打印转换//工具相关函数 var base64EncodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', base64DecodeChars = new Array((-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), 62, (-1), (-1), (-1), 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, (-1), (-1), (-1), (-1), (-1), (-1), (-1), 0, 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, (-1), (-1), (-1), (-1), (-1), (-1), 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, (-1), (-1), (-1), (-1), (-1)); // base64 解码 function stringToBase64(e) { var r, a, c, h, o, t; for (c = e.length, a = 0, r = ''; a < c;) { if (h = 255 & e.charCodeAt(a++), a == c) { r += base64EncodeChars.charAt(h >> 2), r += base64EncodeChars.charAt((3 & h) << 4), r += '=='; break } if (o = e.charCodeAt(a++), a == c) { r += base64EncodeChars.charAt(h >> 2), r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4), r += base64EncodeChars.charAt((15 & o) << 2), r += '='; break } t = e.charCodeAt(a++), r += base64EncodeChars.charAt(h >> 2), r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4), r += base64EncodeChars.charAt((15 & o) << 2 | (192 & t) >> 6), r += base64EncodeChars.charAt(63 & t) } return r } // base64 编码 function base64ToString(e) { var r, a, c, h, o, t, d; for (t = e.length, o = 0, d = ''; o < t;) { do r = base64DecodeChars[255 & e.charCodeAt(o++)]; while (o < t && r == -1); if (r == -1) break; do a = base64DecodeChars[255 & e.charCodeAt(o++)]; while (o < t && a == -1); if (a == -1) break; d += String.fromCharCode(r << 2 | (48 & a) >> 4); do { if (c = 255 & e.charCodeAt(o++), 61 == c) return d; c = base64DecodeChars[c] } while (o < t && c == -1); if (c == -1) break; d += String.fromCharCode((15 & a) << 4 | (60 & c) >> 2); do { if (h = 255 & e.charCodeAt(o++), 61 == h) return d; h = base64DecodeChars[h] } while (o < t && h == -1); if (h == -1) break; d += String.fromCharCode((3 & c) << 6 | h) } return d } // hex 字符转 base64 function hexToBase64(str) { return base64Encode(String.fromCharCode.apply(null, str.replace(/\r|\n/g, "").replace(/([\da-fA-F]{2}) ?/g, "0x$1 ").replace(/ +$/, "").split(" "))); } // base64 转 hex function base64ToHex(str) { for (var i = 0, bin = base64Decode(str.replace(/[ \r\n]+$/, "")), hex = []; i < bin.length; ++i) { var tmp = bin.charCodeAt(i).toString(16); if (tmp.length === 1) tmp = "0" + tmp; hex[hex.length] = tmp; } return hex.join(""); } 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; } function bytesToHex(arr) { var str = ''; var k, j; for (var i = 0; i < arr.length; i++) { k = arr[i]; j = k; if (k < 0) { j = k + 256; } if (j < 16) { str += "0"; } str += j.toString(16); } return str; } function stringToHex(str) { var val = ""; for (var i = 0; i < str.length; i++) { if (val == "") val = str.charCodeAt(i).toString(16); else val += str.charCodeAt(i).toString(16); } return val } 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; } //将byte[]转成String的方法 function bytesToString(arr) { var str = ''; arr = new Uint8Array(arr); for (var i in arr) { str += String.fromCharCode(arr[i]); } return str; } function bytesToBase64(e) { var r, a, c, h, o, t; for (c = e.length, a = 0, r = ''; a < c;) { if (h = 255 & e[a++], a == c) { r += base64EncodeChars.charAt(h >> 2), r += base64EncodeChars.charAt((3 & h) << 4), r += '=='; break } if (o = e[a++], a == c) { r += base64EncodeChars.charAt(h >> 2), r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4), r += base64EncodeChars.charAt((15 & o) << 2), r += '='; break } t = e[a++], r += base64EncodeChars.charAt(h >> 2), r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4), r += base64EncodeChars.charAt((15 & o) << 2 | (192 & t) >> 6), r += base64EncodeChars.charAt(63 & t) } return r } function base64ToBytes(e) { var r, a, c, h, o, t, d; for (t = e.length, o = 0, d = []; o < t;) { do r = base64DecodeChars[255 & e.charCodeAt(o++)]; while (o < t && r == -1); if (r == -1) break; do a = base64DecodeChars[255 & e.charCodeAt(o++)]; while (o < t && a == -1); if (a == -1) break; d.push(r << 2 | (48 & a) >> 4); do { if (c = 255 & e.charCodeAt(o++), 61 == c) return d; c = base64DecodeChars[c] } while (o < t && c == -1); if (c == -1) break; d.push((15 & a) << 4 | (60 & c) >> 2); do { if (h = 255 & e.charCodeAt(o++), 61 == h) return d; h = base64DecodeChars[h] } while (o < t && h == -1); if (h == -1) break; d.push((3 & c) << 6 | h) } return d }object 数组中嵌套 string 数组var string1 = Java.use("java.lang.String").$new("123"); var string2 = Java.use("java.lang.String").$new(""); // var objarr0 = Java.array("Ljava.lang.String;", [string1, string2]); var Ref_arr = Java.use('java.lang.reflect.Array') var stringClass = Java.use("java.lang.String").class var arg1 = Ref_arr.newInstance(stringClass, 2); Ref_arr.set(arg1, 0, string1); Ref_arr.set(arg1, 1, string2); var objarr1 = Java.use("java.lang.String").$new("24717361"); var objarr2 = Java.use("java.lang.Integer").$new(19); var objarr3 = Java.use("java.lang.String").$new(""); var objarr = Java.array("Ljava.lang.Object;", [arg1, objarr1, objarr2, objarr3]);frida 主动加载 dex 并调用其中方法首先将要使用的 java 类写出来, 例如, 自定义的 base64 码表的 encode 和 decode 方法;class Base64DIY { private static char[] base64Code = {'i', '5', 'j', 'L', 'W', '7', 'S', '0', 'G', 'X', '6', 'u', 'f', '1', 'c', 'v', '3', 'n', 'y', '4', 'q', '8', 'e', 's', '2', 'Q', '+', 'b', 'd', 'k', 'Y', 'g', 'K', 'O', 'I', 'T', '/', 't', 'A', 'x', 'U', 'r', 'F', 'l', 'V', 'P', 'z', 'h', 'm', 'o', 'w', '9', 'B', 'H', 'C', 'M', 'D', 'p', 'E', 'a', 'J', 'R', 'Z', 'N'}; private static int[] toInt = new int[128]; //存储的是与base64编码对应的索引 static { for (int i = 0; i < base64Code.length; i++) { toInt[base64Code[i]] = i; } } //主函数 public static void main(String[] args) { String str = "0123456789bcdef"; String str2 = toBase64(str.getBytes()); System.out.println(str2); System.out.println(deBase64(str2)); } //Base64编码 public static String toBase64(byte[] byteArr){ //判空 if(byteArr == null || byteArr.length == 0){ return null; } //确定字符数组的长度 char[] chars = new char[(byteArr.length + 2) / 3 * 4]; int i = 0; int count = 0; while(i < byteArr.length){ //获取原始数据的ascii码值 byte b0 = byteArr[i++]; byte b1 = (i < byteArr.length) ? byteArr[i++] : 0; byte b2 = (i < byteArr.length) ? byteArr[i++] : 0; //转化为对应的base数 /** * 这是3位转4位 * 第一位 右移两位高位补0没问题 * 第二位 b0左移到高4位低四位补0 b1 右移到低四位 结合就是b0原本的低四位 + b1的高四位 * 这里会有问题？ 我们只要b0的最后两位和b1的高四位， b0左移4位高2位不一定会是00 * “& 0x3f ”的作用就是保证高2位是00 * 第三位第四位同理 * */ chars[count++] = base64Code[(b0 >> 2) & 0x3f]; chars[count++] = base64Code[((b0 << 4) | (b1 >> 4)) & 0x3f]; chars[count++] = base64Code[((b1 << 2) | (b2 >> 6)) & 0x3f]; chars[count++] = base64Code[b2 & 0x3f]; } //添加'=' case渗透 switch (byteArr.length % 3){ case 1 : chars[--count] = '='; case 2 : chars[--count] = '='; } return new String(chars); } //Base64解码 public static String deBase64(String str){ //先判空 if(str == null || str.length() == 0){ return str; } int tempNum = str.endsWith("==") ? 2 : str.endsWith("=") ? 1 : 0; //判断字符串结尾有几个'=' byte[] bytes = new byte[str.length() * 3 / 4 - tempNum]; //删除对应个数 int index = 0; //逆序读出明文 for(int i = 0;i < str.length();i++){ int c0 = toInt[str.charAt(i++)]; //Base64编码对应的索引 int c1 = toInt[str.charAt(i++)]; bytes[index++] = (byte) ((c0 << 2) | (c1 >> 4)); if(index >= bytes.length){ return new String(bytes); } int c2 = toInt[str.charAt(i++)]; bytes[index++] = (byte)((c1 << 4) | (c2 >> 2)); if(index >= bytes.length){ return new String(bytes); } int c3 = toInt[str.charAt(i)]; bytes[index++] = (byte) ((c2 << 6) | c3); } return new String(bytes); } }当前文件最好直接放在 src 文件目录下, 这样在执行命令的时候不会出现无法找到的情况;在 idea 中编译运行, 从 .java文件转换成.class文件; 找到对应的Base64DIY.class文件;将.class文件转为.jar文件: jar -cvf ddex.jar Base64DIY.class将.jar文件转为 .dex文件: ~/Library/Android/sdk/build-tools/28.0.3/dx --dex--output=ddex.dex ddex.jar将 .dex文件推入手机中 adb push ddex.dex /data/local/tmp/ddex.dex给.dex文件添加权限: chmod 777 ddex.dex在 frida 脚本中可以 hook 并调用自定义 dex 文件中的方法;///<reference path='./index.d.ts'/> function hook(){ Java.perform(function () { var ddex = Java.openClassFile("/data/local/tmp/ddex.dex"); ddex.load(); var clazz = Java.use("Base64DIY"); var input = "0123456789bcdef"; var encodeBase64 = clazz.toBase64(Java.use("java.lang.String").$new(input).getBytes()); console.log("encodeBase64", encodeBase64); }) } function main(){ hook(); } setImmediate(main)检测判断是否是 rpc 调用looperclazz = (*env)->FindClass(env, &xmmword_39010);// android/os/Looper myLooper_methodID = (*env)->GetStaticMethodID(env, looperclazz, &qword_39028, &xmmword_39040);// myLooper if ( !CallStaticObjectMethodV_(env, (__int64)looperclazz, (__int64)myLooper_methodID) )在 so 中调用android.os.Looper.myLooper(), 如果是正常的调用, 则通过CallStaticObjectMethodV调用的结果为非 0; 如果是通过 frida 的主动调用, 则返回结果为 0; 因为 frida 的主动调用不在主线程中; 可以作为一个主动调用的检测点frida 注册接口function hook_FridaActivity9(){ Java.perform(function () { // $接口 var Frida9Interface = Java.use("com.github.lastingyang.androiddemo.Activity.FridaActivity9$Frida9Interface"); console.log("Frida9Interface", Frida9Interface); var Frida9InterfaceImpl = Java.registerClass({ name: "com.github.lastingyang.androiddemo.Activity.FridaActivity9.FridaInterfaceImpl", implements: [Frida9Interface], methods: { check() { console.log("FridaInterfaceImpl.check"); return true; } } }); var FridaActivity9 = Java.use("com.github.lastingyang.androiddemo.Activity.FridaActivity9"); FridaActivity9.getInstance.implementation = function () { console.log("FridaActivity9.getInstance"); return Frida9InterfaceImpl.$new(); } }) }frida hook thread 打印调用栈通过 hook thread 打印出调用栈, 可以配合 r0capture 对发包位置进行回溯function printJavaStack(tag) { Java.perform(function () { console.log(tag + "\n" + Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new())); }); } var hook_thread = function(){ Java.perform(function(){ var Thread = Java.use("java.lang.Thread"); Thread.init.implementation = function(arg0, arg1, arg2, arg3){ var res = this.init(arg0, arg1, arg2, arg3); var threadid = this.getId(); var target = this.target.value; if (target){ var className = target.$className; console.log("\nRunnable classname ==>", className, threadid); printJavaStack("Runnable " + threadid); }else{ var className = this.$className; console.log("\nThe Thread classname ==>", className, threadid); printJavaStack("The Thread " + threadid); } return res; } Thread.run.implementation = function(){ var threadid = this.getId(); var className = this.$className; console.log("The Thread run classname ==>", className, threadid); return this.run(); } }); } function main() { hook_thread(); } setImmediate(main);本条博客转载自：凡墙总是门
验证码服务平台 谷歌人机 ReCaptcha（v2/v3 通用版）, 直接返回 token ReCaptcha（v2/v3 企业版）, 直接返回 token ReCaptcha（steam）, 直接返回 token CloudFlare 盾通用版, 返回 cf_clearance、__cf_bm 、正确响应源码 html、验证流程使用的 tls 指纹 Incapsula 盾 reese84 通用版, 返回 reese84 cookie 或 solution 参数 Incapsula 盾 __utmvc 通用版, 服务器直接无感验证 或 __utmvc cookie Hcaptcha 通用版, 直接返回 generated_pass_UUID 直接返回 _abck tls 转发接口, 针对校验 ja3、http2 等指纹（akamai/cloudflare）的接口 更多支持后续敬请期待..... 注册连接：https://3ii.cc/V2J 新用户注册加群即送10000点余额 新用户注册加群即送10000点余额 新用户注册加群即送10000点余额IDA 判断 Thumb 指令集和 Arm 指令集IDA - Options - General - number of opcode bytes - 设置为 4此时查看 IDA VIew 中 opcode 的长度, 如果出现 2 个字节和 4 个字节的, 说明为 thumb 指令集如果都是 4 个字节的, 说明是 arm 指令集;在 Thumb 指令集下, inline hook 的偏移地址需要进行 +1 操作;获取所有 JNI 函数函数地址通过 hook ArtMethod 的 RegisterNative 函数, 可以监控所有的静态注册和动态注册的 JNI 函数的地址;frida14 仅在Android 8.1下测试成功，其他版本可能需要重新修改适配 原作者: Simp1er const STD_STRING_SIZE = 3 * Process.pointerSize; class StdString { constructor() { this.handle = Memory.alloc(STD_STRING_SIZE); } dispose() { const [data, isTiny] = this._getData(); if (!isTiny) { Java.api.$delete(data); } } disposeToString() { const result = this.toString(); this.dispose(); return result; } toString() { const [data] = this._getData(); return data.readUtf8String(); } _getData() { const str = this.handle; const isTiny = (str.readU8() & 1) === 0; const data = isTiny ? str.add(1) : str.add(2 * Process.pointerSize).readPointer(); return [data, isTiny]; } } function prettyMethod(method_id, withSignature) { const result = new StdString(); Java.api['art::ArtMethod::PrettyMethod'](result, method_id, withSignature ? 1 : 0); return result.disposeToString(); } function readStdString(str) { if ((str.readU8() & 1) === 1) { // size LSB (=1) indicates if it's a long string return str.add(2 * Process.pointerSize).readPointer().readUtf8String(); } return str.add(1).readUtf8String(); } function attach(addr) { Interceptor.attach(addr, { onEnter: function (args) { this.arg0 = args[0]; // this }, onLeave: function (retval) { var modulemap = new ModuleMap() modulemap.update() var module = modulemap.find(retval) // var string = Memory.alloc(0x100) // ArtMethod_PrettyMethod(string, this.arg0, 1) if (module != null) { console.log('<' + module.name + '> method_name =>', prettyMethod(this.arg0, 1), ',offset=>', ptr(retval).sub(module.base), ',module_name=>', module.name) } else { console.log('<anonymous> method_name =>', readStdString(string), ', addr =>', ptr(retval)) } } }); } function hook_RegisterNative() { var libart = Process.findModuleByName('libart.so') var symbols = libart.enumerateSymbols() for (var i = 0; i < symbols.length; i++) { if (symbols[i].name.indexOf('RegisterNative') > -1 && symbols[i].name.indexOf('ArtMethod') > -1 && symbols[i].name.indexOf('RuntimeCallbacks') < 0) { //art::RuntimeCallbacks::RegisterNativeMethod(art::ArtMethod*, void const*, void**) attach(symbols[i].address) } } } function main() { hook_RegisterNative() } setImmediate(main)枚举内存中的 so 文件用于查看目标 module 是否被正常加载, 使用 Process.enumerateModules() 将当前加载的所有 so 文件打印出来function hook_native(){ var modules = Process.enumerateModules(); for (var i in modules){ var module = modules[i]; console.log(module.name); if (module.name.indexOf("target.so") > -1 ){ console.log(module.base); } } }获取指定 so 文件的基地址function hook_module() { var baseAddr = Module.findBaseAddress("libnative-lib.so"); console.log("baseAddr", baseAddr); }获取指定 so 文件的函数通过导出函数名定位 native 方法function hook_func_from_exports(){ var add_c_addr = Module.findExportByName("libnative-lib.so", "add_c"); console.log("add_c_addr is :",add_c_addr); }通过 symbols 符号定位 native 方法function find_func_from_symbols() { var NewStringUTF_addr = null; var symbols = Process.findModuleByName("libart.so").enumerateSymbols(); for (var i in symbols) { var symbol = symbols[i]; if (symbol.name.indexOf("art") >= 0 && symbol.name.indexOf("JNI") >= 0 && symbol.name.indexOf("CheckJNI") < 0 ){ if (symbol.name.indexOf("NewStringUTF") >= 0) { console.log("find target symbols", symbol.name, "address is ", symbol.address); NewStringUTF_addr = symbol.address; } } } console.log("NewStringUTF_addr is ", NewStringUTF_addr); Interceptor.attach(NewStringUTF_addr, { onEnter: function (args) { console.log("args0",args[0]) console.log("args0", args[0], hexdump(args[0])); console.log("args1", args[1], hexdump(args[1])); var env = Java.vm.tryGetEnv(); if (env != null) { // 直接读取 c 里面的 char console.log("Memory readCstring is :", Memory.readCString(args[1])); }else{ console.log("get env error"); } }, onLeave: function (returnResult) { console.log("result: ", Java.cast(returnResult, Java.use("java.lang.String"))); var env = Java.vm.tryGetEnv(); if (env != null) { var jstring = env.newStringUtf("修改返回值"); returnResult.replace(ptr(jstring)); } } }) }通过地址偏移 inline-hook 任意函数function main(){ // get base address of target so; var libnative_lib_addr = Module.findBaseAddress("libnative-lib.so"); console.log("base module addr ->", libnative_lib_addr); if (libnative_lib_addr){ var add_addr1 = Module.findExportByName("libnative-lib.so", "_Z5r0addii"); var add_addr2 = libnative_lib_addr.add(0x94B2 + 1); // 32位需要加1 console.log(add_addr1); console.log(add_addr2); } // 主动调用 var add1 = new NativeFunction(add_addr1, "int", ["int", "int"]); var add2 = new NativeFunction(add_addr2, "int", ["int", "int"]); console.log("add1 result is ->" + add1(10, 20)); console.log("add2 result is ->" + add2(10, 20)); } setImmediate(main); /* base module addr -> 0xd430b000 0xd43144b3 0xd43144b3 add1 result is ->30 add2 result is ->30 */通过 Intercept 拦截器打印 native 方法参数和返回值, 并修改返回值onEnter: 函数(args) : 回调函数, 给定一个参数 args, 用于读取或者写入参数作为 NativePointer 对象的指针;onLeave: 函数(retval) : 回调函数给定一个参数 retval, 该参数是包含原始返回值的 NativePointer 派生对象; 可以调用 retval.replace(1234) 以整数 1234 替换返回值, 或者调用retval.replace(ptr("0x1234")) 以替换为指针;注意: retval 对象会在 onLeave 调用中回收, 因此不要将其存储在回调之外使用, 如果需要存储包含的值, 需要制作深拷贝, 如 ptr(retval.toString())function find_func_from_exports() { var add_c_addr = Module.findExportByName("libnative-lib.so", "add_c"); console.log("add_c_addr is :",add_c_addr); // 添加拦截器 Interceptor.attach(add_c_addr,{ // 打印入参 onEnter: function (args) { console.log("add_c called"); console.log("arg1:",args[0].toInt32()); console.log("arg2", args[1].toInt32()); }, // 打印返回值 onLeave: function (returnValue) { console.log("add_c result is :", returnValue.toInt32()); // 修改返回值 returnValue.replace(100); } }) }通过 Intercept 拦截器替换原方法function frida_Interceptor() { Java.perform(function () { //这个c_getSum方法有两个int参数、返回结果为两个参数相加 //这里用NativeFunction函数自己定义了一个c_getSum函数 var add_method = new NativeFunction(Module.findExportByName('libhello.so', 'c_getSum'), 'int',['int','int']); //输出结果 那结果肯定就是 3 console.log("result:",add_method(1,2)); //这里对原函数的功能进行替换实现 Interceptor.replace(add_method, new NativeCallback(function (a, b) { //h不论是什么参数都返回123 return 123; }, 'int', ['int', 'int'])); //再次调用 则返回123 console.log("result:",add_method(1,2)); }); }inline hook通俗点说, inline hook就是通过内存地址, 进行 hook;function inline_hook() { var libnative_lib_addr = Module.findBaseAddress("libnative-lib.so"); if (libnative_lib_addr) { console.log("libnative_lib_addr:", libnative_lib_addr); var addr_101F4 = libnative_lib_addr.add(0x102BC); console.log("addr_101F4:", addr_101F4); Java.perform(function () { Interceptor.attach(addr_101F4, { onEnter: function (args) { console.log("addr_101F4 OnEnter :", this.context.PC, this.context.x1, this.context.x5, this.context.x10); }, onLeave: function (retval) { console.log("retval is :", retval) } } ) }) } }so 层方法注册到 js 中, 主动调用new NativeFunction(address, returnType, argTypes[, options])address : 函数地址returnType : 指定返回类型argTypes : 数组指定参数类型类型可选: void, pointer, int, uint, long, ulong, char, uchar, float, double, int8, uint8, int16, int32, uint32, int64, uint64; 参照函数所需的 type 来定义即可;function invoke_native_func() { var baseAddr = Module.findBaseAddress("libnative-lib.so"); console.log("baseAddr", baseAddr); var offset = 0x0000A28C + 1; var add_c_addr = baseAddr.add(offset); var add_c_func = new NativeFunction(add_c_addr, "int", ["int","int"]); var result = add_c_func(1, 2); console.log(result); } Java.perform(function () { // 获取 so 文件基地址 var base = Module.findBaseAddress("libnative-lib.so"); // 获取目标函数偏移 var sub_834_addr = base.add(0x835) // thumb 需要 +1 // 使用 new NativeFunction 将函数注册到 js var sub_834 = new NativeFunction(sub_834_addr, 'pointer', ['pointer']); // 开辟内存, 创建入参 var arg0 = Memory.alloc(10); ptr(arg0).writeUtf8String("123"); var result = sub_834(arg0); console.log("result is :", hexdump(result)); })hook libart 中的 jni 方法jni 全部定在在 /system/lib(64)/libart.so 文件中, 通过枚举 symbols 筛选出指定的方法function hook_libart() { var GetStringUTFChars_addr = null; // jni 系统函数都在 libart.so 中 var module_libart = Process.findModuleByName("libart.so"); var symbols = module_libart.enumerateSymbols(); for (var i = 0; i < symbols.length; i++) { var name = symbols[i].name; if ((name.indexOf("JNI") >= 0) && (name.indexOf("CheckJNI") == -1) && (name.indexOf("art") >= 0)) { if (name.indexOf("GetStringUTFChars") >= 0) { console.log(name); // 获取到指定 jni 方法地址 GetStringUTFChars_addr = symbols[i].address; } } } Java.perform(function(){ Interceptor.attach(GetStringUTFChars_addr, { onEnter: function(args){ // console.log("args[0] is : ", args[0]); // console.log("args[1] is : ", args[1]); console.log("native args[1] is :",Java.vm.getEnv().getStringUtfChars(args[1],null).readCString()); console.log('GetStringUTFChars onEnter called from:\n' + Thread.backtrace(this.context, Backtracer.FUZZY) .map(DebugSymbol.fromAddress).join('\n') + '\n'); // console.log("native args[1] is :", Java.cast(args[1], Java.use("java.lang.String"))); // console.log("native args[1] is :", Memory.readCString(Java.vm.getEnv().getStringUtfChars(args[1],null))); }, onLeave: function(retval){ // retval const char* console.log("GetStringUTFChars onLeave : ", ptr(retval).readCString()); } }) }) }hook libc 中的系统方法/system/lib(64)/libc.so 导出的符号没有进行 namemanline , 直接过滤筛选即可// hook libc.so var pthread_create_addr = null; // console.log(JSON.stringify(Process.enumerateModules())); // Process.enumerateModules() 枚举加载的so文件 var symbols = Process.findModuleByName("libc.so").enumerateSymbols(); for (var i = 0; i < symbols.length; i++){ if (symbols[i].name === "pthread_create"){ // console.log("symbols name is -> " + symbols[i].name); // console.log("symbols address is -> " + symbols[i].address); pthread_create_addr = symbols[i].address; } } Interceptor.attach(pthread_create_addr,{ onEnter: function(args){ console.log("args is ->" + args[0], args[1], args[2],args[3]); }, onLeave: function(retval){ console.log(retval); } });libc.so 中方法替换// hook 检测frida 的方法 function main() { // var exports = Process.findModuleByName("libnative-lib.so").enumerateExports(); 导出 // var imports = Process.findModuleByName("libnative-lib.so").enumerateImports(); 导入 // var symbols = Process.findModuleByName("libnative-lib.so").enumerateSymbols(); 符号 var pthread_create_addr = null; var symbols = Process.getModuleByName("libc.so").enumerateSymbols(); for (var i = 0; i < symbols.length; i++) { var symbol = symbols[i]; if (symbol.name === "pthread_create") { pthread_create_addr = symbol.address; console.log("pthread_create name is ->", symbol.name); console.log("pthread_create address is ->", pthread_create_addr); } } Java.perform(function(){ // 定义方法 之后主动调用的时候使用 var pthread_create = new NativeFunction(pthread_create_addr, 'int', ['pointer', 'pointer','pointer','pointer']) Interceptor.replace(pthread_create_addr,new NativeCallback(function (a0, a1, a2, a3) { var result = null; var detect_frida_loop = Module.findExportByName("libnative-lib.so", "_Z17detect_frida_loopPv"); console.log("a0,a1,a2,a3 ->",a0,a1,a2,a3); if (String(a2) === String(detect_frida_loop)) { result = 0; console.log("阻止frida反调试启动"); } else { result = pthread_create(a0,a1,a2,a3); console.log("正常启动"); } return result; }, 'int', ['pointer', 'pointer','pointer','pointer'])); }) }hook native 调用栈Interceptor.attach(f, { onEnter: function (args) { console.log('RegisterNatives called from:\n' + Thread.backtrace(this.context, Backtracer.ACCURATE) .map(DebugSymbol.fromAddress).join('\n') + '\n'); } });jnitrace安装jnitrace: https://github.com/chame1eon/jnitracepython` : `pip install jnitrace基础用法启动命令jnitrace [options] -l libname packagename例如: jnitrace -l libnative-lib.so com.example.myapplication必要参数-l libname : 指定要trace的.so文件, 可以同时trace多个.so文件, 直接使用 来trace所有的.so文件; 如: -l libnative-lib.so -l libanother-lib.so or -l packagename : 指定要trace的package name可选参数-m: 指定是spawn还是attach-b: 指定是fuzzy还是accurate-i : 指定一个正则表达式来过滤出方法名, 例如: -i Get -i RegisterNatives 就只会打印出名字里包含Get或者RegisterNatives的JNI methods-e 和i相反，同样通过正则表达式来过滤，但这次会将指定的内容忽略掉-I trace导出的方法，jnitrace认为导出的函数应该是从Java端能够直接调用的函数，所以可以包括使用RegisterNatives来注册的函数，例如I stringFromJNI -I nativeMethod([B)V，就包括导出名里有stringFromJNI，以及使用RegisterNames来注册，并带有nativeMethod([B)V签名的函数。-o path/output.json，导出输出到文件里。-p path/to/script.js，用于在加载jnitrace脚本之前将指定路径的Frida脚本加载到目标进程中，这可以用于在jnitrace启动之前对抗反调试。-a path/to/script.js，用于在加载jnitrace脚本之后将指定路径的Frida脚本加载到目标进程中-ignore-env，不打印所有的JNIEnv函数-ignore-vm，不打印所有的JavaVM函数启动方式默认使用 spawn 启动, 可以通过 -m attach 设置通过 attach 启动jnitrace -m attach -l[libnative-lib.so](http://libnative-lib.so) com.kevin.demoso1设置回溯器默认情况下使用 accurate的精确模式来进行回溯, 可以通过 -b fuzzy 修改为模糊模式jnitrace -l [libnative-lib.so](http://libnative-lib.so) -b fuzzy com.kevin.demoso1监控指定规则的方法用于指定应该跟踪的方法名, 该选项可以多次提供;jnitrace -l libnative-lib.so -i RegisterNatives com.kevin.demoso1只过滤出RegisterNatives相关的内容忽略指定规则的方法用于指定在跟踪中应被忽略的方法名, 这个选项可以被多次提供;忽略以Find开头的所有方法;jnitrace -l libnative-lib.so -e ^Find com.kevin.demosojnitace 计算偏移地址0x8e4f3b1 是方法 initSN 方法的绝对地址0xd8e4e000 是 libmyjni.so 基地址使用使用 initSN()V的绝对地址 0xd8e4f3b1 减去 libmyjni.so 的基地址 0xd8e4e000 , 得到偏移 0x13B1g 进行跳转到 0x13B1 即可进入方法frida trace文档地址: https://frida.re/docs/frida-trace/optionsUsage: frida-trace [options] target Options: --version show program's version number and exit -h, --help show this help message and exit -D ID, --device=ID connect to device with the given ID -U, --usb connect to USB device -R, --remote connect to remote frida-server -H HOST, --host=HOST connect to remote frida-server on HOST -f FILE, --file=FILE spawn FILE -F, --attach-frontmost attach to frontmost application -n NAME, --attach-name=NAME attach to NAME -p PID, --attach-pid=PID attach to PID --stdio=inherit|pipe stdio behavior when spawning (defaults to “inherit”) --runtime=duk|v8 script runtime to use (defaults to “duk”) --debug enable the Node.js compatible script debugger -I MODULE, --include-module=MODULE include MODULE -X MODULE, --exclude-module=MODULE exclude MODULE -i FUNCTION, --include=FUNCTION include FUNCTION -x FUNCTION, --exclude=FUNCTION exclude FUNCTION -a MODULE!OFFSET, --add=MODULE!OFFSET add MODULE!OFFSET -T, --include-imports include program's imports -t MODULE, --include-module-imports=MODULE include MODULE imports -m OBJC_METHOD, --include-objc-method=OBJC_METHOD include OBJC_METHOD -M OBJC_METHOD, --exclude-objc-method=OBJC_METHOD exclude OBJC_METHOD -s DEBUG_SYMBOL, --include-debug-symbol=DEBUG_SYMBOL include DEBUG_SYMBOL -q, --quiet do not format output messages -d, --decorate Add module name to generated onEnter log statement -o OUTPUT, --output=OUTPUT dump messages to file基础使用frida-trace [options] packagename启动模式默认使用 attach 模式, 可以指定 -f packageName 使用 spawn 模式启动frida-trace -U -i strcmp -f com.gdufs.xman文件输出frida-trace -U -i "strcmp" -f com.gdufs.xman -o xman.json-o filepath 指定输出的文件路径, 方便内容过多时进行查看trace 任意 functionfrida-trace -U -i "strcmp" com.example.demoso1trace 任意 modulefrida-trace -U -I "libnative-lib.so" com.example.demoso1根据地址进行 tracefrida-trace -U -a "libnative-lib.so!0x9281" com.example.demoso1批量 trace源码地址: https://github.com/Pr0214/trace_nativesps: 需要切换到 frida14 版本1.将traceNatives.py丢进IDA plugins目录中在ida 的python console中运行如下命令即可找到plugins目录：os.path.join(idaapi.get_user_idadir(), "plugins")2.IDA中，Edit-Plugins-traceNatives –> IDA输出窗口就会显示如下字眼：使用方法如下： frida-trace -UF -O C:\Users\Lenovo\Desktop\2021\mt\libmtguard.txtfrida-hook-libart下载地址: https://github.com/lasting-yang/frida_hook_libarthook artfrida -U --no-pause -f package_name -l hook_art.jshook_RegisterNativesfrida -U --no-pause -f package_name -l hook_RegisterNatives.jshook_artmethodinit libext first timeadb push lib/libext64.so /data/local/tmp/libext64.so adb push lib/libext.so /data/local/tmp/libext.so adb shell su -c "cp /data/local/tmp/libext64.so /data/app/libext64.so" adb shell su -c "cp /data/local/tmp/libext.so /data/app/libext.so" adb shell su -c "chown 1000.1000 /data/app/libext*.so" adb shell su -c "chmod 777 /data/app/libext*.so" adb shell su -c "ls -al /data/app/libext*"use hook_artmethod.jsfrida -U --no-pause -f package_name -l hook_artmethod.js # or frida -U --no-pause -f package_name -l hook_artmethod.js > hook_artmethod.logfrida-fart-hook首先拷贝fart.so和fart64.so到/data/app目录下，并使用chmod 777 设置好权限,然后就可以使用了。如果目标 app 没有 sdcard 权限则需要手动添加; 或者可以修改 frida_fart_hook.js 中的源码, 将 savepath 改为 /data/data/应用包名/;该frida版fart是使用hook的方式实现的函数粒度的脱壳，仅仅是对类中的所有函数进行了加载，但依然可以解决绝大多数的抽取保护需要以spawn方式启动app，等待app进入Activity界面后，执行fart()函数即可。如app包名为com.example.test,则frida -U -f com.example.test -l frida_fart_hook.js --no-pause ，然后等待app进入主界面,执行fart()高级用法：如果发现某个类中的函数的CodeItem没有dump下来，可以调用dump(classname),传入要处理的类名，完成对该类下的所有函数体的dump,dump下来的函数体会追加到bin文件当中。frida 文件写入(frida/hook libc)frida api 写入文件function writeFile(){ var file = new File("/sdcard/reg.dat", "w"); file.write("content from frida"); file.flush(); file.close(); }frida 定义 NativeFunction 写入文件function writeFileNative(){ var addr_fopen = Module.findExportByName("libc.so", "fopen"); var addr_fputs = Module.findExportByName("libc.so", "fputs"); var addr_fclose = Module.findExportByName("libc.so", "fclose"); // 将 libc 的系统方法注册到 js 层 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"]); // 在 js 层主动调用 libc 的方法 // 不能直接将 js 的字符串传给 libc中的方法, 需要进行转换 var filename = Memory.allocUtf8String("/sdcard/reg.dat"); var open_mode = Memory.allocUtf8String("w"); var file = fopen(filename, open_mode); var buffer = Memory.allocUtf8String("content from frida"); var result = fputs(buffer, file); console.log("fputs ret: ", result); // 关闭文件 fclose(file); }hook 读写 std::stringfunction readStdString(str){ var isTiny = (str.readU8 & 1) === 0; if (isTiny){ return str.add(1).readUtf8String(); } return str.add(2 * Process.pointerSize).readPointer().readUtf8String(); } function writeStdString(str, content){ var isTiny = (str.readU8() & 1) === 0; if (isTiny){ str.add(1).writeUtf8String(content); }else{ str.add(2 * Process.pointerSize).readPointer().writeUtf8String(content); } }hook so 文件加载后马上 hook//第一种方式（针对较老的系统版本）<=android6 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(); }; } }); } //第二种方式（针对新系统版本） android 8.1 使用该方法 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 ... }hook libc killfunction replaceKILL(){ var kill_addr = Module.findExportByName("libc.so","kill"); Interceptor.replace(kill_addr, new NativeCallback(function(arg0, arg1){ console.log("arg0=> ", arg0); console.log("arg1=> ", arg1); console.log('libc.so!kill called from:\n' + Thread.backtrace(this.context, Backtracer.ACCURATE) .map(DebugSymbol.fromAddress).join('\n') + '\n'); },"int",["int","int"])) }hook init_array//应用以32位在64位终端环境下运行 //adb install --abi armeabi-v7a <path to apk> function get_call_function() { var call_function_addr = null; var symbols = Process.getModuleByName("linker").enumerateSymbols(); for (var m = 0; m < symbols.length; m++) { if (symbols[m].name == "__dl__ZL13call_functionPKcPFviPPcS2_ES0_") { call_function_addr = symbols[m].address; console.log("found call_function_addr => ", call_function_addr) hook_call_function(call_function_addr) } } } function hook_call_function(_call_function_addr){ console.log("hook call function begin!hooking address :=>",_call_function_addr) Interceptor.attach(_call_function_addr,{ onEnter:function(args){ if(args[2].readCString().indexOf("base.odex")<0){ console.log("============================") console.log("function_name =>",args[0].readCString()) var soPath = args[2].readCString() console.log("so path : =>",soPath) var soName = soPath.split("/").pop(); console.log("function offset =>","0x"+(args[1]-Module.findBaseAddress(soName)).toString(16)) console.log("============================") } },onLeave:function(retval){ } }) } setImmediate(get_call_function) function hook_constructor() { if (Process.pointerSize == 4) { var linker = Process.findModuleByName("linker"); } else { var linker = Process.findModuleByName("linker64"); } var addr_call_function =null; var addr_g_ld_debug_verbosity = null; var addr_async_safe_format_log = null; if (linker) { var symbols = linker.enumerateSymbols(); for (var i = 0; i < symbols.length; i++) { var name = symbols[i].name; if (name.indexOf("call_function") >= 0){ addr_call_function = symbols[i].address; } else if(name.indexOf("g_ld_debug_verbosity") >=0){ addr_g_ld_debug_verbosity = symbols[i].address; ptr(addr_g_ld_debug_verbosity).writeInt(2); } else if(name.indexOf("async_safe_format_log") >=0 && name.indexOf('va_list') < 0){ addr_async_safe_format_log = symbols[i].address; } } } if(addr_async_safe_format_log){ Interceptor.attach(addr_async_safe_format_log,{ onEnter: function(args){ this.log_level = args[0]; this.tag = ptr(args[1]).readCString() this.fmt = ptr(args[2]).readCString() if(this.fmt.indexOf("c-tor") >= 0 && this.fmt.indexOf('Done') < 0){ this.function_type = ptr(args[3]).readCString(), // func_type this.so_path = ptr(args[5]).readCString(); var strs = new Array(); //定义一数组 strs = this.so_path.split("/"); //字符分割 this.so_name = strs.pop(); this.func_offset = ptr(args[4]).sub(Module.findBaseAddress(this.so_name)) console.log("func_type:", this.function_type, '\nso_name:',this.so_name, '\nso_path:',this.so_path, '\nfunc_offset:',this.func_offset ); } }, onLeave: function(retval){ } }) } } function main() { hook_constructor(); } setImmediate(main);frida dumpdocument: https://github.com/lasting-yang/frida_dumpfrida dump sofunction 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.log("[name]:", libso.name); console.log("[base]:", libso.base); console.log("[size]:", ptr(libso.size)); console.log("[path]:", libso.path); 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); } }); }frida dump dexfunction get_self_process_name() { var openPtr = Module.getExportByName('libc.so', 'open'); var open = new NativeFunction(openPtr, 'int', ['pointer', 'int']); var readPtr = Module.getExportByName("libc.so", "read"); var read = new NativeFunction(readPtr, "int", ["int", "pointer", "int"]); var closePtr = Module.getExportByName('libc.so', 'close'); var close = new NativeFunction(closePtr, 'int', ['int']); var path = Memory.allocUtf8String("/proc/self/cmdline"); var fd = open(path, 0); if (fd != -1) { var buffer = Memory.alloc(0x1000); var result = read(fd, buffer, 0x1000); close(fd); result = ptr(buffer).readCString(); return result; } return "-1"; } function mkdir(path) { var mkdirPtr = Module.getExportByName('libc.so', 'mkdir'); var mkdir = new NativeFunction(mkdirPtr, 'int', ['pointer', 'int']); var opendirPtr = Module.getExportByName('libc.so', 'opendir'); var opendir = new NativeFunction(opendirPtr, 'pointer', ['pointer']); var closedirPtr = Module.getExportByName('libc.so', 'closedir'); var closedir = new NativeFunction(closedirPtr, 'int', ['pointer']); var cPath = Memory.allocUtf8String(path); var dir = opendir(cPath); if (dir != 0) { closedir(dir); return 0; } mkdir(cPath, 755); chmod(path); } function chmod(path) { var chmodPtr = Module.getExportByName('libc.so', 'chmod'); var chmod = new NativeFunction(chmodPtr, 'int', ['pointer', 'int']); var cPath = Memory.allocUtf8String(path); chmod(cPath, 755); } function dump_dex() { var libart = Process.findModuleByName("libart.so"); var addr_DefineClass = null; var symbols = libart.enumerateSymbols(); for (var index = 0; index < symbols.length; index++) { var symbol = symbols[index]; var symbol_name = symbol.name; //这个DefineClass的函数签名是Android9的 //_ZN3art11ClassLinker11DefineClassEPNS_6ThreadEPKcmNS_6HandleINS_6mirror11ClassLoaderEEERKNS_7DexFileERKNS9_8ClassDefE if (symbol_name.indexOf("ClassLinker") >= 0 && symbol_name.indexOf("DefineClass") >= 0 && symbol_name.indexOf("Thread") >= 0 && symbol_name.indexOf("DexFile") >= 0) { console.log(symbol_name, symbol.address); addr_DefineClass = symbol.address; } } var dex_maps = {}; var dex_count = 1; console.log("[DefineClass:]", addr_DefineClass); if (addr_DefineClass) { Interceptor.attach(addr_DefineClass, { onEnter: function(args) { var dex_file = args[5]; //ptr(dex_file).add(Process.pointerSize) is "const uint8_t* const begin_;" //ptr(dex_file).add(Process.pointerSize + Process.pointerSize) is "const size_t size_;" var base = ptr(dex_file).add(Process.pointerSize).readPointer(); var size = ptr(dex_file).add(Process.pointerSize + Process.pointerSize).readUInt(); if (dex_maps[base] == undefined) { dex_maps[base] = size; var magic = ptr(base).readCString(); if (magic.indexOf("dex") == 0) { var process_name = get_self_process_name(); if (process_name != "-1") { var dex_dir_path = "/data/data/" + process_name + "/files/dump_dex_" + process_name; mkdir(dex_dir_path); var dex_path = dex_dir_path + "/class" + (dex_count == 1 ? "" : dex_count) + ".dex"; console.log("[find dex]:", dex_path); var fd = new File(dex_path, "wb"); if (fd && fd != null) { dex_count++; var dex_buffer = ptr(base).readByteArray(size); fd.write(dex_buffer); fd.flush(); fd.close(); console.log("[dump dex]:", dex_path); } } } } }, onLeave: function(retval) {} }); } } var is_hook_libart = false; function hook_dlopen() { Interceptor.attach(Module.findExportByName(null, "dlopen"), { onEnter: function(args) { var pathptr = args[0]; if (pathptr !== undefined && pathptr != null) { var path = ptr(pathptr).readCString(); //console.log("dlopen:", path); if (path.indexOf("libart.so") >= 0) { this.can_hook_libart = true; console.log("[dlopen:]", path); } } }, onLeave: function(retval) { if (this.can_hook_libart && !is_hook_libart) { dump_dex(); is_hook_libart = true; } } }) Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), { onEnter: function(args) { var pathptr = args[0]; if (pathptr !== undefined && pathptr != null) { var path = ptr(pathptr).readCString(); //console.log("android_dlopen_ext:", path); if (path.indexOf("libart.so") >= 0) { this.can_hook_libart = true; console.log("[android_dlopen_ext:]", path); } } }, onLeave: function(retval) { if (this.can_hook_libart && !is_hook_libart) { dump_dex(); is_hook_libart = true; } } }); } setImmediate(dump_dex);frida dump dex classfunction get_self_process_name() { var openPtr = Module.getExportByName('libc.so', 'open'); var open = new NativeFunction(openPtr, 'int', ['pointer', 'int']); var readPtr = Module.getExportByName("libc.so", "read"); var read = new NativeFunction(readPtr, "int", ["int", "pointer", "int"]); var closePtr = Module.getExportByName('libc.so', 'close'); var close = new NativeFunction(closePtr, 'int', ['int']); var path = Memory.allocUtf8String("/proc/self/cmdline"); var fd = open(path, 0); if (fd != -1) { var buffer = Memory.alloc(0x1000); var result = read(fd, buffer, 0x1000); close(fd); result = ptr(buffer).readCString(); return result; } return "-1"; } function load_all_class() { if (Java.available) { Java.perform(function () { var DexFileclass = Java.use("dalvik.system.DexFile"); var BaseDexClassLoaderclass = Java.use("dalvik.system.BaseDexClassLoader"); var DexPathListclass = Java.use("dalvik.system.DexPathList"); Java.enumerateClassLoaders({ onMatch: function (loader) { try { var basedexclassloaderobj = Java.cast(loader, BaseDexClassLoaderclass); var pathList = basedexclassloaderobj.pathList.value; var pathListobj = Java.cast(pathList, DexPathListclass) var dexElements = pathListobj.dexElements.value; for (var index in dexElements) { var element = dexElements[index]; try { var dexfile = element.dexFile.value; var dexfileobj = Java.cast(dexfile, DexFileclass); console.log("dexFile:", dexfileobj); const classNames = []; const enumeratorClassNames = dexfileobj.entries(); while (enumeratorClassNames.hasMoreElements()) { var className = enumeratorClassNames.nextElement().toString(); classNames.push(className); try { loader.loadClass(className); } catch (error) { console.log("loadClass error:", error); } } } catch (error) { console.log("dexfile error:", error); } } } catch (error) { console.log("loader error:", error); } }, onComplete: function () { } }) console.log("load_all_class end."); }); } } var dex_maps = {}; function print_dex_maps() { for (var dex in dex_maps) { console.log(dex, dex_maps[dex]); } } function dump_dex() { load_all_class(); for (var base in dex_maps) { var size = dex_maps[base]; console.log(base); var magic = ptr(base).readCString(); if (magic.indexOf("dex") == 0) { var process_name = get_self_process_name(); if (process_name != "-1") { var dex_path = "/data/data/" + process_name + "/files/" + base.toString(16) + "_" + size.toString(16) + ".dex"; console.log("[find dex]:", dex_path); var fd = new File(dex_path, "wb"); if (fd && fd != null) { var dex_buffer = ptr(base).readByteArray(size); fd.write(dex_buffer); fd.flush(); fd.close(); console.log("[dump dex]:", dex_path); } } } } } function hook_dex() { var libart = Process.findModuleByName("libart.so"); var addr_DefineClass = null; var symbols = libart.enumerateSymbols(); for (var index = 0; index < symbols.length; index++) { var symbol = symbols[index]; var symbol_name = symbol.name; //这个DefineClass的函数签名是Android9的 //_ZN3art11ClassLinker11DefineClassEPNS_6ThreadEPKcmNS_6HandleINS_6mirror11ClassLoaderEEERKNS_7DexFileERKNS9_8ClassDefE if (symbol_name.indexOf("ClassLinker") >= 0 && symbol_name.indexOf("DefineClass") >= 0 && symbol_name.indexOf("Thread") >= 0 && symbol_name.indexOf("DexFile") >= 0) { console.log(symbol_name, symbol.address); addr_DefineClass = symbol.address; } } console.log("[DefineClass:]", addr_DefineClass); if (addr_DefineClass) { Interceptor.attach(addr_DefineClass, { onEnter: function (args) { var dex_file = args[5]; //ptr(dex_file).add(Process.pointerSize) is "const uint8_t* const begin_;" //ptr(dex_file).add(Process.pointerSize + Process.pointerSize) is "const size_t size_;" var base = ptr(dex_file).add(Process.pointerSize).readPointer(); var size = ptr(dex_file).add(Process.pointerSize + Process.pointerSize).readUInt(); if (dex_maps[base] == undefined) { dex_maps[base] = size; console.log("hook_dex:", base, size); } }, onLeave: function (retval) {} }); } } var is_hook_libart = false; function hook_dlopen() { Interceptor.attach(Module.findExportByName(null, "dlopen"), { onEnter: function (args) { var pathptr = args[0]; if (pathptr !== undefined && pathptr != null) { var path = ptr(pathptr).readCString(); //console.log("dlopen:", path); if (path.indexOf("libart.so") >= 0) { this.can_hook_libart = true; console.log("[dlopen:]", path); } } }, onLeave: function (retval) { if (this.can_hook_libart && !is_hook_libart) { hook_dex(); is_hook_libart = true; } } }) Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), { onEnter: function (args) { var pathptr = args[0]; if (pathptr !== undefined && pathptr != null) { var path = ptr(pathptr).readCString(); //console.log("android_dlopen_ext:", path); if (path.indexOf("libart.so") >= 0) { this.can_hook_libart = true; console.log("[android_dlopen_ext:]", path); } } }, onLeave: function (retval) { if (this.can_hook_libart && !is_hook_libart) { hook_dex(); is_hook_libart = true; } } }); } setImmediate(hook_dex);指针运算符和读写 APIhook so readPointer()Java.perform(function () { var libc_addr = Process.findModuleByName("libc.so").base; console.log("libc address is " + libc_addr); // 0x10 转为十进制为 16, 读取 console.log(libc_addr.readByteArray(0x10)); // readPointer(), 从此内存位置读取 NativePointer console.log("pointer size", Process.pointerSize); console.log("readPointer() is " + libc_addr.readPointer()); console.log("Memory.readPointer()" + Memory.readPointer(libc_addr.add(Process.pointerSize))); })hook so writePointer()Java.perform(function () { var libc_addr = Process.findModuleByName("libc.so").base; console.log("libc_addr : " + libc_addr); // 分配四个字节的空间地址 const r = Memory.alloc(4); // 将 libc_addr 指针写入刚申请的 r 中 r.writePointer(libc_addr); // 读取 r 指针的数据 var buffer = Memory.readByteArray(r, 4); console.log(buffer); }) //libc_addr : 0x7da7fdf000 // 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF // 00000000 00 f0 fd a7 ....hook so readS32(), readU32()Java.perform(function () { var libc_addr = Process.findModuleByName("libc.so").base; console.log(hexdump(libc_addr)); console.log(libc_addr.readS32(), (libc_addr.readS32()).toString(16)); console.log(libc_addr.readU32(), (libc_addr.readU32()).toString(16)); })hook so writeS32(), writeU32()Java.perform(function () { // 开辟四个字节的内存空间 const r = Memory.alloc(4); r.writeS32(0x12345678); console.log(r.readByteArray(0x10)); }) hook so readByteArray(), writeByteArray()Java.perform(function () { // 定义一个需要写入的字节数组 var arr = [ 0x72, 0x6F, 0x79, 0x73, 0x75, 0x65]; //这里申请以arr大小的内存空间 var r = Memory.alloc(arr.length); // 将 arr 写入 r 中 r.writeByteArray(arr); // Memory.writeByteArray(r, arr); 同样可以写入 console.log("memory readbyteArray: ") console.log(r.readByteArray(arr.length)); console.log(Memory.readByteArray(r, arr.length)); })hook so readCString(), writeUtf8String()Java.perform(function () { // 开辟内存空间 存有字符串 var r = Memory.allocUtf8String("你好,世界"); // 读取内存中的字符串 console.log(hexdump(r)); console.log(r.readCString()); // 往内存中写入新的字符串 r.writeUtf8String("Hello,World"); console.log(hexdump(r)); console.log(r.readCString()) })hook 获取 jni array// 获取 jbytesArray 的指针 var arg1Ptr = Java.vm.getEnv().getByteArrayElements(this.arg1, null) // 获取到指针后可以直接 hexdump 打印 console.log("arg1Ptr",hexdump(arg1Ptr)); // 如果是字符串可以直接转 console.log("arg1Ptr",arg1Ptr.readCString());ida 动态调试将 ida 中的 android-server 推入到手机中 adb push /Applications/IDA\ Pro\ 7.0/ida.app/Contents/MacOS/dbgsrv/android_server /data/local/tmp/as android_server 负责调试 32 位的app, android_server64 负责调试 64 位的app, 改名为 as 可以防止部分 android_server 名称检测给 android_server 增加权限``adb shellsuchmod +x /data/local/tmp/as进行端口转发 adb forward tcp:11678 tcp:11678 启动 android_server 并指定端口为 11678 adb shellsu/data/local/tmp/as -p11678调试之前先注入 frida 新建一个 ida 界面 Debugger - Remote ARMLinux/Android debugger hostname: localhost; Port: 11678; 勾选 Save network settings as default frida 打印出目标 function 最终的地址, ida 中 g 到目标 function 地址, 查看是否是 thumb 指令集, 如果是 thumb 则 option + g, 将 T 修改为1, 再按 c; File - Script file - 读取 ida trace 脚本, 需要更改目标 so 文件和 function 的起始地址和结束地址; 读取之后会出现断点; 快捷键: option F7; frida 主动调用脚本, 检测断点是否触发 断点检测正常后, ida 中执行 starthook()命令, 进行 hook 操作; 执行suspend_other_thread()挂起其他线程(可选择) ida 中 Debugger - tracing - tracing options - 设置 Trace file 路径 和 取消 Trace over debugger segments 的勾选 ida 中 Debugger - tracing - 勾选 instruction tracing frida 主动调用触发断点 ida 点击运行按钮进行执行, 此时 ida 中黄色部分为已经执行完的指令, 点击 Debugger -tracing - tracing window 可以看到当前执行进度; ida 添加自有 python 路径 ------------------ 修改路径下的文件 : /Applications/IDA Pro 7.0/ida.app/Contents/MacOS/python/init.py# Prepare sys.path so loading of the shared objects works lib_dynload = os.path.join( sys.executable, IDAPYTHON_DYNLOAD_BASE, "python", "lib", "python2.7", "lib-dynload") # added by kevin sys.path.insert(0, "/Users/zhangyang/anaconda3/envs/py2/lib/python2.7/site-packages") idapython 脚本调试 -------------- 在 pycharm 中开启调试 ![6.png][7] 在需要调试的脚本中添加断点import pydevd_pycharm pydevd_pycharm.settrace('localhost', port=12345, stdoutToServer=True, stderrToServer=True)在 idapython 的__init__.py文件中添加自有的 python 路径; 先在 pycharm 中打开调试监听, 在 ida 中运行要调试的脚本即可; jni_helper ---------- 进入目录 ~/androidFxxk/idaTools/jni_helper java -jar JadxFindJNI/JadxFindJNI.jar <apk.path> <output.json> ida 中Script File运行 jni_help脚本, 路径 ~/androidFxxk/idaTools/jni_helper/ida/jni_helper.py 导入刚才生成的 output.json 文件即可自动识别 frida dump 内存中的 so 文件 --------------------- 给应用提供 sdcard 权限, 方便将 dump 下来的内容直接写入 sdcard 中; adb shell dumpsys window | grep mCurrentFocus 查看当前 app 的包名 根据包名查看进程 pid✔  adb shellsailfish:/ $ susailfish:/ # ps -ef | grep com.kevin.sotestu0_a136 29251 641 0 16:10:14 ? 00:00:00 com.kevin.sotestroot 29845 29827 0 16:17:46 pts/3 00:00:00 grep com.kevin.sotest通过对应进程id 和 so 名称, 查看其在虚拟地址空间中的映射sailfish:/ # cat /proc/29251/maps | grep libnative-lib.so7593f4a000-7593f7a000 r-xp 00000000 103:13 1180186 /data/app/com.kevin.sotest-Wmu_QuZYJCLa0CPho2HTHw==/lib/arm64/libnative-lib.so7593f7b000-7593f7f000 r--p 00030000 103:13 1180186 /data/app/com.kevin.sotest-Wmu_QuZYJCLa0CPho2HTHw==/lib/arm64/libnative-lib.so7593f7f000-7593f80000 rw-p 00034000 103:13 1180186 /data/app/com.kevin.sotest-Wmu_QuZYJCLa0CPho2HTHw==/lib/arm64/libnative-lib.sofrida 脚本将该内存中的内容全部 dump 下来///<reference path='./index.d.ts'/> // 应用需要申请 sdcard 权限, dump下来的内容直接写入 sdcard 中 var global_so_name = "libnative-lib.so"; function catMaps() { Java.perform(function () { console.log("执行 -> ", "hook starting dump"); var ProcessClass = Java.use("android.os.Process"); var pid = ProcessClass.myPid(); console.log("pid", ProcessClass.myPid()); console.log("cat /proc/" + pid + "/maps | grep " + global_so_name); }) } function dump() { var inputArgs = arguments; Java.perform(function () { for (var i in inputArgs) { var fileHandle = null; if (i == 0) { fileHandle = new File("/sdcard/" + global_so_name, "wb"); } else { fileHandle = new File("/sdcard/" + global_so_name, "ab"); } if (fileHandle && fileHandle != null) { var base = "0x" + inputArgs[i].split("-")[0]; var end = "0x" + inputArgs[i].split("-")[1]; var size = parseInt(parseInt(end) - parseInt(base)); console.log("size", size, typeof size); // Memory.protect(ptr(base), size, 'rwx'); console.log("base", base, typeof base); var libso_buffer = ptr(base).readByteArray(size); console.log("buffer", libso_buffer); fileHandle.write(libso_buffer); fileHandle.flush(); fileHandle.close(); } } }) } // dump('7593f4a000-7593f7a000', '7593f7b000-7593f7f000', '7593f7f000-7593f80000') hook system_properties_get --------------------------///<reference path='./index.d.ts'/> // hook libc.so var targetAddr = null; // console.log(JSON.stringify(Process.enumerateModules())); // Process.enumerateModules() 枚举加载的so文件 var symbols = Process.findModuleByName("libc.so").enumerateSymbols(); for (var i = 0; i < symbols.length; i++){ if (symbols[i].name === "__system_property_get"){ targetAddr = symbols[i].address; } } // Interceptor.attach(targetAddr, { onEnter: function (args) { this._name = args[0].readCString(); this._value = args[1]; }, onLeave: function (retval) { console.log(JSON.stringify({ result_length: retval, name: this._name, val: this._value.readCString() })) } });Process 对象API ------------- Process.id 返回附加目标进程的 PIDfunction Process_pid() { console.log(Process.id); } // 10190调用的结果和在 android 中通过 ps -ef | grep hellojni_sign2的pid 相同u0_a139 10190 639 0 15:03:40 ? 00:00:10 com.example.hellojni_sign2 Process.isDebuggerAttached() ---------------------------- 检测当前是否对目标程序已经附加 Process.enumerateModules() -------------------------- 枚举当前加载的模块，返回模块对象的数组。 Process.enumerateModules()会枚举当前所有已加载的so模块，并且返回了数组Module对象; 可以通过 JSON.stringify()打印module 对象;function ProcessTest() { var modules = Process.enumerateModules(); for (var i = 0; i < modules.length; i++) { console.log(JSON.stringify(modules[i])); var module = modules[i]; console.log("module.name", module.name); console.log("module.base", module.base); console.log("module.size", module.size); console.log("module.path", module.path); } }其中 module 对象中含有的属性为: name: so 的名称 base: so 加载的基础地址 size: so 文件的大小 path: so 文件的路径 Process.enumerateThreads() -------------------------- Process.enumerateThreads()：枚举当前所有的线程，返回包含以下属性的对象数组： ![7.png][8] Module 对象 API 和属性 ----------------- Module 对象的属性 ------------ 索引 属性 含义 1 name 模块名称 2 base 模块地址，其变量类型为NativePointer 3 size 大小 4 path 完整文件系统路径 Module.load() ------------- 主要用于加载指定 so 文件, 返回一个 Module 对象;function frida_Module() { Java.perform(function () { //参数为so的名称 返回一个Module对象 const hooks = Module.load('libhello.so'); //输出 console.log("模块名称:",hooks.name); console.log("模块地址:",hooks.base); console.log("大小:",hooks.size); console.log("文件系统路径",hooks.path); }); } setImmediate(frida_Module,0); 输出如下： 模块名称: libhello.so 模块地址: 0xdf2d3000 大小: 24576 文件系统路径 /data/app/com.roysue.roysueapplication-7adQZoYIyp5t3G5Ef5wevQ==/lib/arm/libhello.so Module.enumerateImports() ------------------------- 枚举模块中的所有 import 函数function frida_Module() { Java.perform(function () { const hooks = Module.load('libhello.so'); var Imports = hooks.enumerateImports(); for(var i = 0; i < Imports.length; i++) { //函数类型 console.log("type:",Imports[i].type); //函数名称 console.log("name:",Imports[i].name); //属于的模块 console.log("module:",Imports[i].module); //函数地址 console.log("address:",Imports[i].address); } }); } setImmediate(frida_Module,0); 输出如下： [Google Pixel::com.roysue.roysueapplication]-> type: function name: __cxa_atexit module: /system/lib/libc.so address: 0xf58f4521 type: function name: __cxa_finalize module: /system/lib/libc.so address: 0xf58f462d type: function name: __stack_chk_fail module: /system/lib/libc.so address: 0xf58e2681 ... Module.enumerateExports() ------------------------- 获取指定 so 的所有导出函数function frida_Module() { Java.perform(function () { const hooks = Module.load('libhello.so'); var Exports = hooks.enumerateExports(); for(var i = 0; i < Exports.length; i++) { //函数类型 console.log("type:",Exports[i].type); //函数名称 console.log("name:",Exports[i].name); //函数地址 console.log("address:",Exports[i].address); } }); } setImmediate(frida_Module,0); 输出如下： [Google Pixel::com.roysue.roysueapplication]-> type: function name: Java_com_roysue_roysueapplication_hellojni_getSum address: 0xdf2d411b type: function name: unw_save_vfp_as_X address: 0xdf2d4c43 type: function address: 0xdf2d4209 type: function ... Module.enumerateSymbols() ------------------------- 获取指定 so 的所有符号表function frida_Module() { Java.perform(function () { const hooks = Module.load('libc.so'); var Symbol = hooks.enumerateSymbols(); for(var i = 0; i < Symbol.length; i++) { console.log("isGlobal:",Symbol[i].isGlobal); console.log("type:",Symbol[i].type); console.log("section:",JSON.stringify(Symbol[i].section)); console.log("name:",Symbol[i].name); console.log("address:",Symbol[i].address); } }); } setImmediate(frida_Module,0); 输出如下： isGlobal: true type: function section: {"id":"13.text","protection":"r-x"} name: _Unwind_GetRegionStart address: 0xf591c798 isGlobal: true type: function section: {"id":"13.text","protection":"r-x"} name: _Unwind_GetTextRelBase address: 0xf591c7cc ... Module.findExportByName(exportName) Module.getExportByName(exportName) ----------------------------------------------------------------------function frida_Module() { Java.perform(function () { Module.getExportByName('libhello.so', 'c_getStr') console.log("Java_com_roysue_roysueapplication_hellojni_getStr address:",Module.findExportByName('libhello.so', 'Java_com_roysue_roysueapplication_hellojni_getStr')); console.log("Java_com_roysue_roysueapplication_hellojni_getStr address:",Module.getExportByName('libhello.so', 'Java_com_roysue_roysueapplication_hellojni_getStr')); }); } setImmediate(frida_Module,0); 输出如下： Java_com_roysue_roysueapplication_hellojni_getStr address: 0xdf2d413d Java_com_roysue_roysueapplication_hellojni_getStr address: 0xdf2d413d Module.findBaseAddress(name)、Module.getBaseAddress(name) -------------------------------------------------------- 返回 name 模块的绝对基地址function frida_Module() { Java.perform(function () { var name = "libhello.so"; console.log("so address:",Module.findBaseAddress(name)); console.log("so address:",Module.getBaseAddress(name)); }); } setImmediate(frida_Module,0); 输出如下： so address: 0xdf2d3000 so address: 0xdf2d3000 Memory 对象 --------- Memory 的一些 API 通常是对内存处理, 譬如 Memroy.copy()用来复制内存; 又或者Memory.writeByteArray写入字节到指定内存中; Memory.scan搜索内存数据 ----------------- 其主要功能是搜索内存中以address地址开始，搜索长度为size，需要搜是条件是pattern，callbacks搜索之后的回调函数；此函数相当于搜索内存的功能。 如果我想搜索在内存中112A地址的起始数据要怎么做，代码示例如下。function frida_Memory() { Java.perform(function () { //先获取so的module对象 var module = Process.findModuleByName("libhello.so"); //??是通配符 var pattern = "03 49 ?? 50 20 44"; //基址 console.log("base:"+module.base) //从so的基址开始搜索，搜索大小为so文件的大小，搜指定条件03 49 ?? 50 20 44的数据 var res = Memory.scan(module.base, module.size, pattern, { onMatch: function(address, size){ //搜索成功 console.log('搜索到 ' +pattern +" 地址是:"+ address.toString()); }, onError: function(reason){ //搜索失败 console.log('搜索失败'); }, onComplete: function() { //搜索完毕 console.log("搜索完毕") } }); }); } setImmediate(frida_Memory,0);先来看看回调函数的含义，onMatch：function(address，size)：使用包含作为NativePointer的实例地址的address和指定大小为数字的size调用，此函数可能会返回字符串STOP以提前取消内存扫描。onError：Function(Reason)：当扫描时出现内存访问错误时使用原因调用。onComplete：function()：当内存范围已完全扫描时调用。 我们来来说上面这段代码做了什么事情：搜索libhello.so文件在内存中的数据，搜索以pattern条件的在内存中能匹配的数据。搜索到之后根据回调函数返回数据。 ![8.png][9] 搜索内存数据Memory.scanSync --------------------- 功能与Memory.scan一样，只不过它是返回多个匹配到条件的数据。代码示例如下。function frida_Memory() { Java.perform(function () { var module = Process.findModuleByName("[libhello.so](http://libhello.so)"); var pattern = "03 49 ?? 50 20 44"; var scanSync = Memory.scanSync(module.base, module.size, pattern); console.log("scanSync:"+JSON.stringify(scanSync)); }); } setImmediate(frida_Memory,0); // 输出如下，可以看到地址搜索出来是一样的 scanSync:[{"address":"0xdf2d412a","size":6}] Memory.alloc() -------------- 在目标进程中的堆上申请size大小的内存，并且会按照Process.pageSize对齐，返回一个NativePointer，并且申请的内存如果在JavaScript里面没有对这个内存的使用的时候会自动释放的。也就是说，如果你不想要这个内存被释放，你需要自己保存一份对这个内存块的引用。function frida_Memory() { Java.perform(function () { const r = Memory.alloc(10); console.log(hexdump(r, { offset: 0, length: 10, header: true, ansi: false })); }); } setImmediate(frida_Memory,0); Memory.allocUtf8String(),Memory.allocUtf16String(),Memory.allocAnsiString() ------------------------------------------------------------------------ Memory.allocUtf8String(str) 分配utf字符串 Memory.allocUtf16String 分配utf16字符串 Memory.allocAnsiString 分配ansi字符串 内存复制Memory.copy --------------- 如同c api memcp一样调用，使用案例如下。 从module.base中复制10个字节的内存到新年申请的r内function frida_Memory() { Java.perform(function () { //获取so模块的Module对象 var module = Process.findModuleByName("libhello.so"); //条件 var pattern = "03 49 ?? 50 20 44"; //搜字符串 只是为了将so的内存数据复制出来 方便演示~ var scanSync = Memory.scanSync(module.base, module.size, pattern); //申请一个内存空间大小为10个字节 const r = Memory.alloc(10); //复制以module.base地址开始的10个字节 那肯定会是7F 45 4C 46...因为一个ELF文件的Magic属性如此。 Memory.copy(r,module.base,10); console.log(hexdump(r, { offset: 0, length: 10, header: true, ansi: false })); }); } setImmediate(frida_Memory,0); 写入内存Memory.writeByteArray ------------------------- 将字节数组写入一个指定内存，代码示例如下:function frida_Memory() { Java.perform(function () { //定义需要写入的字节数组 这个字节数组是字符串"roysue"的十六进制 var arr = [ 0x72, 0x6F, 0x79, 0x73, 0x75, 0x65]; //申请一个新的内存空间 返回指针 大小是arr.length const r = Memory.alloc(arr.length); //将arr数组写入R地址中 Memory.writeByteArray(r,arr); //输出 console.log(hexdump(r, { offset: 0, length: arr.length, header: true, ansi: false })); }); } setImmediate(frida_Memory,0); 输出如下。 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 00000000 72 6f 79 73 75 65 roysue 读取内存Memory.readByteArray ------------------------ 将一个指定地址的数据按照 c 中bytearray进行读取function frida_Memory() { Java.perform(function () { //定义需要写入的字节数组 这个字节数组是字符串"roysue"的十六进制 var arr = [ 0x72, 0x6F, 0x79, 0x73, 0x75, 0x65]; //申请一个新的内存空间 返回指针 大小是arr.length const r = Memory.alloc(arr.length); //将arr数组写入R地址中 Memory.writeByteArray(r,arr); //读取r指针，长度是arr.length 也就是会打印上面一样的值 var buffer = Memory.readByteArray(r, arr.length); //输出 console.log("Memory.readByteArray:"); console.log(hexdump(buffer, { offset: 0, length: arr.length, header: true, ansi: false })); }); }); } setImmediate(frida_Memory,0); //输出如下。 [Google Pixel::com.roysue.roysueapplication]-> Memory.readByteArray: 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 00000000 72 6f 79 73 75 65 roysue hook fgets 让 TracerPid 为 0 --------------------------var anti_fgets = function () { 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) { console.log(bufstr); //此代码由我添加，打印TracerPid的值 Memory.writeUtf8String(buffer, "TracerPid:\t0"); } return retval; }, 'pointer', ['pointer', 'int', 'pointer'])); };frida 批量 trace -------------- 使用 native_trace 对关键函数进行批量 trace, 获取 trace.log 使用 python 脚本提取其中的地址, 编辑了模板快速使用 traceimport renative_trace = """16071 ms | sub_1494c4() ;debug 最终结果出现位置 (md5resultPtr, len, resultPtr) 16071 ms | sub_2546c() 16071 ms | sub_28d18() 16071 ms | | sub_bc028() 16071 ms | | | sub_cf970() 16071 ms | | | | sub_d4680()"""addr_list = re.findall(r"sub_(.*)()", native_trace)addr_list = list(map(lambda x:"0x" + x, addr_list))print(addr_list)使用 trace 出来的地址使用 frida 批量进行 hook; 编辑了模板快速使用 print_arg和 trace // native var base = Process.findModuleByName("libhello-jni.so").base; function print_arg(addr) { var range = Process.findRangeByAddress(addr); if (range != null) { return hexdump(addr) + '\r\n'; }else{ return ptr(addr) + "\r\n"; } } function native_trace(addr) { var offset = addr; addr = base.add(addr); Interceptor.attach(addr, { onEnter: function (args) { // frida 设计的时候就无法获知 args 的长度 console.log("======================onenter sub_" + offset + "=========================="); for (var i = 0; i < 5; i++) { eval("this.arg" + i + "=args[" + i + "]"); console.log("this.arg" + i, print_arg(eval("this.arg" + i))); } }, onLeave: function (returnValue) { console.log("======================onleave sub_" + offset + "=========================="); for (var i = 0; i < 5; i++) { console.log("this.arg" + i, print_arg(eval("this.arg" + i))) } console.log("returnVlaue", print_arg(returnValue)); } }) } function main() { var trace_list = ['0x1494c4']; for (var i = 0; i < trace_list.length; i++) { native_trace(trace_list[i]); } } setImmediate(main); 本条博客转载自：凡墙总是门
验证码服务平台 谷歌人机 ReCaptcha（v2/v3 通用版）, 直接返回 token ReCaptcha（v2/v3 企业版）, 直接返回 token ReCaptcha（steam）, 直接返回 token CloudFlare 盾通用版, 返回 cf_clearance、__cf_bm 、正确响应源码 html、验证流程使用的 tls 指纹 Incapsula 盾 reese84 通用版, 返回 reese84 cookie 或 solution 参数 Incapsula 盾 __utmvc 通用版, 服务器直接无感验证 或 __utmvc cookie Hcaptcha 通用版, 直接返回 generated_pass_UUID 直接返回 _abck tls 转发接口, 针对校验 ja3、http2 等指纹（akamai/cloudflare）的接口 更多支持后续敬请期待..... 注册连接：https://3ii.cc/V2J 新用户注册加群即送10000点余额 新用户注册加群即送10000点余额 新用户注册加群即送10000点余额工欲善其事必先利其器工具：真机or模拟器 Fiddler 或任意一款抓包工具 VSCode frida 老规矩 先抓包其中 zzReqSign 便是我们需要还原的算法 我们查课发现没有加固 直接丢进jadx进行反编译搜索定位 我们来到如下位置发现是个 native 方法 那么加密在so层 上面 System.loadLibrary("signLib"); 就是加载so的指令 其中 signLib就是so名字解压apk 去lib目录寻找是否有这个文件拖入IDA 导出函数 搜索 getSign定位如下位置：代码如下：int __fastcall Java_com_zhuanzhuan_sign_SignUtil_getSign(JNIEnv_ *a1, int a2, int a3, int a4) { const char *app_sign_sha1; // r0 struct _jobject *v9; // r8 signed int v10; // r5 int DevID; // r0 struct _jobject *v12; // r8 int v13; // r9 jbyte *v14; // r6 jsize v15; // r10 _BYTE *v16; // r8 signed int i; // r11 jbyte v18; // r4 int v19; // r1 JNIEnv_ *v20; // r4 int v21; // r9 int MD5; // r10 int v24; // [sp+8h] [bp-30h] struct _jobject *v25; // [sp+Ch] [bp-2Ch] struct _jobject *v26; // [sp+10h] [bp-28h] JNIEnv_ *v27; // [sp+14h] [bp-24h] jbyte *v28; // [sp+18h] [bp-20h] if ( !a3 ) return 0; app_sign_sha1 = get_app_sign_sha1(a1, a4); if ( !app_sign_sha1 ) return 0; if ( strcmp(app_sign_sha1, app_sing_str) ) return 0; v9 = toBytes(a1, a3); v10 = a1->functions->GetArrayLength(a1, v9); v28 = a1->functions->GetByteArrayElements(a1, v9, 0); DevID = getDevID(a1, a2); if ( !DevID ) return 0; v26 = v9; v24 = DevID; v12 = toBytes(a1, DevID); v13 = 0; v14 = a1->functions->GetByteArrayElements(a1, v12, 0); v27 = a1; v25 = v12; v15 = a1->functions->GetArrayLength(a1, v12); v16 = malloc(v10); for ( i = 0; i < v10; ++i ) { v18 = v28[v13 % v10]; v19 = (v13 + 1) % v15; v13 += 2; v16[i] = v14[v19] ^ v18; } v20 = v27; v21 = v27->functions->NewByteArray(&v27->functions, v10 + 9); (v20->functions->SetByteArrayRegion)(v20, v21, 0, v10, v16, a3); v20->functions->SetByteArrayRegion(&v20->functions, v21, v10, 9, "smiletozz"); MD5 = getMD5(v27, v21); v20->functions->DeleteLocalRef(&v20->functions, v21); free(v16); v20->functions->DeleteLocalRef(&v20->functions, v24); v20->functions->ReleaseByteArrayElements(&v20->functions, v25, v14, 0); v20->functions->ReleaseByteArrayElements(&v20->functions, v26, v28, 0); v20->functions->DeleteLocalRef(&v20->functions, v25); v20->functions->DeleteLocalRef(&v20->functions, v26); return MD5;这段函数最终返回了MD5 MD5是由 MD5 = getMD5(v27, v21);而来的 我们姑且认为他是一个md5算法 点进去看看代码如下：int __fastcall getDigestedBytes(_JNIEnv *a1, int a2) { struct _jobject *v4; // r6 jmethodID v5; // r5 jstring v6; // r0 _jobject *v7; // r9 _jmethodID *v8; // r0 int v9; // r8 v4 = a1->functions->FindClass(a1, "java/security/MessageDigest"); v5 = a1->functions->GetStaticMethodID(a1, v4, "getInstance", "(Ljava/lang/String;)Ljava/security/MessageDigest;"); v6 = a1->functions->NewStringUTF(a1, "MD5"); v7 = _JNIEnv::CallStaticObjectMethod(a1, v4, v5, v6); v8 = a1->functions->GetMethodID(a1, v4, "digest", "([B)[B"); v9 = _JNIEnv::CallObjectMethod(a1, v7, v8, a2); a1->functions->DeleteLocalRef(a1, v4); a1->functions->DeleteLocalRef(a1, v7); return v9;这段就比较简单了 反射java调用md5算法 到这里好像已经分析完毕 貌似就是个md5算法而已 是不是这样呢？我们试验一下frida hook java层 getSign方法代码如下：let SignUtil = Java.use("com.zhuanzhuan.sign.SignUtil"); SignUtil["getSign"].overload('java.lang.String', 'android.content.Context').implementation = function (str, context) { console.log(`SignUtil.getSign is called: str=${str}, context=${context}`); let result = this["getSign"](str, context); console.log(`SignUtil.getSign result=${result}`); return result; };抓包结果为：zzReqSign: 47383db507e87badc5bc9ce348a6193e我们去hook结果搜索下面为结果 上面是明文 我们md5一下发现结果并不对 那么肯定是在md5之前还有其他操作 我们返回so继续分析getDevID 代码如下:int __fastcall getDevID(int a1, int a2) { int v4; // r2 v4 = (*(*a1 + 452))(a1, a2, "getDeviceId", "()Ljava/lang/String;"); return j__JNIEnv::CallStaticObjectMethod(a1, a2, v4); }这里对应的好像就是java 层我们hook一下此方法 拿到值let SignUtil = Java.use("com.zhuanzhuan.sign.SignUtil"); SignUtil["getDeviceId"].implementation = function () { console.log(`SignUtil.getDeviceId is called`); let result = this["getDeviceId"](); console.log(`SignUtil.getDeviceId result=${result}`); return result; };值=SignUtil.getDeviceId result=BE304BAE68E9C99571DEE26718102680到此分析完毕算法还原