越狱iPhone安装Frida
本文的方法适用于已越狱的iPhone手机
打开Cydia,软件源,编辑(右上角),添加(左上角):https://build.frida.re



然后搜索Frida,点击安装

参考: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:SSH中执行
/usr/sbin/frida-server -l 0.0.0.0:6666
启动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 hook 无符号函数(sub_xxx函数)
需要按照地址进行hook
参考:https://bbs.kanxue.com/thread-259875.htm
其中get_func_addr的第一个参数module是macho的名字,第二个参数是要hook的函数的偏移量,可以用:IDA看到的偏移量 减去 IDA设置的基地址(Ctrl+S可以看) 得到
#coding=utf-8
import frida, sys
jscode = """
if(ObjC.available){
function get_func_addr(module, offset) {
var base_addr = Module.findBaseAddress(module);
console.log("base_addr: " + base_addr);
var func_addr = base_addr.add(offset);
if (Process.arch == 'arm')
return func_addr.add(1); //如果是32位地址+1
else
return func_addr;
}
var relative_offset = 0x2AB384;
var func_addr = get_func_addr('SFMainland_Store_Pro', relative_offset);
console.log('func_addr: ' + func_addr);
console.log('relative_offset: 0x' + relative_offset.toString(16));
Interceptor.attach(ptr(func_addr) ,{
onEnter: function(args) {
console.log(' onEnter ');
console.log('arg0: ' + args[0]);
console.log(Memory.readByteArray(ptr(args[0]), 96));
console.log('\\n');
console.log('arg1: ' + args[1]);
// console.log(Memory.readByteArray(ptr(args[1]), 96));
// console.log('\\n');
console.log('arg2: ' + args[2]);
console.log(Memory.readByteArray(ptr(args[2]), 96));
console.log('\\n');
console.log('*(arg2+0x08): ' + ptr(args[2]).add(0x08).readPointer() );
console.log(Memory.readByteArray(ptr(args[2]).add(0x08).readPointer(), 96));
console.log('\\n');
console.log('arg3: ' + args[3]);
console.log('arg4: ' + args[4]);
console.log('called from:\\n' + Thread.backtrace(this.context,Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\\n') + '\\n');
// console.log('arg0: ' + ObjC.Object(args[0]));
// console.log('arg1: ' + ObjC.Object(args[1]));
// console.log('arg1 methods: ' + ObjC.Object(args[1]).methods());
// console.log('arg2: ' + ObjC.Object(args[2]));
// console.log('arg2: ' + ObjC.Object(args[2]).toString());
// console.log('arg3: ' + ObjC.Object(args[3]));
// console.log('arg3: ' + ObjC.Object(args[4]));
}
});
}
"""
# frida-ps -H 192.168.31.8:6666 -a
bundle = 'com.sf-express.waybillcn'
processName = "顺丰速运"
# wifi连接
# ssh执行 /usr/sbin/frida-server -l 0.0.0.0:6666
str_host = '192.168.31.32:6666'
manager = frida.get_device_manager()
device = manager.add_remote_device(str_host)
# pid = device.spawn([bundle]) #启动指定bundleId的app
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()#读取打印日志
然后先启动App,再python运行此脚本即可!
frida hook 非消息函数
有些函数并不是消息传递格式的(不带[])
比如SEL NSSelectorFromString(NSString *aSelectorName);
可以这么hook
frida-trace -i "NSSelectorFromString*" -H 192.168.31.8:6666 和平营地
frida-trace -U -i "NSSelectorFromString*" 和平营地
frida hook C++函数
iOS app开发中除使用OC之外,还可以使用C++
比如一些STL函数:
ctu::hex(std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > const&)()
它mangle完长这样:
_ZN3ctu3hexERKNSt3__16vectorIhNS0_9allocatorIhEEEE()
我们知道它mangle完一定还带有hex或者vector这样的字段,所以可以这么hook(越精确越好,但也可能造成遗漏)
frida-trace -i "*vector*" -H 192.168.137.187:6666 顺丰速运
-i
是include的意思:https://frida.re/docs/frida-trace/
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() );
打印地址处的值
注意一些C++函数,不能使用ObjC.Object(args[0])
这种模式来获取参数值,因为他们的参数不兼容OC对象,是纯纯的C++对象。
这时候可以这么处理:
console.log("arg2:", Memory.readUtf8String(args[2]));
console.log('arg1: ' + args[1]);
console.log(Memory.readByteArray(ptr(args[1]), 48));
console.log('\\n');
console.log('arg2: ' + args[2]);
console.log(Memory.readByteArray(ptr(args[2]), 48));
console.log('\\n');
console.log(Memory.readByteArray(ptr("0x12345678"), 48));
打印函数代码
var funcaddr = ptr(hooking.implementation);
var targetAddress = funcaddr; // 替换为你要读取的地址
var data = Memory.readByteArray(targetAddress, 1610); // 读取 1610 字节的内容
console.log(data);
例如:
jscode = """
if(ObjC.available){
console.log('\\n[*] Starting Hooking');
var _className = "DeviceJSBridgePlugin";
var _methodName = "- handle_device_getNetworkType:callback:";
var hooking = ObjC.classes[_className][_methodName];
console.log('className is: ' + _className + ' and methodName is: ' + _methodName);
var funcaddr = ptr(hooking.implementation);
console.log('address is: ' + funcaddr);
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: ' + this._className + ' --> ' + this._methodName);
var targetAddress = funcaddr; // 替换为你要读取的地址
var data = Memory.readByteArray(targetAddress, 16*10); // 读取 16*10 字节的内容
console.log(data);
}
});
}
"""
根据参数取出值,以该值为地址取出值
类似下面的操作:
console.log('arg2: ' + args[2]);
console.log(Memory.readByteArray(ptr(args[2]), 96));
console.log('\\n');
console.log('*(arg2+0x08): ' + ptr(args[2]).add(0x08).readPointer() );
console.log(Memory.readByteArray(ptr(args[2]).add(0x08).readPointer(), 96));
console.log('\\n');
各种根据地址的复杂取值
Interceptor.attach(ptr(func_addr) ,{
onEnter: function(args) {
console.log(' onEnter ');
// console.log('arg0: ' + args[0]);
console.log('arg0: ' + ObjC.Object(args[0]));
// console.log(Memory.readByteArray(ptr(args[0]), 96));
// console.log('\\n');
// console.log('*(arg0+0x00): ' + ptr(args[0]).add(0x00).readPointer() );
// console.log(Memory.readByteArray(ptr(args[0]).add(0x00).readPointer(), 96));
// console.log('\\n');
// console.log('*(arg0+0x10): ' + ptr(args[0]).add(0x10).readPointer() );
// console.log(Memory.readByteArray(ptr(args[0]).add(0x10).readPointer(), 96));
// console.log('\\n');
console.log('*(arg0+0x20): ' + ptr(args[0]).add(0x20).readPointer() );
console.log(Memory.readByteArray(ptr(args[0]).add(0x20).readPointer(), 96));
console.log('*(arg0+0x20) [OC]: ' + ObjC.Object(ptr(args[0]).add(0x20).readPointer()));
console.log('\\n');
console.log('*(arg0+0x28): ' + ptr(args[0]).add(0x28).readPointer() );
console.log(Memory.readByteArray(ptr(args[0]).add(0x28).readPointer(), 96));
console.log('*(arg0+0x28) [OC]: ' + ObjC.Object(ptr(args[0]).add(0x28).readPointer()));
console.log('\\n');
console.log('*(arg0+0x30): ' + ptr(args[0]).add(0x30).readPointer() );
console.log(Memory.readByteArray(ptr(args[0]).add(0x30).readPointer(), 96));
console.log('*(arg0+0x30) [OC]: ' + ObjC.Object(ptr(args[0]).add(0x30).readPointer()));
console.log('\\n');
// console.log('arg1: ' + args[1]);
// console.log(Memory.readByteArray(ptr(args[1]), 96));
// console.log('\\n');
// console.log('arg2: ' + args[2]);
// console.log(Memory.readByteArray(ptr(args[2]), 96));
// console.log('\\n');
// console.log('*(arg2+0x08): ' + ptr(args[2]).add(0x08).readPointer() );
// console.log(Memory.readByteArray(ptr(args[2]).add(0x08).readPointer(), 96));
// console.log('\\n');
// console.log('*(*(arg2+0x08) + 0x18): ' + ptr(args[2]).add(0x08).readPointer().add(0x18).readPointer() );
// console.log(Memory.readByteArray(ptr(args[2]).add(0x08).readPointer().add(0x18).readPointer(), 96));
// console.log('\\n');
// console.log('arg3: ' + args[3]);
console.log('called from:\\n' + Thread.backtrace(this.context,Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\\n') + '\\n');
// if(ObjC.Object(args[0]).toString().includes("currentLocation"))
// {
// console.log('called from:\\n' + Thread.backtrace(this.context,Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\\n') + '\\n');
// }
// console.log('arg0: ' + ObjC.Object(args[0]));
// console.log('arg1: ' + ObjC.Object(args[1]));
// console.log('arg1 methods: ' + ObjC.Object(args[1]).methods());
// console.log('arg2: ' + ObjC.Object(args[2]));
// console.log('arg2: ' + ObjC.Object(args[2]).toString());
// console.log('arg3: ' + ObjC.Object(args[3]));
// console.log('arg3: ' + ObjC.Object(args[4]));
}
});
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()#读取打印日志
打印所有修饰名
也就是打印所有C++的Mangling Name
// 先打开app,后启动脚本
frida -H 192.168.31.32:6666 -F -l 222-symbol-frida-script.js
222-symbol-frida-script.js:
function intercept() {
console.log('[*] Starting Hooking');
// Process.enumerateModules().forEach(m => {
// console.log(`Module: ${m.name} (Base: ${m.base}, Size: ${m.size})`);
// });
Process.enumerateModules().forEach(module => {
const filteredExports = module.enumerateExports().filter(curexport =>
curexport.name.includes("xxxxx")
// && curexport.name.includes("yyyyyy")
);
if (filteredExports.length > 0) {
console.log(`\n[+] Found in module: ${module.name} (Base: ${module.base})`);
filteredExports.forEach((exp, index) => {
console.log(` ${index + 1}. ${exp.name} (Address: ${exp.address})`);
});
}
});
}
intercept();
具体类的处理
打印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] + "]`"
注意使用\n:
要这样写,使用\\n
if (arg1.includes("evaluateJavaScript") && arg1.includes("completionHandler")){
log('arg2: ' + ObjC.Object(args[2]).toString());
log('arg3: ' + ObjC.Object(args[3]).toString() + '\\n');
};
不要写成\n
,因为自动生成代码时会被转义
if (arg1.includes("evaluateJavaScript") && arg1.includes("completionHandler")){
log('arg2: ' + ObjC.Object(args[2]).toString());
log('arg3: ' + ObjC.Object(args[3]).toString() + '\n');
};
常见报错
报错 Failed to attach: module not found at “/usr/lib/libSystem.B.dylib”
使用frida-trace时出现这个报错
一种原因是因为被frida hook的app,也被shadow越狱屏蔽插件所处理了
frida和shadow屏蔽插件出现了冲突
解决办法:尝试关闭shadow中的一些选项,或者定位app的越狱检测点然后自己实现越狱绕过
比如针对我分析的app,关闭Dynamic Libraries选项之后,frida就能hook了
经验
关于NSString的OC和C形式
当我们逆向一个iOS App中的C函数时,如果使用Memory.readByteArray
打印某个变量(某个地址处的值),发现结果以41 e9 80 fc a1 21 00 00 8c 07 00 00 05 00 00 00 xx
开头,后面是一个可打印字符,比如:
这说明这个位置很可能是一个OC的NSString类型对象
使用ObjC.Object
转换后打印
比如之前是:
console.log('*(arg0+0x20): ' + ptr(args[0]).add(0x20).readPointer() );
console.log(Memory.readByteArray(ptr(args[0]).add(0x20).readPointer(), 96));
console.log('\\n');
现在是:
console.log('*(arg0+0x20): ' + ptr(args[0]).add(0x20).readPointer() );
console.log('*(arg0+0x20) [OC]: ' + ObjC.Object(ptr(args[0]).add(0x20).readPointer()));
console.log('\\n');