Frida iOS使用总结
Frida:
TIPS:
1.frida-trace -m "*[x y]" 这里面的*就是包含+号类方法,和-号实例方法(+为类方法,这类方法是可以直接用类名来调用的)(-为实例方法,必须使用类的实例才可以调用的)
2.系统的方法前缀一般是两个字母开头,并且是大写,如:NS,UI,WK,CF等
- 使用局域网连接
ssh [email protected] -p 22 # 登录设备
/usr/sbin/frida-server -l 0.0.0.0:6666 # 手动启用服务,监听 6666 端口
frida -H 192.168.50.227:6666 -n Springboard # 电脑端连接远程设备
- 加载 JS 注入到指定名字的进程
frida -n Twitter -l demo1.js # 本机
frida -U -n Twitter -l demo1.js # USB 设备
frida -U -f com.sougu.MyBus -l demo1.js --no-pause # 启动应用,并且启动后不暂停
- 跟踪 Native API
frida-trace -n Twitter -i "*URL*" # 本机
frida-trace -U Twitter -i "*URL*" # USB设备上的Twitter
- 系统自带加解密库
frida-trace -U -i "CCCrypt" 应用名(进程id)
frida-trace -U -i "CC_MD5" 应用名(进程id)
frida-trace -U -i "SecKeyEncrypt" 应用名(进程id)
- 跟踪 Objective-C API
frida-trace -U -f 'com.xxxx.xxx' -m "-[NSURL* *HTTP*]"
frida-trace -U Twitter -m "-[NSURL* *HTTP*]"
frida-trace -U -p 6604 -m "-[NSURL* *HTTP*]"
frida-trace -U QQ -m "-[CFT_PayCenterBusi *]" -m "+[CFT_PayCenterBusi init*]" -m "*[QIMService postRegisteNotification:Object:userInfo:]" # 追踪多个,实例方法,类方法,所有方法
- 追溯一个 Objective-C 方法调用
# Add the following code to the onEnter event-handler in the auto-generated JS of the desired API
log('\tBacktrace:\n\t' + Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n\t'));
- 拦截器 frida-trace oc方法hook:
Tips:使用frida-trace生成出来的代码args[]是一个数组
args[0] = 当前对象
args[1] = 当前的方法名
args[2]= 第一个参数
args[3]= 第二个参数
.....
frida-trace -U -m "类方法+/实例方法-[类名 方法名:]" 应用名
-m hook某OC方法 -M 排除某OC方法
-i 跟踪某个函数 -x 排除某个函数
-a 跟踪某一个地址,需要指定模块名称
执行后找到相应的js文件函数块添加相应代码打印内容
var objcData = new ObjC.Object(args[2])
//转oc对象然后打印<br>.readUtf8String()<br>.UTF8String()<br>
Webview加载,加载网页,Webview中的身份验证会话
frida-trace -U -m "*[*Webview load*]" -m "*[SFSafariViewController init*]" -m "*[*AuthenticationSession init*]"
- hook c函数:
Interceptor.attach(Module.findExportByName(null, "方法"), {
onEnter: function(args) {
console.log("方法");
},
onLeave: function(retval) {
console.log("after之后操作");
},
})
- hook oc方法//采用API查找器和拦截器组合使用
var resolver = new ApiResolver('objc');
//objc为要过滤的类
resolver.enumerateMatches('*[objc *]', {
onMatch: function(match) {
var method = match['name'];
var implementation = match['address'];
// 过滤需要拦截的方法objc_method
if ((method.indexOf("objc_method") != -1)) {
console.log(match['name'] + ":" + match['address']);
try {
Interceptor.attach(implementation, {
onEnter: function(args) {
//参数打印
var className = ObjC.Object(args[0]);
var methodName = args[1];
var arg_info = ObjC.Object(args[2]);
console.log("className: " + className.toString());
console.log("methodName: " + methodName.readUtf8String());
console.log("arg_info: " + arg_info.toString());
},
onLeave: function(retval) {
}
});
} catch (err) {
console.log("[!] Exception: " + err.message);
}
}
},
onComplete: function() {
}
});
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)
{
}
}
);
hook oc 方法2:implementation
//hook +[NSURL URLWithString:] var method = ObjC.classes.NSURL['+ URLWithString:']; var origImp = method.implementation; method.implementation = ObjC.implement(method, function (self, sel, url){ console.log("+ [NSURL URLWithString:]"); var urlString = ObjC.Object(url); console.log("url: " + urlString.toString()); return origImp(self, sel, url); //调用原方法,如果不调用则原方法得不到执行 //替换参数,将 URL 替换成 http://www.ioshacker.net //var newUrl = ObjC.classes.NSString.stringWithString_("http://www.ioshacker.net"); //return origImp(self, sel, newUrl); });
- 将 NSData 转换为字符串
var data = new ObjC.Object(args[2]);
Memory.readUtf8String(data.bytes(), data.length());
# Tip: 2nd argument (number of bytes) is not required if the string data is null-terminated.
将 NSData 转换为二进制数据
Memory.readByteArray(data.bytes(), data.length());
- 迭代一个 NSArray
var count = array.count();
for (var i = 0; i !== count; i++) {
var element = array.objectAtIndex_(i);
}
- 解档
var parsedValue = ObjC.classes.NSKeyedUnarchiver.unarchiveObjectWithData_(value);
- 读一个结构体
# If args[0] is a pointer to a struct, and let’s say you want to read the uint32 at offset 4, you can do it as shown below:
Memory.readU32(args[0].add(4));
// Get a reference to the openURL selector
var openURL = ObjC.classes.UIApplication["- openURL:"];
// Intercept the method
Interceptor.attach(openURL.implementation, {
onEnter: function(args) {
// As this is an ObjectiveC method, the arguments are as follows:
// 0. 'self'
// 1. The selector (openURL:)
// 2. The first argument to the openURL selector
var myNSURL = new ObjC.Object(args[2]);
// Convert it to a JS string
var myJSURL = myNSURL.absoluteString().toString();
// Log it
console.log("Launching URL: " + myJSURL);
}
});
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格式化字符串的语法
# objc的函数,第0个参数是id,第1个参数是SEL,真正的参数从args[2]开始
console.log("Type of arg[2] -> " + new ObjC.Object(args[2]).$className)
# 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());
var str ="hello";
var newstring = ObjC.classes.NSString.stringWithString_(str);
args[2] = newstring;
#用整数1337替换返回值
retval.replace(1337)
#用指针替换
retval.replace(ptr("0x1234"))
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);
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);
}
});
- frida rpc使用:传送