1,frida-server
下面是frida官网给出的Android环境下hook的使用demo:
其中中间绿色部分是js代码,上下部分是python语言;
python语言部分只是单纯的为了将js代码发送到设备而已,核心功能还是在js部分实现;
1,js语言是弱语言,不对变量类型做强检查,所以我们可以都用var
表示;
2,java中的类都用java.use
获取;
3,js代码function大括号内部是用于hook的主要代码,其余部分基本不变
import frida, sys def on_message(message, data): if message['type'] == 'send': print("[*] {0}".format(message['payload'])) else: print(message) jscode = """ Java.perform(function () { // Function to hook is defined here var MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity'); // Whenever button is clicked var onClick = MainActivity.onClick; onClick.implementation = function (v) { // Show a message to know that the function got called send('onClick'); // Call the original onClick handler onClick.call(this, v); // Set our values after running the original onClick handler this.m.value = 0; this.n.value = 1; this.cnt.value = 999; // Log to the console that it's done, and we should have the flag! console.log('Done:' + JSON.stringify(this.cnt)); }; }); """ process = frida.get_usb_device().attach('com.example.seccon2015.rock_paper_scissors') script = process.create_script(jscode) script.on('message', on_message) print('[*] Running CTF') script.load() sys.stdin.read()
下面以姜维在CSDN上给出的几个例子来讲解记录一下这部分js代码的用法:
java层hook:
1,hook构造方法
原方法:

hook代码:

首先脚本中使用Java.use方法通过类名获取类类型,然后构造方法是固定写法:
$init
;这个要记住,然后因为需要重载所以用overload(……)形式即可,参数和参数之间用逗号隔开即可,这里用
return
重新调用了原来方法。
2,hook普通方法(static、private、public等)
原方法:

hook代码:

这里的用法和构造方法基本一致,就是把
$init
换成了方法名而已;
3,修改参数和返回值
参数和返回值的修改在上面内容很容易看出来,就是在方法拦截后通过arguments参数去获取传入参数,然后修改,返回值的话直接修改return函数就好了,这里重点讲的是自定义类型的参数:

构造一个新对象的方法有很多,这里选择最简单的
$new
即可,上面那行注释也是可以用的,这里的返回值直接修改成了我们构造出来的这个对象coinObj,但是如果要修改一个对象的内部值的话,直接用对象名加参数是不行的,那么就需要用反射了:

其中
java.cast(Object.getClass(),).getDeclaredField("")
的固定用法记住即可;
4,打印方法的堆栈信息
可以用来查看方法的调用链关系,这里提供两种思路,一种是自己编写一个打印堆栈的方法,弄成dex注入到程序中,需要打印的时候直接调用;第二种就是简单粗暴的直接在打印的地方构造一个异常对象然后抛出,但是这种方法会因为异常直接导致程序崩溃,不过无所谓了,我们可以看到调用关系就好了;
Native层hook:
1,hook未导出的函数

首先计算出目标函数实际地址=函数偏移+so基址+1,+1是因为要标识arm和thumb指令区别;
然后通过实际地址构造NativePointer
对象;
通过拦截器模式attach到目标开始hook,这里的onEnter和onLeave与xposed中的before和after很像,其实功能也一样,都是在函数开始前和结束后;
var nativePointer = new NativePointer(实际地址)
这里读取到的参数其实一个指针,也就是一个地址,想要获取内容需要用到Memory.readCString(地址)
来打印参数;读取返回值的时候,因为C语言中是多数情况是通过参数来返回函数结果的,我们就把参数地址里的内容打印出来,如果有多个参数就全打印出来再分析,这里是将返回结果的字节数组按字节打印,然后拼接在一起在打印出来的;
2,hook导出的函数

这里省去了手动计算地址的过程,只需要传入so名字和函数名字即可,如果是c++的话则需要注意,因为C++支持重载,所以导出函数名会不同;
这里通过函数名可以知道就是一个native函数了,那么他第一个参数肯定是JNIEnv指针,第二个参数是jclass类型,这个是标准的如果是静态方法第二个参数没啥用,后面的参数就是真的传递到native层的值了,比如这里Java层的方法:

放到native层就变成4个参数了:

只有后面两个才是我们需要的,然后查看hook返回结果:

这里可以看到传入参数都返回了,但是函数return值是空的,这是因为这里的返回值是一个jString类型,看过jni.h文件的同学应该知道,jni里有一套自己的数据类型定义,虽然格式都和java一致,但是他属于自定义类型;

这里new一个NativeFunction的用法可以查看api:

至于获取jString的方法官网也有说明,很简单:
var env =Java.vm.getEnv(); var jstring = env.newStringUtf("HelloWorld");

2,frida-Gadget
frida-gadget作为一个动态库文件无法直接在linux/Android环境下运行,需要用过将apk反编译成smali代码,然后通过System.loadlibrary("frida-Gadget");
的形式将so动态库运行起来,此时Frida-server就是以应用的权限运行起来的,在应用的沙箱中是拥有全部权限的,包括拦截、插桩等操作;
1,程序首先要打开可调试状态开关,
比如AndroidManifest文件中的Application中增加android:debuggable="true"
标签
2,将frida-gadget.so放到目标apk的lib文件夹下,将so的名字改成lib***.so
的格式

3,植入点通常放在Application的入口处,因为尽可能早的hook住程序就可以避免漏过方法执行;
在smali代码下表现为Application中的构造函数处,.methods static constructor <clinit>()V
static{ // //loadlibary里 要把SO文件名的lib和后缀去掉。libfgma.so --> fgma System.loadLibrary("fgma"); }


如果一个应用有多个进程的话则不必将so的植入点放到最初application的构造函数中,可以选择将植入点选在新进程开启后的合适的地点,最好还是构造函数初始化过程,但不限于最早组件,可选择适当时机启动的组件,如下,并在加载后使进程休眠20s的时间,用来等待粘贴js代码;

4,程序运行到so启动时会卡住,此时是在等待客户端像服务端发送建立连接的命令
此时在pc端命令行输入adb shell netstat -ls
查看网络端口会发现有27042的消息端口;
如果在pc端命令行输入frida -U Gadget
命令即可与之建立连接,此过程相当于frida-server模式下的python注入过程,在此建立连接之后我们可以直接输入js代码去执行目的代码,
例如:
//打印目的内存值 var tar = new NativePointer(0x94b778d9);send(hexdump(this.tar));
//获取目的函数模块 var nativePointer = Module.findExportByName(null,'SeedDecryptAndIDEncrypt'); //打印目的函数地址 send("smm native pointers:" + nativePointer); //连接目的函数进程 Interceptor.attach(nativePointer,{ onEnter: function(args){ //打印目标函数传入参数值 send("sDIDE args: args[0]:" + args[0] + ", args[1]:" + args[1] + ", args[2]:" + args[2] + ", args[3]:" + args[3]); //打印目的函数参数作为地址指向的内存的数值 send("smm sDIDE Memory args: args[0]:" + Memory.readInt(args[0]).toString() + ", args[2]:" + Memory.readInt(args[2]).toString() + ", args[3]:" + Memory.readInt(args[3]).toString()); }, onLeave: function(retval){ //打印目标函数的返回值 send("smm SeedDecryptAndIDEncrrypt retval:" + retval.toString()); } });
注意此处的方法名使用单引号,findExportByName()
函数的第一个参数可以用so名字或者为空;
以上就是frida的主要用法,其实frida的功能还有很多,而且不限于Android平台,总之,大佬们的总结是个好东西,官网api也是个好东西。
作者:M_天河
链接:https://www.jianshu.com/p/51e6aef175a2
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。