1.Frida 启动
frida 安装 https://github.com/frida/frida
pip install frida frida-tools
attach 启动
直接附加到指定包名的应用中
frida -U com.kevin.android -l hook.js
直接附加到当前应用中
frida -UF -l hook.js
import 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-pause
import 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()
2.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.1
frida
frida 远程连接自定义端口
# 连接指定 6666 端口 frida -H 192.168.0.1:6666 com.demo1.app -l demo1.js # 默认使用端口 27042 frida -H 192.168.0.1 -l demo1.js
python
# -*- 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()
3.Frida rpc 远程调用
python
import 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 进行内网穿透
session = frida.get_device_manager().add_remote_device('10.0.0.124:56666').attach('com.example.demoso1')
4.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);
5.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);
6.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);
6.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(); }; });
7.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);
8.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)
9.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)
10.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)
11.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" } } } } } }) }
12.Hook 动态加载的 dex
function 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); } }); }) }
13.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)); }) }
14.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)); } }) }) }
15.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()); // 直接打印接口名称 } } } } }) }) }
16.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()); } }) }) }
17.Hook 获取 context
function 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()); }) }
18.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);
19.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);
20.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()); } }) }) }
21.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()
22.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; } }) }
23.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); } }) }
24.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()); }) }
25.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()) }) }
26.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()) }) }
27.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 + "==============================") }
28.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);
29.Hook 打印 non-ascii 和特殊字符
一些特殊字符和不可见字符, 可以先通过编码再解码的方式进行 hook
int ֏(int x) { return x + 100; }
针对上面的֏
, 直接用js
编码, 在通过类名[js解码的方法名]
进行implementation
Java.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; } } )
30.简易 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)
31.hook frida 实现 runnable
Java.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 () {} }); });
32.Hook 监控控件 onClick
var 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);
33.Hook startActivity
Java.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); } })
34.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"); }) }
35.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; } } });
36.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(); }) })
37.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; }; }) }
38.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);
39.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; }
40.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(); }) })
41.常用打印转换
//工具相关函数 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 }
42.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]);
43.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)
44.检测判断是否是 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 的主动调用不在主线程中; 可以作为一个主动调用的检测点
45.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(); } }) }
46.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);