越狱iPhone安装Frida
本文的方法适用于已越狱的iPhone手机
打开Cydia,软件源,编辑(右上角),添加(左上角):https://build.frida.re
![图片名称](https://img-blog.csdnimg.cn/direct/e942bec8826749e58a65c6ca29cee705.png)
![图片名称](https://img-blog.csdnimg.cn/direct/19b3c57c76aa4d61ad7aced64856eac6.png)
![图片名称](https://img-blog.csdnimg.cn/direct/8d2d8fec204f420896a361a9a9abe1c8.png)
然后搜索Frida,点击安装
![图片名称](https://img-blog.csdnimg.cn/direct/e630930030de4d49bc0ff9cf235721ca.png)
参考:https://blog.csdn.net/boildoctor/article/details/122769942
安装指定版本Frida
iOS上的Frida版本需要和PC上的Frida版本保持一致,所以有时候需要安装指定版本Frida
下载指定版本deb包:
https://github.com/frida/frida/releases
例如:frida_15.2.2_iphoneos-arm.deb
通过XFTP将deb拷贝至手机/private/var/tmp
目录(也就是/tmp
目录)
ssh进入手机 /tmp
目录,执行安装:
dpkg -i xx.deb
参考:https://cloud.tencent.com/developer/article/2160543
使用Frida
在iPhone上启动Frida-server后,将iPhone通过USB连接至PC
PC上安装Frida,通过命令行输入命令 或 运行脚本
frida-ls-devices 查看电脑连接的iOS设备信息
frida-ps 查看正在运行的应用
USB连接
frida-ps -Ua
Wi-Fi连接
frida-ps -H 10.168.1.34:6666 -a
frida hook 类函数
- 函数名以”+”开头的,如:“+ URLWithString:”,可以直接通过类名调用方法,相当于java中的static函数
#coding=utf-8
import frida, sys
jscode = """
if(ObjC.available){
console.log('\\n[*] Starting Hooking');
var _className = "JDJR_HackersInfo";
var _methodName = "+ judgementJailbreak";
var hooking = ObjC.classes[_className][_methodName];
console.log('className is: ' + _className + ' and methodName is: ' + _methodName);
Interceptor.attach(hooking.implementation,{
onEnter: function(args) {
//args[0]:self
//args[1]:The selector
//args[2]:第一个参数
console.log(' hook success ')
this._className = ObjC.Object(args[0]).toString();
this._methodName = ObjC.selectorAsString(args[1]);
// console.log('Detected call to: ');
// console.log('[-] Detected call to: ' + this._className + ' --> ' + this._methodName);
// console.log('\\n----------------' + this._methodName + '----------------');
// console.log('arg2:\\n' + ObjC.Object(args[2]).toString());
//console.log('called from:\\n' + Thread.backtrace(this.context,Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\\n') + '\\n');
//print_arguments(args);
},
//onLeave.function(returnValue)被拦截函数调用之后回调 其中returnValue表示原始函数的返回值
onLeave:function(returnValue){
// console.log('Return value of: ');
// console.log(' ' + this._className + ' --> ' + this._methodName);
// var typeValue = Object.prototype.toString.call(returnValue);
// console.log("\\t Type of return value: " + typeValue);
// console.log("\\t Return Value: " + returnValue);
console.log("old Return Value: " + ObjC.Object(returnValue));
var newRet = ObjC.classes.NSString.stringWithString_("1");
returnValue.replace(newRet);
console.log("new Return Value: " + ObjC.Object(returnValue));
}
});
}
"""
bundle = 'xxx.xxx.xxx'
device = frida.get_usb_device() #连接usb设备 参数:超时时长
pid = device.spawn([bundle]) #启动指定bundleId的app
session = device.attach(pid) #附加到app
script = session.create_script(jscode) #创建frida javaScript脚本
script.load() #load脚本到app进程中 这样即注入成功
device.resume(pid) #恢复app运行
sys.stdin.read()#读取打印日志
frida hook 实例函数
- 函数名以“-”开头的需要找到一个实例化的对象,然后再调用方法
- 如果内存中没有这样的对象
这种情况需要手动生成一个实例,用法为ObjC.classes.类名.alloc() - 如果内存中存在实例化后的对象
这种情况需要先找出一个类的实例,使用var tmp=ObjC.chooseSync(ObjC.classes.类名),例如:
ObjC.chooseSync(ObjC.classes.PARSHealthPedometer10thHomeViewController)[0]
其中[0]表示取找到的实例中的第一个实例,可根据实际情况换成其他的实例。
- 如果内存中没有这样的对象
#coding=utf-8
import frida, sys
jscode = """
if(ObjC.available){
console.log('\\n[*] Starting Hooking');
// setToken: 有内容
var _className = "WLRequestInfo";
var _methodName = "- setToken:";
// var hookingclass = ObjC.chooseSync(ObjC.classes[_className])[0]; //如果内存中存在实例化后的对象,需要先找出一个类的实例
var hookingclass = ObjC.classes[_className].alloc(); //如果内存中没有实例化后的对象,手动实例化
var hooking = hookingclass[_methodName];
console.log('className is: ' + _className + ' and methodName is: ' + _methodName);
Interceptor.attach(hooking.implementation,{
onEnter: function(args) {
//args[0]:self
//args[1]:The selector
//args[2]:第一个参数
// console.log(' hook success ')
this._className = ObjC.Object(args[0]).toString();
this._methodName = ObjC.selectorAsString(args[1]);
// console.log('Detected call to: ');
// console.log('[-] Detected call to: ' + this._className + ' --> ' + this._methodName);
// console.log('\\n[-]' + this._className + ' --> ' + this._methodName + ' : ');
console.log('\\n----------------' + this._methodName + '----------------');
console.log('arg2:\\n' + ObjC.Object(args[2]));
console.log('called from:\\n' + Thread.backtrace(this.context,Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\\n') + '\\n');
//print_arguments(args);
},
//onLeave.function(returnValue)被拦截函数调用之后回调 其中returnValue表示原始函数的返回值
onLeave:function(returnValue){
// console.log('Return value of: ');
// console.log(' ' + this._className + ' --> ' + this._methodName);
// var typeValue = Object.prototype.toString.call(returnValue);
// console.log("Type of return value: " + typeValue);
// console.log("Return Value: " + returnValue);
console.log("Return Value: \\n" + ObjC.Object(returnValue));
}
});
}
"""
bundle = 'cn.gov.pbc.dcep'
device = frida.get_usb_device() #连接usb设备 参数:超时时长
pid = device.spawn([bundle]) #启动指定bundleId的app
session = device.attach(pid) #附加到app
script = session.create_script(jscode) #创建frida javaScript脚本
script.load() #load脚本到app进程中 这样即注入成功
device.resume(pid) #恢复app运行
sys.stdin.read()#读取打印日志
参考:https://mabin004.github.io/2018/08/24/%E5%9C%A8iOS%E4%B8%8A%E4%BD%BF%E7%94%A8Frida/
frida Wi-Fi连接
先使用ssh在iPhone上执行 /usr/sbin/frida-server -l 0.0.0.0:6666
,开启6666端口
iPhone和PC连接至同一局域网
假设iPhone的IP为10.168.1.34,开启端口为6666,则wifi连接代码如下:
# frida-ps -H 10.168.1.34:6666 -a
bundle = 'com.unionpay.chsp'
# wifi连接
# ssh执行 /usr/sbin/frida-server -l 0.0.0.0:6666
str_host = '10.168.1.34:6666'
manager = frida.get_device_manager()
device = manager.add_remote_device(str_host)
pid = device.spawn([bundle]) #启动指定bundleId的app
session = device.attach(pid) #附加到app
script = session.create_script(jscode) #创建frida javaScript脚本
script.load() #load脚本到app进程中 这样即注入成功
device.resume(pid) #恢复app运行
sys.stdin.read()#读取打印日志
frida USB连接
对比之下,USB连接部分代码如下:
# frida-ps -Ua
bundle = 'com.unionpay.chsp'
# usb连接
device = frida.get_usb_device() #连接usb设备 参数:超时时长
pid = device.spawn([bundle]) #启动指定bundleId的app
session = device.attach(pid) #附加到app
script = session.create_script(jscode) #创建frida javaScript脚本
script.load() #load脚本到app进程中 这样即注入成功
device.resume(pid) #恢复app运行
sys.stdin.read()#读取打印日志
完整代码如下:
#coding=utf-8
import frida, sys
jscode = """
if(ObjC.available){
console.log('\\n[*] Starting Hooking');
var _className = "CDVWKWebViewEngine";
var _methodName = "- userContentController:didReceiveScriptMessage:";
var hooking = ObjC.classes[_className][_methodName];
console.log('className is: ' + _className + ' and methodName is: ' + _methodName);
Interceptor.attach(hooking.implementation,{
onEnter: function(args) {
//args[0]:self
//args[1]:The selector
//args[2]:第一个参数
// console.log(' hook success ')
// this._className = ObjC.Object(args[0]).toString();
// this._methodName = ObjC.selectorAsString(args[1]);
// console.log('Detected call to: ');
// console.log('[-] Detected call to: ' + this._className + ' --> ' + this._methodName);
console.log('arg2: ' + ObjC.Object(args[2]).toString());
console.log('arg3: ' + ObjC.Object(args[3]).toString());
console.log('called from:\\n' + Thread.backtrace(this.context,Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\\n') + '\\n');
}
});
}
"""
# frida-ps -Ua
bundle = 'com.unionpay.chsp'
# usb连接
# device = frida.get_usb_device() #连接usb设备 参数:超时时长
# pid = device.spawn([bundle]) #启动指定bundleId的app
# session = device.attach(pid) #附加到app
# script = session.create_script(jscode) #创建frida javaScript脚本
# script.load() #load脚本到app进程中 这样即注入成功
# device.resume(pid) #恢复app运行
# sys.stdin.read()#读取打印日志
# wifi连接
# ssh执行 /usr/sbin/frida-server -l 0.0.0.0:6666
str_host = '10.168.1.34:6666'
manager = frida.get_device_manager()
device = manager.add_remote_device(str_host)
pid = device.spawn([bundle]) #启动指定bundleId的app
session = device.attach(pid) #附加到app
script = session.create_script(jscode) #创建frida javaScript脚本
script.load() #load脚本到app进程中 这样即注入成功
device.resume(pid) #恢复app运行
sys.stdin.read()#读取打印日志
打印backtrace
console.log('called from:\n' + Thread.backtrace(this.context,Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n');
打印模块基地址
比如我们在backtrace中看到
0x103a71690 comic-universal!0x2d69690
这里面!前面的就是模块名
我们可以这么获取模块在主存中的基地址
var moduleName = "comic-universal";
console.log("\n" + "base : " + Module.findBaseAddress(moduleName));
当然你也可以用0x103a71690-0x2d69690
获得
打印参数类型
例如我想打印retval的类型
console.log('retval: ' + ObjC.Object(retval).toString() );
console.log('retval type: ' + ObjC.Object(retval).$className + '\n');
输出:
retval: {method:account.getUserInfo; args:4; target:<SPWelfareGlobalJSHandler: 0x2813c23d0>}
retval type: JSHandlerV2
获取变量类型
使用.$class
例如
console.log('arg0->globalJSHandlerRegister: ' + ObjC.Object(args[0]).$class.globalJSHandlerRegister() );
frida-trace
模糊匹配
hook 所有类的- userContentController:didReceiveScriptMessage:函数,可以用*进行模糊匹配
// wifi连接
frida-trace -m "-[* userContentController:didReceiveScriptMessage:]" -H 10.168.1.34:6666 腾讯视频
// USB连接
frida-trace -m "-[* userContentController:didReceiveScriptMessage:]" -U 腾讯视频
其中"腾讯视频"是进程名,可以通过frida-ps -H 10.168.1.34:6666 -a
获得
参考:https://www.jianshu.com/p/7a3ba7ae3c29
hook多个函数
例如 同时hook
-[* userContentController:didReceiveScriptMessage:]和
-[* userContentController:didReceiveScriptMessage:replyHandler:]
frida-trace -m "-[* userContentController:didReceiveScriptMessage:]" -m "-[* userContentController:didReceiveScriptMessage:replyHandler:]" -H 192.168.31.32:6666 UC浏览器
自定义处理逻辑
在执行frida-trace的文件夹下,会产生__handlers__
文件夹
直接修改文件夹中的JS代码,即可生效
Spawn模式 和 Attach模式
Spawn模式直接启动app
关键代码:
pid = device.spawn([‘com.tencent.live4iphone’])
# frida-ps -H 10.168.1.34:6666 -a
bundle = 'com.tencent.live4iphone'
# ssh执行 /usr/sbin/frida-server -l 0.0.0.0:6666
str_host = '10.168.1.34:6666'
manager = frida.get_device_manager()
device = manager.add_remote_device(str_host)
pid = device.spawn([bundle])
session = device.attach(pid) #附加到app
script = session.create_script(jscode) #创建frida javaScript脚本
script.load() #load脚本到app进程中 这样即注入成功
device.resume(pid) #恢复app运行
sys.stdin.read()#读取打印日志
Attach模式在已启动app情况下注入
关键代码:
pid = device.get_process(“腾讯视频”).pid
# frida-ps -H 10.168.1.34:6666 -a
processName = "腾讯视频"
# wifi连接
# ssh执行 /usr/sbin/frida-server -l 0.0.0.0:6666
str_host = '10.168.1.34:6666'
manager = frida.get_device_manager()
device = manager.add_remote_device(str_host)
pid = device.get_process(processName).pid
session = device.attach(pid) #附加到app
script = session.create_script(jscode) #创建frida javaScript脚本
script.load() #load脚本到app进程中 这样即注入成功
device.resume(pid) #恢复app运行
sys.stdin.read()#读取打印日志
具体类的处理
打印WKScriptMessage的内容
// console.log('arg3: ' + ObjC.Object(args[3]).toString());
// <WKScriptMessage: 0x28eda47e0>
var scriptMessage = ObjC.Object(args[3]);
var scriptMessageName = scriptMessage.name();
var nameString = scriptMessageName.toString();
console.log("scriptMessageName: " + scriptMessageName);
var scriptMessageBody = scriptMessage.body();
var bodyString = scriptMessageBody.toString();
if (bodyString.includes("handlername = getTreasureBoxReportTime;")
|| bodyString.includes("handlername = sendRadarLog;")
|| bodyString.includes("handlername = setClientLog;")) {
return;
}
console.log("scriptMessageBody: " + scriptMessageBody);
例如:
var _className = "KMYWKWebViewBridge";
var _methodName = "- userContentController:didReceiveScriptMessage:";
var hooking = ObjC.classes[_className][_methodName];
console.log('className is: ' + _className + ' and methodName is: ' + _methodName);
Interceptor.attach(hooking.implementation, {
onEnter: function (args) {
//args[0]:self
//args[1]:The selector
//args[2]:第一个参数
// console.log(' hook success ')
// this._className = ObjC.Object(args[0]).toString();
// this._methodName = ObjC.selectorAsString(args[1]);
// console.log('Detected call to: ');
// console.log('[-] Detected call to: ' + this._className + ' --> ' + this._methodName);
// console.log('arg2: ' + ObjC.Object(args[2]).toString()); // <WKUserContentController: 0x285aa7ba0>
// console.log('arg3: ' + ObjC.Object(args[3]).toString()); // <WKScriptMessage: 0x28eda47e0>
var scriptMessage = ObjC.Object(args[3]);
var scriptMessageName = scriptMessage.name();
var nameString = scriptMessageName.toString();
console.log("scriptMessageName: " + scriptMessageName);
var scriptMessageBody = scriptMessage.body();
var bodyString = scriptMessageBody.toString();
if (bodyString.includes("handlername = getTreasureBoxReportTime;")
|| bodyString.includes("handlername = sendRadarLog;")
|| bodyString.includes("handlername = setClientLog;")) {
return;
}
console.log("scriptMessageBody: " + scriptMessageBody);
// console.log('called from:\\n' + Thread.backtrace(this.context,Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\\n') + '\\n');
}
});
输出类似
- userContentController:didReceiveScriptMessage: hooked
scriptMessageName: WindVaneCallNative
scriptMessageBody: {
name = "WVPerformance.onStage";
params = "{\"stage\":{\"H5_JST_displayedTime\":1720385567684}}";
reqId = "$5507588";
}
- userContentController:didReceiveScriptMessage: hooked
scriptMessageName: WindVaneCallNative
scriptMessageBody: {
name = "WVPerformance.onProperty";
params = "{\"property\":{\"H5_JST_UNIQID\":\"j1i27fgt5lbtgd9249bu4eo6ukf1o0bg\"}}";
reqId = "$5507589";
}
- userContentController:didReceiveScriptMessage: hooked
scriptMessageName: WindVaneFirstScreen
scriptMessageBody: {
backtrack = 0;
cost = 1720385568182;
timestamp = 2782;
uniqId = j1i27fgt5lbtgd9249bu4eo6ukf1o0bg;
}
比如:
- userContentController:didReceiveScriptMessage: hooked
scriptMessageName: WindVaneCallNative
scriptMessageBody: {
name = "WVPerformance.onStage";
params = "{\"stage\":{\"H5_JST_displayedTime\":1720385567684}}";
reqId = "$5507588";
}
在JS中此时发生的调用就是
window.webkit.messageHandlers.WindVaneCallNative.postMessage({
name = "WVPerformance.onStage";
params = "{\"stage\":{\"H5_JST_displayedTime\":1720385567684}}";
reqId = "$5507588";
})
替换字符串参数的值
onEnter(log, args, state) {
// log(`-[NSMutableURLRequest setValue:${args[2]} forHTTPHeaderField:${args[3]}]`);
// console.log('arg2: ' + ObjC.Object(args[2]).toString());
// console.log('arg3: ' + ObjC.Object(args[3]).toString());
// console.log("\n");
arg3 = ObjC.Object(args[3]);
if (arg3.isEqualToString_('Cookie')) {
args[2] = ObjC.classes.NSString.stringWithString_("xxx");
console.log('[*] New cookie value:'+ ObjC.Object(args[2]).toString() );
}
}
打印args[1]也就是Selector
ObjC.selectorAsString(args[1])
例如:
onEnter: function (args) {
console.log('Selector: ' + ObjC.selectorAsString(args[1]));
}
修改Frida-trace源码
Frida-trace源代码位置(以我的环境为例):
D:\Academic\Python\Python39\Lib\site-packages\frida_tools\tracer.py
修改针对iOS的OC hook时:
对onEnter的修改在这里:
{
/**
* Called synchronously when about to call %(display_name)s.
*
* @this {object} - Object allowing you to store state for use in onLeave.
* @param {function} log - Call this function with a string to be presented to the user.
* @param {array} args - Function arguments represented as an array of NativePointer objects.
* For example use args[0].readUtf8String() if the first argument is a pointer to a C string encoded as UTF-8.
* It is also possible to modify arguments by assigning a NativePointer object to an element of this array.
* @param {object} state - Object allowing you to keep state across function calls.
* Only one JavaScript function will execute at a time, so do not worry about race-conditions.
* However, do not use this to store function arguments across onEnter/onLeave, but instead
* use "this" which is an object for keeping state local to an invocation.
*/
onEnter(log, args, state) {
log(%(log_str)s);
},
其中的log_str,在OC hook中,在这里修改:
def _create_stub_native_handler(self, target, decorate):
if target.flavor == 'objc':
state = {"index": 2}
def objc_arg(m):
index = state["index"]
r = ":${args[%d]} " % index
state["index"] = index + 1
return r
log_str = "`" + re.sub(r':', objc_arg, target.display_name) + "`"
if log_str.endswith("} ]`"):
log_str = log_str[:-3] + "]`"