[TOC]
Frida all in one
努力ALL IN ONE,部分代码可配合AndroidReversePractice/FridaTestApp at main · Forgo7ten/AndroidReversePractice (github.com) 试验。
JavaScript API | Frida • A world-class dynamic instrumentation framework
Frida环境与基础使用
多python frida版本切换
pyenv安装
1 2 curl -L https://gitee.com/forgo7ten-mirrors/pyenv-installer/raw/master/bin/pyenv-installer | bash cd ~/.pyenv && src/configure && make -C src
.bashrc添加
1 2 3 4 5 6 7 8 9 export PYENV_ROOT="$HOME /.pyenv" export PATH="$PYENV_ROOT /bin:$PATH " export PATH="$PYENV_ROOT /shims:$PATH " if command -v pyenv 1>/dev/null 2>&1; then eval "$(pyenv init -) " fi eval "$(pyenv virtualenv-init -) "
pyenv使用
1 2 3 4 5 6 7 8 9 10 11 pyenv install 3.8.2 pyenv versions pyenv local 3.8.2 python local system
Frida环境安装
需前往frida · PyPI 查看支持python版本(如frida 15.0.13不支持python3.7)
安装特定版本(使用pip默认源 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 python 3.8.5 frida_12_8_0 pyenv local frida_12_8_0 pip install wheel pip install frida==12.8.0 pip install frida-tools==5.3.0 pip install objection==1.8.4 python 3.8.6 frida_14_2_18 pyenv local frida_14_2_18 pip install frida==14.2.18 pip install frida-tools==9.2.4 pip install objection==1.11.0 python 3.8.8 frida_15_1_28 pyenv local frida_15_1_28 pip install frida==15.1.28 pip install frida-tools==10.8.0 pip install objection==1.11.0
Release Frida 12.8.0 · frida/frida (github.com)
手机环境配置
前往Releases · frida/frida (github.com) 下载对应的frida-server
文件,frida-server版本要和frida版本一致
push到设备目录,赋予执行权限
监听
设置端口号为8888
Objection环境安装
Frida开发环境搭建
全局获得代码提示(在用户根目录使用)
1 2 npm install --save @types/frida-gum
或者ts编译
1 2 3 git clone https://gitee.com/Forgo7ten/frida-agent-example.git cd frida-agent-example/npm install
在agent
目录下编写
1 2 3 4 5 6 7 function main ( ) { Java.perform(function x ( ) { console .log("Hello Frida" ) }) } setImmediate(main)
控制台执行
1 2 3 4 5 frida -H 192.168.0.106:8888 -f com.android.settings -l test.js --no-pause -H:设置主机和端口号 -f:启动某个应用(packagename) -l:执行的js文件
npm run watch
会监控代码修改自动编译生成js文件
或者 python脚本执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import timeimport fridadevice = frida.get_device_manager().add_remote_device("192.168.0.103:8888" ) pid = device.spawn("com.android.settings" ) device.resume(pid) time.sleep(1 ) session = device.attach(pid) with open ("test.js" ) as f: script = session.create_script(f.read()) script.load()
Objection使用
Objection基本使用
objection启动并注入内存
1 2 3 4 5 6 objection -N -h [hostname] -p [port] -d -g [packagename] explore
memory
1 2 3 4 5 6 7 8 memory list modules memory list exports [modulename] memory list exports [modulename] --json [filename]
1 2 3 4 5 memory dump all [filename] memory dump from_base [start_addr] [length] [file_name]
android
1 2 android heap search instances [class_name]
1 2 3 4 5 6 android heap execute [instance_handle] [method_name] android heap evaluate [instance_handle] {Enter}
启动activity或service
1 2 android intent launch_activity [activity]
1 2 android intent launch_service [service]
hooking
1 2 3 4 5 6 7 8 9 10 11 android hooking list activities android hooking list services android hooking list classes android hooking list class_methods [class]
1 2 3 4 5 android hooking search classes [string] android hooking search methods [string]
1 2 3 4 5 6 7 8 android hooking generate simple [class] android hooking watch class [class_method] --dump-args --dump-backtrace --dump-return android hooking watch [class_method] [method_name]
objection jobs
1 2 3 4 5 jobs listjobs kill [JobID]
objection插件
插件使用
objection高级使用
objection spawn进程(hook某些一启动就执行的app)
1 objection -d -g [packageName] explore --startup-command 'android hooking watch class xxx'
1 objection -d -g [packageName] explore -c commands.txt
Frida Java层应用
hook方法
hook静态方法与实例方法
Frida对静态方法与实例方法没有区分,直接hook即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function main ( ) { Java.perform(function ( ) { Java.use("com.forgotten.fridatestapp.HookedObject" ).stringTwo.implementation = function (arg ) { var result = this .stringTwo(arg); console .log("stringTwo arg,result: " , arg, result); return Java.use("java.lang.String" ).$new("hhello" ); }; }); } setImmediate(main);
hook构造方法
构造方法为$init
,例如
1 2 3 4 5 6 7 8 9 10 11 12 13 function main2 () { Java.perform(function(){ Java.use("com.forgotten.fridatestapp.HookedObject" ).$init.overload().implementation = function () { var result = this .$init(); console.log("call $init" ); return result; }; }) }
对方法重载的hook
使用.overload()
来确定hook哪个重载方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function main ( ) { Java.perform(function ( ) { Java.use("com.forgotten.fridatestapp.HookedObject" ).addNumber.overload("int" , "int" ).implementation = function ( ) { for (var i = 0 ; i < arguments .length; i++) { console .log("addNumber arguments[" + i + "]=" + arguments [i]); } var result = this .addNumber(arguments [0 ], arguments [1 ]); console .log("addNumber arg,result: " , arguments , result); return 99 ; }; }); } setImmediate(main);
hook指定方法的所有重载
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 function hookMethodAllOverloads (className, methodName ) { Java.perform(function ( ) { var clazz = Java.use(className); var overloadsLength = clazz[methodName].overloads.length; for (var i = 0 ; i < overloadsLength; i++) { clazz[methodName].overloads[i].implementation = function ( ) { var result = this [methodName].apply(this , arguments ); var paramStr = "" ; for (var j = 0 ; j < arguments .length; j++) { if (j == arguments .length - 1 ) { paramStr += arguments [j]; } else { paramStr += arguments [j] + "," ; } } console .log("Called" , className + "." + methodName + "(" + paramStr + ") :" , result); return result; }; } console .log("[" + overloadsLength + "]" , className + "." + methodName, "Hooked!" ); }); } function main ( ) { var className = "com.forgotten.fridatestapp.HookedObject" ; var methodName = "addNumber" ; hookMethodAllOverloads(className, methodName); }
hook内部类与匿名类中的方法
内部类获取时className
使用$
拼接,如:
1 var InnerClazz = Java.use("com.forgotten.fridatestapp.HookedObject$innerClass" );
匿名类也同样使用$
拼接,但其后的要使用smali或者objection内存搜索来查看
1 var clazz = Java.use("com.forgotten.fridatestapp.MainActivity$1" );
不可见函数名hook
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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; } } )
内存搜索
查找实例对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function main ( ) { Java.perform(function ( ) { var className = "com.forgotten.fridatestapp.MainActivity" ; Java.choose(className, { onMatch : function (instance ) { console .log("Found it!!" , instance); }, onComplete : function ( ) { console .log("Search complete!" ); }, }); }); }
保存对象在外部方法使用
Java.retain(obj)
:复制一份obj
的java包装器以便于以后使用。
1 2 3 4 5 6 7 8 Java.perform(() => { const Activity = Java.use("android.app.Activity" ); let lastActivity = null ; Activity.onResume.implementation = function ( ) { lastActivity = Java.retain(this ); this .onResume(); }; });
主动调用方法、获取及修改变量
主动调用方法
静态方法使用Java.use
获得类 后直接调用
非静态方法需要使用Java.choose
查找到类实例 后进行调用
构造方法为$init
对于方法重载
可根据传入的参数自动选择重载
或通过.overload().apply()
手动选择选择要调用的重载方法
静态/非静态变量
获取成员变量:[field].value
获取静态成员的值:使用类 或者类实例 。
获取非静态成员的值:内存搜索到类实例 后获取。
设置成员变量的值,写法是[field_name].value = [value]
,其他方面和函数一样。
如果有一个成员变量和成员函数的名字相同,则在其前面加一个_
,如_[field_name].value = [value]
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 function invoke ( ) { Java.perform(function ( ) { Java.choose("com.forgotten.fridatestapp.HookedObject" , { onMatch : function (instance ) { console .log("Found `HookedObject` instance:" , instance); console .log("instance.score =" , instance.score.value); console .log("HookedObject.msg =" , Java.use("com.forgotten.fridatestapp.HookedObject" ).msg.value); console .log("instance.msg =" , instance.msg.value); instance.score.value = Java.use("java.lang.Integer" ).parseInt("-900" ); console .log("instance.score =" , instance.score.value); console .log("instance.stringTwo =" , instance._stringTwo.value); console .log(instance.addNumber(1 , 2 )); console .log(instance.addNumber(4 , 5 , 6 )); console .log(instance.addNumber.overload("int" , "int" ).apply(instance,[8 ,9 ])); }, onComplete : function ( ) { console .log("Found Completed" ); }, }); }); }
枚举所有类并hook所有方法
配合hook指定方法的所有重载 来hook所有的方法
使用Java.enumerateLoadedClasses()
API
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 function main ( ) { Java.perform(function ( ) { Java.enumerateLoadedClasses({ onMatch : function (name,handle ) { if (name.indexOf("com.forgotten.fridatestapp" ) != -1 ){ console .log(name,handle); var TargetClass = Java.use(name); var methodsList = TargetClass.class.getDeclaredMethods(); for (var i = 0 ; i < methodsList.length; i++){ console .log(methodsList[i].getName()); hookMethodAllOverloads(name,methodsList[i].getName()); } } }, onComplete : function ( ) { console .log("enumerateLoadedClasses complete!!!" ) } }) }) }
使用Java.enumerateLoadedClassesSync()
API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function main ( ) { Java.perform(function ( ) { var classList = Java.enumerateLoadedClassesSync(); for (var i = 0 ; i < classList.length; i++) { var targetClass = classList[i]; if (targetClass.indexOf("com.forgotten.fridatestapp" ) != -1 ) { console .log("hook the class: " , targetClass); var TargetClass = Java.use(targetClass); var methodsList = TargetClass.class.getDeclaredMethods(); for (var j = 0 ; j < methodsList.length; j++) { console .log(methodsList[j].getName()); hookMethodAllOverloads(targetClass,methodsList[j].getName()); } } } }); }
hook动态加载dex
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 function findClassLoader (className ) { Java.perform(function ( ) { Java.enumerateClassLoaders({ onMatch : function (loader ) { try { if (loader.findClass(className)) { console .log("Successfully found loader" ); console .log(loader); Java.classFactory.loader = loader; } } catch (error) { console .log("find error:" + error); } }, onComplete : function ( ) { console .log("End" ); }, }); Java.use("[class_name]" ); }); }
或使用Java.enumerateClassLoadersSync()
API
内存搜索接口
枚举类的所有接口
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 function findAllInterfaces (packageName ) { Java.perform(function ( ) { Java.enumerateLoadedClasses({ onMatch :function (class_name ) { if (class_name.indexOf("fridatestapp.DynamicDex" )>=0 ){ return ; } if (class_name.indexOf(packageName)<0 ){ return ; }else { var clazz = Java.use(class_name); var interfaces = clazz.class.getInterfaces(); if (interfaces.length>0 ){ console .log(class_name,":" ); for (var i in interfaces){ console .log("\t" ,interfaces[i].toString()) } } } }, onComplete :function ( ) { console .log("findAllInterfaces end" ); } }) }) } function main ( ) { var packageName = "com.forgotten.fridatestapp" ; findAllInterfaces(packageName); }
寻找接口的实现类
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 function findImpByInterface (packageName,interfaceName ) { Java.perform(function ( ) { Java.enumerateLoadedClasses({ onMatch :function (class_name ) { if (class_name.indexOf("fridatestapp.DynamicDex" )>=0 ){ return ; } if (class_name.indexOf(packageName)<0 ){ return ; }else { var clazz = Java.use(class_name); var interfaces = clazz.class.getInterfaces(); if (interfaces.length>0 ){ for (var i in interfaces){ if (interfaces[i].toString().indexOf(interfaceName)>=0 ){ console .log(class_name,":" ,interfaces[i].toString()) } } } } }, onComplete :function ( ) { console .log("findImpByInterface end" ); } }) }) } function main ( ) { var packageName = "com.forgotten.fridatestapp" ; var interfaceName = "android.view.View$OnClickListener" ; findInterface(packageName, interfaceName); }
定位抽象类的实现类
打印所有类的父类
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 function findAllSuperclasses (packageName ) { Java.perform(function ( ) { Java.enumerateLoadedClasses({ onMatch :function (class_name ) { if (class_name.indexOf("fridatestapp.DynamicDex" )>=0 ){ return ; } if (class_name.indexOf(packageName)<0 ){ return ; }else { var hook_cls = Java.use(class_name); var superClass = hook_cls.class.getSuperclass(); console .log(class_name,":" ) while (superClass!=null ){ console .log("\t" ,superClass.toString()); superClass = superClass.getSuperclass(); } } }, onComplete :function ( ) { console .log("findAllSuperclasses end" ); } }) }) }
寻找继承某类的子类
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 function findChildBySuper (packageName,superClassName ) { Java.perform(function ( ) { Java.enumerateLoadedClasses({ onMatch : function (class_name ) { if (class_name.indexOf("DynamicDex" )>=0 ){ return ; } if (class_name.indexOf(packageName) < 0 ) { return ; } else { var hook_cls = Java.use(class_name); var superClass = hook_cls.class.getSuperclass(); while (superClass!=null ){ if (superClass.toString().indexOf(superClassName)>=0 ){ console .log("Found:" ,class_name,superClass); } superClass = superClass.getSuperclass(); } } }, onComplete : function ( ) { console .log("findChildBySuper end" ); }, }); }); }
打印参数
加载dex使用gson打印
1 2 3 4 5 6 Java.openClassFile("xxx.dex" ).load(); Java.openClassFile("/data/local/tmp/r0gson.dex" ).load(); const gson = Java.use('com.r0ysue.gson.Gson' );gson.$new().toJson( object );
查看类与类型强转
对于hook时,找不到相关属性(Frida不清楚类型的),可以使用Cast类型强转来指定类型。
1 2 3 4 5 6 7 8 .getClass().getName().toString() Java.cast() var WaterHandle = Java.cast(JuiceHandle,Java.use("com.r0ysue.a0526printout.Water" ))
1 2 3 4 5 6 7 8 9 10 11 12 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)); }
Array数组
hook array数组
1 2 3 4 Java.use("java.util.Arrays" ).toString.overload('[C' ).implementation = function (charArray ) {}
打印 array数组
使用gson打印
使用java.util.Arrays.toString()
打印
使用JSON.stringify()
打印
遍历
如果Frida直接打印可以显示为数组,可通过.length
属性进行for循环遍历
复杂类型的,如Frida直接打印显示为[[C
、[B
的,可以使用Java的Array
类。Array.getLength()
获得长度、Array.get()
获得对应元素,Array.getChar()
获得char元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function main ( ) { Java.perform(function ( ) { Java.use("java.util.Arrays" ).toString.overload("[C" ).implementation = function ( ) { console .log("arg = " , arguments [0 ]); console .log("arg = " , Java.use("java.util.Arrays" ).toString(arguments [0 ])); console .log("arg = " , JSON .stringify(arguments [0 ])); var arg = Java.array("char" , ["上" , "山" , "打" , "老" , "虎" ]); var result = this .toString(arg); console .log("[NEW]arg,result = " , arg, result); return result; }; }); }
byte[]打印
1 2 var ByteString = Java.use("com.android.okhttp.okio.ByteString" );console .log(ByteString.of(result).hex());
或者使用格式化打印十六进制数据
枚举
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Java.perform(function ( ) { Java.choose("com.forgotten.fridatestapp.construct.ConstructoredObject" , { onMatch : function (instance ) { var venum = Java.cast(instance.color.value, Java.use("com.forgotten.fridatestapp.construct.ConstructoredObject$Signal" )); console .log("venum:" , venum); console .log("venum.name():" , venum.name()); console .log("venum.ordinal():" , venum.ordinal()); }, onComplete : function ( ) { console .log("venum: search completed" ); }, }); });
HashMap类型
使用iterator迭代打印
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 function main ( ) { Java.perform(function ( ) { Java.choose("com.forgotten.fridatestapp.construct.ConstructoredObject" , { onMatch : function (instance ) { var vmap = Java.cast(instance.map.value, Java.use("java.util.HashMap" )); console .log("vmap:" , vmap); var key_iterator = vmap.keySet().iterator(); while (key_iterator.hasNext()) { var key = key_iterator.next().toString(); var value = vmap.get(key).toString(); console .log(key + ": " + value); } var entry_iterator = vmap.entrySet().iterator(); while (entry_iterator.hasNext()) { var entry = Java.cast(entry_iterator.next(),Java.use("java.util.HashMap$Node" )); console .log("entry" , entry); console .log(entry.getKey(), entry.getValue()); } console .log("vmap.toString():" , vmap.toString()); }, onComplete : function ( ) { console .log("vmap: search completed" ); }, }); }); }
构造参数
构造Object[]
1 result = Java.array("Ljava.lang.Object;" ,[Java.use("java.lang.Integer" ).$new (9 ),Java.use("java.lang.String" ).$new ("nihao" )])
构造 array数组
1 2 3 4 5 Java.array('char' , [ '烟' ,'村' ,'四' ,'五' ,'家' ]); Java.array('java.lang.String' , [ '烟' ,'村' ,'四' ,'五' ,'家' ]);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function main ( ) { Java.perform(function ( ) { var string1 = Java.use("java.lang.String" ).$new("123" ); var string2 = Java.use("java.lang.String" ).$new("" ); var Ref_arr = Java.use("java.lang.reflect.Array" ); var stringClass = Java.use("java.lang.String" ).class; var arr = Ref_arr.newInstance(stringClass, 2 ); Ref_arr.set(arr, 0 , string1); Ref_arr.set(arr, 1 , string2); var obj1 = Java.use("java.lang.String" ).$new("24717361" ); var obj2 = Java.use("java.lang.Integer" ).$new(19 ); var obj3 = Java.use("java.lang.String" ).$new("" ); var objarr = Java.array("java.lang.Object" , [arr, obj1, obj2, obj3]); console .log(JSON .stringify(objarr)) console .log(Java.use("java.util.Arrays" ).toString(objarr)); }); }
Others
Frida构造多线程(新建类)
新建线程(即实现类时实现Runnable接口)
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 function newThread ( ) { Java.perform(function ( ) { var Runnable = Java.use("java.lang.Runnable" ); var Thread = Java.use("java.lang.Thread" ); var MyRunnable = Java.registerClass({ name : "com.forgotten.thread" , implements : [Runnable], fields : { num : "int" , str : "java.lang.String" }, methods : { $init : [ { returnType : "void" , argumentTypes : ["int" ,"java.lang.String" ], implementation : function (num,str ) { this .num.value = num; this .str.value = str; }, }, ], run : function ( ) { var i = 0 ; for (i = 0 ; i < 10 ; i++) { console .log(this .num.value,this .str.value); this .num.value = this .num.value + 1 ; Thread.sleep(5 ); } }, }, }); var myRunnable = MyRunnable.$new(10 ,"hello" ); var myThread = Thread.$new(myRunnable); myThread.start(); }); }
获取context
1 2 3 4 5 6 7 8 9 10 11 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()); }) }
强制主线程运行
1 2 3 4 5 6 7 8 9 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(); }) })
java层代码模板复用
打印调用栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function printStack (name ) { Java.perform(function ( ) { var Exception = Java.use("java.lang.Exception" ); var ins = Exception.$new("Exception" ); var straces = ins.getStackTrace(); if (straces != undefined && straces != null ) { var strace = straces.toString(); var replaceStr = strace.replace(/,/g , "\n" ); console .log("=============================" + name + " Stack strat=======================" ); console .log(replaceStr); console .log("=============================" + name + " Stack end=======================\r\n" ); Exception.$dispose(); } }); }
1 2 3 4 5 6 7 8 function printStack (name ) { Java.perform(function ( ) { var throwable = Java.use("android.util.Log" ).getStackTraceString(Java.use("java.lang.Throwable" ).$new()); console .log("=============================" + name + " Stack strat=======================" ); console .log(throwable); console .log("=============================" + name + " Stack end=======================\r\n" ); }); }
1 2 3 var exception = Java.use("android.util.Log" ).getStackTraceString(Java.use("java.lang.Exception" ).$new());console .log(exception);
格式化打印十六进制数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function jhexdump (array, off, len ) { off = off || 0 ; len = len || 0 ; var llen = len == 0 ? array.length : len; var ptr = Memory.alloc(llen); for (var i = 0 ; i < llen; ++i) Memory.writeS8(ptr.add(i), array[i]); console .log(hexdump(ptr, { offset : off == 0 ? 0 : off, length : llen, header : false , ansi : false })); }
事件
hook Toast提示 定位
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function hook_toast ( ) { function printStack (name ) { Java.perform(function ( ) { var throwable = Java.use("android.util.Log" ).getStackTraceString(Java.use("java.lang.Throwable" ).$new()); console .log("=============================" + name + " Stack strat=======================" ); console .log(throwable); console .log("=============================" + name + " Stack end=======================\r\n" ); }); } Java.perform(function ( ) { var Toast = Java.use("android.widget.Toast" ); Toast.show.implementation = function ( ) { printStack("SHOW Toast" ); return this .show(); }; }); } setImmediate(hook_toast);
hook findViewById 定位组件
hook StartActivity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Java.perform(function ( ) { var Activity = Java.use("android.app.Activity" ); Activity.startActivity.overload('android.content.Intent' ).implementation=function (p1 ) { console .log("Hooking android.app.Activity.startActivity(p1) successfully,p1=" +p1); 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(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(decodeURIComponent (p1.toUri(256 ))); this .startService(p1); } })
hook onClick事件 定位
hook Keystore:客户端证书校验
Frida RPC(Remote procedure call)
RPC主动调用
rpc.exports导出名不可以有大写字母或者下划线
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 function invoke ( ) { Java.perform(function ( ) { Java.choose("com.forgotten.fridatestapp.HookedObject" , { onMatch : function (instance ) { console .log("found HookedObject:" , instance); console .log("ho.getPasswd():" , instance.getPasswd("123 ABC" )); }, onComplete : function ( ) { console .log("HookedObject: search complete." ); }, }); }); } function test ( ) { console .log("I'm Frida_rpc.js!" ); } rpc.exports = { invokefunc : invoke, testfunc : test, };
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 import timeimport fridadef my_message_handler (message,payload ): print (message) print (payload) device = frida.get_device_manager().add_remote_device("192.168.0.104:8888" ) pid = device.spawn(["com.forgotten.fridatestapp" ]) device.resume(pid) time.sleep(1 ) session = device.attach(pid) with open ("./frida_rpc.js" ) as f: script = session.create_script(f.read()) script.on("message" ,my_message_handler) script.load() command = "" while True : command = input ("Enter Command(y/t/n): " ) if command=="y" : script.exports.invokefunc() elif command=="t" : script.exports.testfunc() elif command=="n" : break
rpc动态修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Java.perform(function ( ) { Java.use("com.forgotten.fridatestapp.HookedObject" ).getPasswd.implementation = function ( ) { var string_to_send = arguments [0 ] + ":" + this .getPasswd(arguments [0 ]); var string_to_recv; send(string_to_send); recv(function (received_json_objection ) { console .log("recv in js:" ,JSON .stringify(received_json_objection)) string_to_recv = received_json_objection.my_data; console .log("string_to_recv:" , string_to_recv); }).wait(); var result = Java.use("java.lang.String" ).$new(string_to_recv); return result; }; });
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 import timeimport fridadef my_message_handler (message, payload ): print (message) print (payload) if message["type" ] == "send" : print (message["payload" ]) script.post({"my_data" : "Hello" }) device = frida.get_device_manager().add_remote_device("192.168.0.104:8888" ) pid = device.spawn(["com.forgotten.fridatestapp" ]) device.resume(pid) time.sleep(1 ) session = device.attach(pid) with open ("./frida_rpc.js" ) as f: script = session.create_script(f.read()) script.on("message" , my_message_handler) script.load() command = "" while True : command = input ("Enter `n` for leave: " ) if command == "n" : break
More
frida-python/examples at main · frida/frida-python (github.com)
Frida native层应用
Frida实现的Env:frida-java-bridge/env.js at main · frida/frida-java-bridge (github.com)
判断Thumb OR arm
判断Thumb OR arm模式:
IDA查看指令机器码长度,都为4字节为arm指令
push
指令为thumb指令特有
hook用户函数
attach静态注册(或导出)函数
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 function main0 ( ) { Java.perform(function ( ) { var lib_fridatestapp_addr = Module.findBaseAddress("libfridatestapp.so" ); console .log("native_lib_addr -> " , lib_fridatestapp_addr); var staticString_addr = Module.findExportByName("libfridatestapp.so" , "Java_com_forgotten_fridatestapp_MainActivity_staticString" ); console .log("staticString() addr -> " , staticString_addr); Interceptor.attach(staticString_addr, { onEnter : function (args ) { console .log("CCCryptorCreate called from:\n" + Thread.backtrace(this .context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\n" ) + "\n" ); console .log("Interceptor.attach staticString() args:" , args[0 ], args[1 ], args[2 ]); console .log("jstring is" , Java.vm.getEnv().getStringUtfChars(args[2 ], null ).readCString()); var new_arg2 = Java.vm.getEnv().newStringUtf("new arg2 from Frida" ); args[2 ] = new_arg2; }, onLeave : function (reval ) { console .log("Interceptor.attach staticString() retval" , reval); console .log("Interceptor.attach staticString() retval" , Java.vm.getEnv().getStringUtfChars(reval, null ).readCString()); var new_reval = Java.vm.getEnv().newStringUtf("HaHa Frida!!!" ); reval.replace(new_reval); }, }); }); }
主动调用静态注册(或导出)函数
注册native函数:new NativeFunction(address, returnType, argTypes[, abi])
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 function main1 ( ) { Java.perform(function invoke_justAdd_func ( ) { var lib_fridatestapp_addr = Module.findBaseAddress("libfridatestapp.so" ); console .log("native_lib_addr -> " , lib_fridatestapp_addr); var justAdd_addr = Module.findExportByName("libfridatestapp.so" , "_Z7justAddii" ); console .log("justAdd() addr -> " , justAdd_addr); var justAdd_func = new NativeFunction(justAdd_addr, "int" , ["int" , "int" ]); var justAdd_result = justAdd_func(10 , 2 ); console .log("invoke justAdd(10,2) result-> " , justAdd_result); }); Java.perform(function invoke_nativeString_func ( ) { var lib_fridatestapp_addr = Module.findBaseAddress("libfridatestapp.so" ); console .log("native_lib_addr -> " , lib_fridatestapp_addr); var staticString_addr = Module.findExportByName("libfridatestapp.so" , "Java_com_forgotten_fridatestapp_MainActivity_staticString" ); console .log("staticString() addr -> " , staticString_addr); var nativeString_func = new NativeFunction(staticString_addr, "pointer" , ["pointer" , "pointer" , "pointer" ]); Interceptor.attach(staticString_addr, { onEnter : function (args ) { console .log("Interceptor.attach staticString() args:" , args[0 ], args[1 ], args[2 ]); console .log("jstring is" , Java.vm.getEnv().getStringUtfChars(args[2 ], null ).readCString()); console .log("==> invoke stringfunc(): " , Java.vm.getEnv().getStringUtfChars(nativeString_func(args[0 ], args[1 ], args[2 ]), null ).readCString()); var new_arg2 = Java.vm.getEnv().newStringUtf("new arg2 from Frida" ); args[2 ] = new_arg2; }, onLeave : function (reval ) { console .log("Interceptor.attach staticString() retval" , reval); console .log("Interceptor.attach staticString() retval" , Java.vm.getEnv().getStringUtfChars(reval, null ).readCString()); var new_reval = Java.vm.getEnv().newStringUtf("HaHa Frida!!!" ); reval.replace(new_reval); }, }); }); }
replace静态注册(或导出)函数
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 function main2 ( ) { Java.perform(function replace_func ( ) { var lib_fridatestapp_addr = Module.findBaseAddress("libfridatestapp.so" ); console .log("native_lib_addr -> " , lib_fridatestapp_addr); var justAdd_addr = Module.findExportByName("libfridatestapp.so" , "_Z7justAddii" ); console .log("justAdd() addr -> " , justAdd_addr); Interceptor.replace( justAdd_addr, new NativeCallback( function (a, b ) { console .log("justAdd args: " , a, b); var result = a * (b + 5 ); console .log("new Func Result: " , result); return result; }, "int" , ["int" , "int" ] ) ); }); }
通过地址偏移操作未导出函数
通过ida找到函数偏移
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 function main3 () { Java.perform(function () { var lib_fridatestapp_addr = Module.findBaseAddress("libfridatestapp.so" ); console.log("native_lib_addr -> " , lib_fridatestapp_addr); var dynamicString_addr = lib_fridatestapp_addr.add(0xa48 ); console.log("dynamicString() addr -> " , dynamicString_addr); Interceptor.attach(dynamicString_addr, { onEnter: function (args) { console.log("Interceptor.attach dynamicString() args:" , args[0 ], args[1 ], args[2 ]); var new_arg2 = Java.vm.getEnv().newStringUtf("new arg2 from Frida" ); args[2 ] = new_arg2; }, onLeave: function (reval) { console.log("Interceptor.attach dynamicString() retval" , reval); console.log("Interceptor.attach dynamicString() retval" , Java.vm.getEnv().getStringUtfChars(reval, null ).readCString()); var new_reval = Java.vm.getEnv().newStringUtf("HaHa Frida!!!" ); }, }); }); }
模块相关操作
枚举出所有模块的所有导出符号
1 2 3 4 5 6 7 8 9 10 11 12 function EnumerateAllExports ( ) { var modules = Process.enumerateModules(); for (var i = 0 ; i < modules.length; i++) { var module = modules[i]; var module_name = modules[i].name; var exports = module .enumerateExports(); console .log("module.enumerateeExports" , JSON .stringify(exports )); } }
遍历某模块符号/导出/导入
1 2 3 4 5 6 7 8 9 function look_module (module_name ) { var native_lib_addr = Process.findModuleByAddress(Module.findBaseAddress(module_name)); console .log("native_lib_addr => " ,JSON .stringify(native_lib_addr)); console .log("enumerateImports=>" ,JSON .stringify(native_lib_addr.enumerateSymbols())); } look_module("linker64" );
JNI框架层的利用
JNI框架hook:
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 function hook_getStringUTFChars_func ( ) { var GetStringUTFChars_addr = null ; var symbools = Process.findModuleByName("libart.so" ).enumerateSymbols(); for (var i = 0 ; i < symbools.length; i++) { var symbol = symbools[i].name; if (symbol.indexOf("CheckJNI" ) == -1 && symbol.indexOf("JNI" ) >= 0 ) { if (symbol.indexOf("GetStringUTFChars" ) >= 0 ) { console .log("finally found GetStringUTFChars name:" , symbol); GetStringUTFChars_addr = symbools[i].address; console .log("finally found GetStringUTFChars address :" , GetStringUTFChars_addr); } } } Interceptor.attach(GetStringUTFChars_addr, { onEnter : function (args ) { console .log("art::JNI::GetStringUTFChars(_JNIEnv*,_jstring*,unsigned char*)->" , args[0 ], Java.vm.getEnv().getStringUtfChars(args[1 ], null ).readCString(), args[2 ]); }, onLeave : function (retval ) { console .log("retval is->" , retval.readCString()); }, }); }
JNI框架replace:
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 function replace_NewStringUTF_func ( ) { var NewStringUTF_addr = null ; var symbools = Process.findModuleByName("libart.so" ).enumerateSymbols(); for (var i = 0 ; i < symbools.length; i++) { var symbol = symbools[i].name; if (symbol.indexOf("CheckJNI" ) == -1 && symbol.indexOf("JNI" ) >= 0 ) { if (symbol.indexOf("NewStringUTF" ) >= 0 ) { console .log("finally found NewStringUTF_name:" , symbol); NewStringUTF_addr = symbools[i].address; console .log("finally found NewStringUTF_address :" , NewStringUTF_addr); } } } var NewStringUTF = new NativeFunction(NewStringUTF_addr, "pointer" , ["pointer" , "pointer" ]); Interceptor.replace( NewStringUTF_addr, new NativeCallback( function (arg1, arg2 ) { console .log("NewStringUTF arg1,arg2->" , arg1, arg2.readCString()); var newARG2 = Memory.allocUtf8String("newPARG2" ); var result = NewStringUTF(arg1, arg2); return result; }, "pointer" , ["pointer" , "pointer" ] ) ); }
native层代码模板复用
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 27 28 function writeSomething (path, contents ) { var fopen_addr = Module.findExportByName("libc.so" , "fopen" ); var fputs_addr = Module.findExportByName("libc.so" , "fputs" ); var fclose_addr = Module.findExportByName("libc.so" , "fclose" ); var fopen = new NativeFunction(fopen_addr, "pointer" , ["pointer" , "pointer" ]); var fputs = new NativeFunction(fputs_addr, "int" , ["pointer" , "pointer" ]); var fclose = new NativeFunction(fclose_addr, "int" , ["pointer" ]); var fileName = Memory.allocUtf8String(path); var mode = Memory.allocUtf8String("a+" ); var fp = fopen(fileName, mode); var contentHello = Memory.allocUtf8String(contents); var ret = fputs(contentHello, fp); fclose(fp); }
遍历module的导出表与符号表
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 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 function writeSomething (path, contents ) { var fopen_addr = Module.findExportByName("libc.so" , "fopen" ); var fputs_addr = Module.findExportByName("libc.so" , "fputs" ); var fclose_addr = Module.findExportByName("libc.so" , "fclose" ); var fopen = new NativeFunction(fopen_addr, "pointer" , ["pointer" , "pointer" ]); var fputs = new NativeFunction(fputs_addr, "int" , ["pointer" , "pointer" ]); var fclose = new NativeFunction(fclose_addr, "int" , ["pointer" ]); var fileName = Memory.allocUtf8String(path); var mode = Memory.allocUtf8String("a+" ); var fp = fopen(fileName, mode); var contentHello = Memory.allocUtf8String(contents); var ret = fputs(contentHello, fp); fclose(fp); } function attach (name, address ) { console .log("attaching " , name); Interceptor.attach(address, { onEnter : function (args ) { console .log("Entering => " , name); }, onLeave : function (retval ) { }, }); } var app_packagename = "com.forgotten.learntest" ;function traceNativeExport ( ) { var modules = Process.enumerateModules(); for (var i = 0 ; i < modules.length; i++) { var module = modules[i]; if (module .name.indexOf("libssl.so" ) < 0 ) { continue ; } var path = "/data/data/" + app_packagename + "/cache/" + module .name + "_exports.txt" ; var exports = module .enumerateExports(); for (var j = 0 ; j < exports .length; j++) { console .log("module name is =>" , module .name, " symbol name is =>" , exports [j].name); writeSomething(path, "type: " + exports [j].type + " function name :" + exports [j].name + " address : " + exports [j].address + " offset => 0x" + exports [j].address.sub(modules[i].base) + "\n" ); if (exports [j].name.indexOf("SSL_write" ) >= 0 ) { attach(exports [j].name, exports [j].address); } } } } function traceNativeSymbol ( ) { var modules = Process.enumerateModules(); for (var i = 0 ; i < modules.length; i++) { var module = modules[i]; if (module .name.indexOf("linker64" ) < 0 ) { continue ; } var path = "/data/data/" + app_packagename + "/cache/" + module .name + "_symbols.txt" ; var exports = module .enumerateSymbols(); for (var j = 0 ; j < exports .length; j++) { if (exports [j] == null ) { continue ; } console .log("module name is =>" , module .name, " symbol name is =>" , exports [j].name); writeSomething(path, "type: " + exports [j].type + " function name :" + exports [j].name + " address : " + exports [j].address + " offset => 0x" + exports [j].address.sub(modules[i].base) + "\n" ); } } } function main ( ) { console .log("Entering main" ); traceNativeExport(); traceNativeSymbol(); } setImmediate(main);
读写 std::string
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function 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); } }
Frida打印String[]
1 2 3 4 5 6 7 8 9 10 11 const len = Java.vm.getEnv().getArrayLength(jstringArr);console .log("len" , len);for (var i=0 ;i<len;i++){ var obj = Java.vm.getEnv().getObjectArrayElement (jstringArr,i); var element = Java.vm.getEnv().getStringUtfChars(obj, null ).readCString(); console .log("第" +i+"个:" + element) }
相关脚本使用
Unpackers
FRIDA-DEXDump
hluwa/frida-dexdump: A frida tool to dump dex in memory to support security engineers analyzing malware. (github.com)
frida_fart
hanbinglengyue/FART: ART环境下自动化脱壳方案 (github.com)
frida_dump
lasting-yang/frida_dump: frida dump dex, frida dump so (github.com)
NetWork
r0capture
r0ysue/r0capture: 安卓应用层抓包通杀脚本 (github.com)
OkHttpLogger-Frida
siyujie/OkHttpLogger-Frida: Frida 实现拦截okhttp的脚本 (github.com)
frida_ssl_logger
BigFaceCat2017/frida_ssl_logger: ssl_logger based on frida (github.com)
okhttp-sslunpinning
bxl0608/okhttp-sslunpinning (github.com)
Trace
jnitrace
chame1eon/jnitrace: A Frida based tool that traces usage of the JNI API in Android apps. (github.com)
frida_hook_libart
lasting-yang/frida_hook_libart: Frida hook some jni functions (github.com)
frida-trace
frida-trace | Frida • A world-class dynamic instrumentation framework
Memory
Wallbreaker
hluwa/Wallbreaker: 🔨 Break Java Reverse Engineering form Memory World! (github.com)
Or