Frida
Created At :
Views 👀 :
Frida原理
frida注入的原理就是找到目标进程,使用ptrace跟踪目标进程获取mmap,dlpoen,dlsym等函数库的偏移获取mmap在目标进程申请一段内存空间将在目标进程中找到存放frida-agent-32/64.so的空间启动执行各种操作由agent去实现
Frida基础指令
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 26
| frida-ps --help 使用方式: frida-ps [选项]
选项: -h, --help 显示帮助信息并退出 -D ID, --device ID 连接到具有给定ID的设备 -U, --usb 连接到USB设备 -R, --remote 连接到远程frida-server -H HOST, --host HOST 连接到HOST上的远程frida-server --certificate CERTIFICATE 与HOST进行TLS通信,期望的CERTIFICATE --origin ORIGIN 连接到设置了"Origin"头为ORIGIN的远程服务器 --token TOKEN 使用TOKEN验证HOST --keepalive-interval INTERVAL 设置心跳包间隔(秒),或设置为0以禁用(默认为-1,根据传输方式自动选择) --p2p 与目标建立点对点连接 --stun-server ADDRESS 设置与--p2p一起使用的STUN服务器地址 --relay address,username,password,turn-{udp,tcp,tls} 添加与--p2p一起使用的中继 -O FILE, --options-file FILE 包含额外命令行选项的文本文件 --version 显示程序版本号并退出 -a, --applications 只列出应用程序 -i, --installed 包括所有已安装的应用程序 -j, --json 以JSON格式输出结果
|
先开启adb,再运行frida
1 2 3 4 5 6 7
| C:\Users\n1ng>adb root restarting adbd as root
C:\Users\n1ng>adb shell gracelte:/ # su gracelte:/ # cd data/local/tmp gracelte:/data/local/tmp # ./fr
|
1.操作模式
CLI(命令模式):通过命令行直接将JavaScript脚本注入到进程中
RPC模式:使用python进行JavaScript脚本的注入,可以通过RPC传输给python脚本进行复杂数据的处理
2.注入模式和启动命令
Spawn模式:将启动App的权利交由Frida来控制,即使目标App已经启动,在使用Frida注入程序时还是会重新启动App
1
| frida -U -f 进程名 -l hook.js //Spawn模式
|
Attach模式:在目标App已经启动的情况下,Frida通过ptrace注入程序从而执行Hook的操作
1
| frida -U 进程名 -l hook.js //Attach模式
|
frida_server自定义端口:
1 2 3 4 5 6
| frida server 默认端口:27042
taimen:/ $ su taimen:/ # cd data/local/tmp/ taimen:/data/local/tmp # ./fs1280 -l 0.0.0.0:6666
|
3.基础语法
API名称 |
描述 |
Java.use(className) |
获取指定的Java类并使其在JavaScript代码中可用。 |
Java.perform(callback) |
确保回调函数在Java的主线程上执行。 |
Java.choose(className, callbacks) |
枚举指定类的所有实例。 |
Java.cast(obj, cls) |
将一个Java对象转换成另一个Java类的实例。 |
Java.enumerateLoadedClasses(callbacks) |
枚举进程中已经加载的所有Java类。 |
Java.enumerateClassLoaders(callbacks) |
枚举进程中存在的所有Java类加载器。 |
Java.enumerateMethods(targetClassMethod) |
枚举指定类的所有方法。 |
4.日志输出语法
日志方法 |
描述 |
区别 |
console.log() |
使用JavaScript直接进行日志打印 |
多用于在CLI模式中,console.log() 直接输出到命令行界面,使用户可以实时查看。在RPC模式中,console.log() 同样输出在命令行,但可能被Python脚本的输出内容掩盖。 |
send() |
Frida的专有方法,用于发送数据或日志到外部Python脚本 |
多用于RPC模式中,它允许JavaScript脚本发送数据到Python脚本,Python脚本可以进一步处理或记录这些数据。 |
安装Frida代码提示
进入工程文件
Frida使用
Frida启动前注入
1
| frida -U -f com.n1ng.app -l Hook.js
|
注入后app启动但停滞在启动界面,输入‘%resume’进入app,后面加‘–no–pause ’则不会停滞
使用python来注入,此时使用的是ip加端口来连接,在启动服务端时需要监听端口,即加上 “-l 0.0.0.0:27042”
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 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 52 53 54 55 56
| import frida, sys
jsCode = """
Java.perform(function(){
var RequestUtil = Java.use('com.dodonew.online.http.RequestUtil'); RequestUtil.encodeDesMap.overload('java.lang.String', 'java.lang.String', 'java.lang.String').implementation = function(a, b, c){ console.log('data: ', a); console.log('desKey: ', b); console.log('desIV: ', c); var retval = this.encodeDesMap(a, b, c); console.log('retval: ', retval); return retval; }
var Utils = Java.use('com.dodonew.online.util.Utils'); Utils.md5.implementation = function(a){ console.log('MD5 string: ', a); var retval = this.md5(a); console.log('retval: ', retval); return retval; } });
""";
process1 = frida.get_device_manager().add_remote_device('192.168.1.11:9999').attach('com.dodonew.online') process2 = frida.get_device_manager().add_remote_device('127.0.0.1:6666').attach('com.dodonew.online') script1 = process1.create_script(jsCode) script2 = process2.create_script(jsCode)
script1.load() script2.load()
print("开始运行");
sys.stdin.read()
|
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 26 27 28 29
| import frida, sys
jscode = """
function hookTest1(){ Java.perform(function(){ var Utils = Java.use('com.xiaojianbang.app.Utils'); Utils.getCalc.implementation = function(a, b){ console.log('getCalc: ', a, b); var retval = this.getCalc(a, b); console.log('retval: ', retval); return 4000; } }); } hookTest1();
"""
rdev = frida.get_usb_device() pid = rdev.spawn(["com.xiaojianbang.app"]) print(pid) process = rdev.attach(pid) script = process.create_script(jscode) script.load() rdev.resume(pid) sys.stdin.read()
|
1 2 3 4 5 6 7 8 9 10
| Java.perform(function(){ var RequestUtil = Java.use("com.dodonew.online.http.RequestUtil"); console.log(RequestUtil); RequestUtil.encodeDesMap.implementation = function(a,b,c){ var retval = this.encodeDesMap(a,b,c); console.log(retval); return retval; } });
|
Hook Java层(Hook底层函数)
可以去HookHashMap,ArrayList,自吐算法,String,JsonObject,StringBuilder(批量的字符串处理,如签名时排序,很多会用StringBuilder作为容器)
1 2 3 4 5 6 7
| Java.perform(function(){ var Map = Java.use("Java.util.Map"); Map.put.implementation=function(a,b){ console.log("Map:",a,b); return this.put(a,b); } });
|
函数调用栈的打印
1 2 3 4 5 6 7 8 9
| function showStack(){ var stack = Java.use('android.util.log').getStackTraceString(Java.use('Java.lang.Throwable').$new()); console.log(stack); }
|
Hook构造函数
需要加‘$init’
1 2 3 4 5 6 7 8 9 10 11
| function hookTest2(){ Java.perform(function(){ var money = Java.use("com.xiaojianbang.app.Money"); money.$init.overload('java.lang.String', 'int').implementation = function(str, num){ console.log(str, num); str = "欧元"; num = 2000; this.$init(str, num); } }); }
|
Hook修改类的字段
非静态字段使用Java.choose来选择对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| fuction Hooktest3(){ Java.perform(function(){ var money = Java.use('com.xiaojianbang.app.Money'); console.log('money.flag:',JSON.stringify(money.flag)); money.flag.value='xiaojianbang'; console.log(money.flag.value); Java.choose('com.xiaojianbang.app.Money',{ onMatch: function(obj){ obj._name.value = 'ouyuan'; obj.num.value = 150000; }, onComplete: function(){ } }); }); }
|
Hook内部类中的方法,匿名类
在类后面加’$’再加上内部类的类名即可,
匿名类没有名字因此加’$’在加1,即第几个匿名类,以此类推
主动调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function HookTest4(){ Java.perform(function(){ var Utils = Java.use("com.xiaojianbang.app.Utils"); var retval = Utils.test(); console.log(retval);
retval = Utils.test(666); console.log(retval);
var Money = Java.use("comlxiaojianbang.app.Money"); retval = Utils.text(Money.$new("xiaojianbang",777)); console.log(retval); }); }
|
枚举所有的类和所有的方法
使用enumerateLoadedClassesSync
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
function HookTest5(){ Java.perform(function(){ var classes = Java.enumerateLoadedClassesSync(); for(var i = 0;i<classes.length;i++){ if(classes[i].indexof("com.xiaojianbang.app")!=-1){ console.log('classes[i]'); var clazz = Java.use(classes[i]); var methods = clazz.class.getDeclaredMethods(); for(var j = 0;j<methods.length;j++){ console.log(methods[j]); } } } }); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
function HookTest6{ Java.perform(function(){ Java.enumerateLoadedClasses({ onMatch: function(name,handle){ if(name.indexof("com.xiaojianbang.app")!=-1){ console.log(name); var calzz = Java.use(name); console.log(clazz); var methods = clazz.class.getDeclaredMethods(); for(var i = 0;i<methods.length;i++){ console.log(methods[i]); } } }, onComplete: function(){} }); }); }
|
Hook类的所有方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| function HookTest7(){ function hookAll(md5, methodsName){ for(var k = 0;k<md5[methodsName].overloads.length;k++){ md5[methodsName].overloads[k].implenmentation=function(){ for(car i = 0;i<arguments.length;i++){ console.log(arguments[i]); } console.log(methodsName); return this[methodsName].apple(this,arguments); } } } Java.perform(function(){ var md5 = Java.use('com.xiaojianbang.app.MD5'); var methods = md5.class.getDeclaredMethods(); for(var j = 0; j<methods.length;j++){ var methodsName = methods[j].getName(); console.log(methodsName); hookAll(md5,methodsName); } }); }
|
Hook动态加载的dex
因为class loader不是默认的,因此直接hook会找不到,在frida中采用枚举class loader的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function HookTest8(){ Java.perform(function(){ Java.enumerateClassLoaders({ onMatch: function(loader){ try { if(loader.loadClass("com.xiaojianbang.app.Dynamic")){ Java.classFactory.loader = loader; var Dynamic = Java.use("com.xiaojianbang.app.Dynamic"); console.log(Dynamic); Dynamic.sayHello.implementation = function(){ return "jingyi"; } } } catch (error) { } }, onComplete: function(){} }); }); }
|
Hook特殊类型
可以使用java的方法来遍历,但要符合js的语法
1 2 3 4 5 6 7 8 9 10 11 12 13
| function HookTest9(){ Java.perform(function(){ var ShufferMap = Java.use("com.xiaojianbang.app.ShufferMap"); ShufferMap.show.implementation = function(map){ console.log(JSON.stringify(map)); map.put('pass','jingyi'); map.put('guanwang','bbs'); var retval = this.show(map); console.log(retval); return retval; } }); }
|
Hook so层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function hook_so(){ var soAddr = Moudle.findBaseAddress("libtre.so"); var base64_encode_new = soAddr.add(0x152c+1); Interceptor.attach(base64_encode_new,{ onEnter: function(args){ console.log("args[o]: ",args[0]); console.log("args[o]: ",hexdump(args[0]); console.log("args[o]: ",ptr(args[0]).readCString()); this.args1 = args[1]; console.log("args[o]: ",args[1]); console.log("args[o]: ",hexdump(args[1]); console.log("args[o]: ",args[2]); }, onLeave: function(retval){ console.log("retval: ",retval); console.log("retval: ",hexdump(retval)); console.log("retval: ",ptr(this.args1).readCString()); } }); }
|
算法转发
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 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 52 53 54 55 56 57 58
|
from fastapi import FastAPI import uvicorn import frida
jsCode = """
function hookTest(username, passward){ var result; Java.perform(function(){ var time = new Date().getTime(); time = '1597582774344';
var string = Java.use('java.lang.String'); var signData = string.$new('equtype=ANDROID&loginImei=Android352689082129358&timeStamp=' + time + '&userPwd=' + passward + '&username=' + username + '&key=sdlkjsdljf0j2fsjk');
var Utils = Java.use('com.dodonew.online.util.Utils'); var sign = Utils.md5(signData).toUpperCase(); console.log('sign: ', sign); var encryptData = '{"equtype":"ANDROID","loginImei":"Android352689082129358","sign":"'+ sign +'","timeStamp":"'+ time +'","userPwd":"' + passward + '","username":"' + username + '"}';
var RequestUtil = Java.use('com.dodonew.online.http.RequestUtil'); var Encrypt = RequestUtil.encodeDesMap(encryptData, '65102933', '32028092'); console.log('Encrypt: ', Encrypt); result = Encrypt; }); return result; } rpc.exports = { xiaojianbang: hookTest };
""";
process = frida.get_device_manager().add_remote_device('192.168.1.11:27042').attach("com.dodonew.online") script = process.create_script(jsCode) print('[*] Running 小肩膀') script.load()
app = FastAPI()
@app.get("/get") async def getEchoApi(item_id, item_user, item_pass): result = script.exports.xiaojianbang(item_user, item_pass) return {"item_id": item_id, "item_retval": result}
if __name__ == '__main__': uvicorn.run(app, port = 8080)
|
Objection的安装与使用
1
| pip install objection -i https://pypi.tuna.tsinghua.edu.cn/simple/ --trusted-host pypi.tuna.tsinghua.edu.cn
|
注入进程
1
| objection -g com.xiaojianbang.app explore
|
1
| objection -N -h 192.168.1.3 -p 9999 -g com.xiaojianbang.app explore//以网络的方式连接,启动前注入则在后面加 --start--command "android hooking watch class 'com.xiaojianbang.app.MD5'"
|
常用命令
1 2 3 4 5 6 7 8 9 10 11 12 13
| android hooking search classes <name>//枚举类,name为类名
android hooking list class_methods com.xiaojianbang.app.MD5//列出类的所有方法
android hooking watch class com.xiaojianbang.app.MD5//hook类的所有方法
jobs list//查看hook的类 jobs kill xxjobid
android hooking watch class_methods com.xiaojianbang.app.MD5.md5_1 --dump--args --dump--return --dump--backtrack//hook方法的参数,返回,调用堆栈,会hook所有的重载,如果要hook其中的某一个函数则需要加上“参数类型”
android heap execute 地址 getinfo//主动调用方法
|
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 3049155267@qq.com