Frida在iOS平台进行OC函数hook的常用方法

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);

}
});