Java层 hook程序流程中被调用的普通方法,重写该方法的实现 模板:
1 2 3 4 5 6 7 8 9 10 11 12 Java .perform (function ( ) { var <class_reference> = Java .use ("<package_name>.<class>" ); <class_reference>.<method_to_hook>.implementation = function (<args> ) { } })
implementation中可以通过this.mathod_name()来调用原本类下的实现,同样也可以声明一个变量接收方法的返回值
传入的参数可以自己定,注意类型要与原方法相同
适用于button之类的触发式交互
重载方法通过这样子定义:
1 2 3 4 5 6 7 <class_reference>.<method_to_hook>.overload ('data_type1' ,'data_type2' ).implementation = ... Act .check .overload ('int' , 'int' ).implementation = function (a, b ){ console .log ("random number:" +a); console .log ("your input:" +b); this .check (a,b); }
但是重载字符串的时候要注意,java是没有字符串这个数据类型的,因此重载的数据类型要写入java.lang.String
例子:
1 2 3 4 5 6 7 8 9 10 11 function test ( ){ Java .perform (function ( ){ var Act = Java .use ("com.ad2001.frida0x1.MainActivity" ); Act .check .overload ('int' , 'int' ).implementation = function (a, b ){ console .log ("random number:" +a); console .log ("your input:" +b); this .check (a,b); } }); } test ();
hook类中的构造方法,重写实现 模板:
1 2 3 4 5 6 7 8 9 10 11 12 Java .perform (function ( ) { var <class_reference> = Java .use ("<package_name>.<class>" ); <class_reference>.$init .implementation = function (<args> ) { } })
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 setTimeout (function ( ){ if (Java .available ) { Java .perform (function ( ){ console .log ("start hook" ); var User = Java .use ("com.roysue.roysueapplication.User" ); if (User != undefined ) { User .$init .implementation = function (name,age ){ console .log ("name:" +name); console .log ("age:" +age); this .$init(name, age); } } else { console .log ("User: undefined" ); } console .log ("hook end" ); }); } });
hook类的静态方法,重写实现 静态方法对实例无依赖,无需实例就能调用,只要有类在。因此是比较简单的hook
模板:
1 2 3 4 5 6 7 8 9 10 11 12 Java .perform (function ( ) { var <class_reference> = Java .use ("<package_name>.<class>" ); <class_reference>.<static_method>.implementation = function (<args> ) { } })
调用类的普通方法或修改成员变量,拥有实例 通过java.choose在堆上扫描实例化的对象
模板:
1 2 3 4 5 6 7 8 9 Java .performNow (function ( ) { Java .choose ('<Package>.<class_Name>' , { onMatch : function (instance ) { instance.var .value = something; }, onComplete : function ( ) {} }); });
onMatch在每次实例被匹配到时执行,function(instance)中的instance就是匹配到的实例对象,你可以将它写成你喜欢的名字,不必时instance,比如ins。用instance.mathod()来直接调用该实例中可调用的方法
onComplete在每次Java.choose结束时执行,可有可无
例子:
1 2 3 4 5 6 7 8 9 10 function main ( ){ Java .perform (function ( ){ Java .choose ("com.ad2001.frida0x5.MainActivity" , { onMatch : function (ins ){ ins.flag (1337 ); } }) }) } main ()
调用类的普通方法,但缺少实例 缺少实例我们可以new()一个(MainActivity类之外的实例方法可以这么做)
模板:
1 2 3 4 5 6 7 Java .perform (function ( ) { var <class_reference> = Java .use ("<package_name>.<class>" ); var <class_instance> = <class_reference>.$new(); <class_instance>.<method>(); })
在该实例中我们可以随便执行方法,传入我们想传入的参数,给成员变量赋值
例子:
1 2 3 4 5 6 7 8 9 10 function main ( ){ Java .perform (function ( ){ let check = Java .use ("com.ad2001.frida0x4.Check" ); let ch = check.$new(); let result = ch.get_flag (1337 ); console .log ("result" +result); console .log ("test" ) }) } main ()
MainActivity中的未创建实例的普通方法,我们不能创建实例,MainActivity可以看作是安卓程序的入口,具有唯一性,新建的实例没有上下文,不会参与到被Hook的程序已经执行的流程中去,所以我们要在内存中找到目标进行Hook。
Java.performNow :用于在 Java 运行时上下文中执行代码的函数。
Java.choose:在运行时枚举指定 Java 类的实例(作为第一个参数提供)。
1 2 3 4 5 6 7 8 9 Java .performNow (function ( ) { Java .choose ('com.ad2001.frida0x5.MainActivity' , { onMatch : function (instance ) { console .log ("Instance found" ); instance.flag (1337 ); }, onComplete : function ( ) {} }); });
拦截内部类 1 2 3 4 5 6 7 8 9 10 11 12 Java .perform (function ( ) { var <class_reference> = Java .use ("<package_name>.<class>.User$inner_class" ); <class_reference>.<method_to_hook>.implementation = function (<args> ) { } })
拦截动态加载dex 先通过枚举查找类加载器,然后寻找目标类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function hook_dyn_dex ( ) { Java .perform (function ( ) { Java .enumerateClassLoaders ({ onMatch : function (loader ) { try { if (loader.findClass ("com.xxx" )) { console .log (loader); Java .classFactory .loader = loader; } } catch (error) { } }, onComplete : function ( ) { } }); var DynamicCheck = Java .use ("com.xxx" ); console .log (DynamicCheck ); DynamicCheck .check .implementation = function ( ) { console .log ("DynamicCheck.check" ); return true ; } }); }
Native层 Native层一般是一些本地服务和链接库,让我想起之前linux pwn中的elf文件动态链接,令人惊讶的是native层也是通过c语言和c++实现的。让我更震惊的是,在pwn中很多想都不敢想的操作——监视链接库函数调用,随意调用链接库函数,直接修改返回值,甚至修改.text段的权限,通过Frida都可以举重若轻的完成。
通过Module对象枚举、查找函数 Module就是模块,链接库,在frida中他作为一个对象有一个这样的结构:
索引
属性
含义
1
name
模块名称
2
base
模块地址,其变量类型为NativePointer
3
size
大小
4
path
完整文件系统路径
例子:
1 2 3 4 5 6 7 function frida_module ( ){ Process .EnumererateModules (); console .log (Module .enumerateImports ('lib****.so' )); console .log (Module .enumerateExports ('lib****.so' )); method_addr = addrModule.findExportByName (‘lib****.so ',' method_name');//已知要查找的具体函数的名字的情况下,在链接库的导出库中查找该函数的地址,返回十六进制地址 base_addr = Module.findBaseAddress(name);//返回模块(即链接库)的基地址 }
顺便一提,安卓的链接库地址是自动开启aslr的,但同样也能通过计算基址偏移找到函数地址
module对象下的方法名字都很长,但都遵循首字母小写的驼峰命名法
通过以上方法,结合分析apk文件反编出的Java代码,可以很容易的找出我们感兴趣的链接函数地址和函数链接情况
getExportByName方法的模块参数可有可无
通过Interceptor对象拦截函数 获取到模块函数的地址之后Interceptor.attach方法可以直接拦截该函数的调用,读取甚至更改参数和返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Interceptor .attach (targetAddress, { onEnter : function (args ) { console .log ('Entering ' + functionName); var arg0 = Memory .readUtf8String (args[0 ]); console .log (arg0) }, onLeave : function (retval ) { console .log ('Leaving ' + functionName); console .log ("Original return value :" + retval); retval.replace (1337 ) } });
attach方法第一个参数传入目标函数的地址,第二个参数是两个回调函数,和Java.choose一样,onEnter是进入函数时触发,onLeave是函数退出时触发,不同的是这两个回调函数传入的分别是参数数组和返回值
replace方法极其强大,它不仅能更改参数和返回值,他甚至能直接劫持链接函数的实现!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Java .perform (function ( ) { var add_method = new NativeFunction (Module .findExportByName ('libhello.so' , 'c_getSum' ), 'int' ,['int' ,'int' ]); console .log ("result:" ,add_method (1 ,2 )); Interceptor .replace (add_method, new NativeCallback (function (a, b ) { return 123 ; }, 'int' , ['int' , 'int' ])); console .log ("result:" ,add_method (1 ,2 )); }); }
当然,如果你觉得直接劫持函数不方便,你甚至还可以自己new一个!
通过Nativepointer对象创建函数 1 2 3 var native_adr = new NativePointer (<address_of_the_native_function>);const native_function = new NativeFunction (native_adr, '<return type>' , ['argument_data_type' ]);native_function (<arguments >);
在定义的时候必须要将参数类型个数和参数类型以及返回值完全匹配,假设有三个参数都是int,则new NativeFunction(address, returnType, ['int', 'int', 'int']),而返回值是int则new NativeFunction(address, 'int', argTypes[, options]),必须要全部匹配,并且第一个参数一定要是函数地址指针。
例子:
1 2 3 4 var adr = Module .findBaseAddress ("libfrida0xa.so" ).add (0x18BB0 ) var get_flag_ptr = new NativePointer (adr);const get_flag = new NativeFunction (get_flag_ptr, 'void' , ['int' , 'int' ]);get_flag (1 ,2 );
通过Memory对象进行内存读写 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const baseAddr = Module .findBaseAddress ('libleidian.so' )console .log (baseAddr.add (0x1234 ).readCString ())console .log (hexdump (baseAddr.add (0x1234 )))console .log (baseAddr.add (0x1234 ).readByteArray (16 ))console .log (Memory .readByteArray (baseAddr.add (0x2c00 ), 16 )) baseAddr.add (0x1234 ).writeByteArray (stringToBytes ('xiaojianbang' )) Memory .alloc ()Memory .allocUtf8String ()Memory .protect (ptr (libso.base ), libso.size , 'rwx' )
reference Frida Java Hook 详解(安卓9):代码及示例(上)
https://github.com/DERE-ad2001/Frida-Labs
FRIDA-API使用篇:Java、Interceptor、NativePointer(Function/Callback)使用方法及示例-安全KER - 安全资讯平台
FRIDA-API使用篇:rpc、Process、Module、Memory使用方法及示例-安全KER - 安全资讯平台
通过Frida-Labs入门Frida | Closure