frida是一款基于python + java 的hook框架,可运行在Android、iOS、Linux、Windows、MAC OS X各平台,主要使用动态二进制插桩(dynamic binary instrumentation ,DBI)技术。
动态二进制插桩技术,可以在不影响程序动态执行结果的前提下,按照用户的分析需求,在程序执行过程中插入特定分析代码,实现对程序动态执行过程的监控与分析。目前,应用广泛的动态二进制分析平台有Pin,DynamoRIO和Frida等。
这篇文章针对已经对Frida有过了解的初学者,对Frida的实际操作中常用方法进行总结。如果你还没有使用过Frida,可以先阅读Frida的官方文档 ,里面很详细地说明了Frida的功能和使用方式。
Python脚本的撰写 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 from __future__ import print_function import frida import sys #进程名 process_name = 'myprocess' #导入的js脚本 js_file_name = 'myhookjs.js' # 自定义回调函数 数据通过send(message [,data])传递给python的on_message(消息,数据)函数,其中我们前面已经介绍过了, 第一个参数是一个python字典类型,其中的message['payload']存放的就是第一个参数内容 def on_message(message, data): if message['type'] == 'send': print(message['payload']) elif message['type'] == 'error': print(message['stack']) # hook逻辑脚本 def get_js_code(): js_file = open(js_file_name) # type: BinaryIO return js_file.read() #start here if __name__ == '__main__': # 注入进程,attach传入进程名称(字符串)或者进程号(整数) process_id = 0 device = frida.get_usb_device() # 循环等待,根据进程名查找进程pid。找到执行hook while True: try: process1 = device.get_process(process_name) process_id = process1.pid #也可用 #pid = device.spawn([“com.android.chrome”]) print(process_id) break except: pass session = device.attach(process_id) # 指定JavaScript脚本 #script = session.create_script(get_js_code()% int(sys.argv[1], 16))) script = session.create_script(get_js_code()) script.on('message', on_message) script.load() # 读取返回输入 sys.stdin.read() #int()函数把字符串表示的16进制数转换成整数 #上面的jscode % int(sys.argv[1], 16)是python格式化字符串的语法
Frida hook Object-C 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 var className = "className"; var funcName = "functionName"; var hook = eval('ObjC.classes.' + className + '["' + funcName + '"]'); #Interceptor.attach(target, callbacks) #target是NativePointer指定要拦截调用的函数的地址 #如果从Frida API获取地址(例如Module.getExportByName()),Frida将处理详细信息 Interceptor.attach(hook.implementation,{ #回调函数给出一个参数 args,可用于读取或写入参数作为NativePointer对象数组 onEnter: function(args) { }, #给定一个参数的回调函数,该参数 retval是NativePointer包含原始返回值的衍生对象 #请注意,此对象在onLeave调用中循环使用,因此请勿在回调之外存储和使用它。如果需要存储包含的值,请进行深层复制,例如:ptr(retval.toString()) onLeave: function(retval) { } } );
查看参数类型 1 2 #objc的函数,第0个参数是id,第1个参数是SEL,真正的参数从args[2]开始 console.log("Type of arg[2] -> " + new ObjC.Object(args[2]).$className)
参数(返回值)NS与js类型转换 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 #log String var myString = new ObjC.Object(args[2]); console.log("String argument: " + myString.toString()); #NSString(NCFString) to String var NSString = new ObjC.Object(args[2]); var str = NSString.UTF8String(); #replace js String str = str.replace(/BJP/,"HZH"); #log String console.log(str); #NSNumber to Int var myNumber = args[3].toInt32(); console.log(myNumber); #Converting NSData to String var data = new ObjC.Object(args[2]); var myString = data.bytes().readUtf8String(data.length()); console.log(myString); #Converting NSData to Base64String var myString = new ObjC.Object(args[2]); var base = myString.base64EncodedStringWithOptions_(0) console.log("String argument: " + base); Tip: 2nd argument (number of bytes) is not required if the string data is null-terminated. #Converting NSData to Binary Data var data = new ObjC.Object(args[2]); data.bytes().readByteArray(data.length());
替换参数 1 2 3 var str ="hello"; var newstring = ObjC.classes.NSString.stringWithString_(str); args[2] = newstring;
替换返回值 1 2 3 4 #用整数1337替换返回值 retval.replace(1337) #用指针替换 retval.replace(ptr("0x1234"))
调用函数 1 2 3 4 5 6 var st = Memory.allocUtf8String("TESTMEPLZ!"); #In NativeFunction param 2 is the return value type, #and param 3 is an array of input types var f = new NativeFunction(hook.implementation, 'pointer', ['pointer','char','pointer']); #f(st,0,NSString1);
通过一个函数获得其他函数地址进行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 var className = "DTURLRequestOperation"; var funcName = "- rpcV1Sign:newSign:request: "; var hook = eval('ObjC.classes.' + className + '["' + funcName + '"]'); var rpcV1SignAddr = hook.implementation; console.log('rpcV1SignAddr: ' + rpcV1SignAddr ); /* var className2 = "DTURLRequestOperation"; var funcName2 = "- avmpSign: "; var hook2 = eval('ObjC.classes.' + className2 + '["' + funcName2 + '"]'); var avmpSignAddr = hook2.implementation; console.log('avmpSignAddr: ' + avmpSignAddr ); */ #add的这个偏移是通过IDA的静态地址相减得到的 var avmpSignAddr = rpcV1SignAddr.add(0x1DCE); console.log('avmpSignAddr: ' + avmpSignAddr); Interceptor.attach(avmpSignAddr, { onEnter: function(args){ console.log("onEnter"); console.log(args[0]); console.log(args[1]); }, onLeave: function(retval){ console.log("onLeave"); console.log(retval); } });