【iOS安全】越狱iOS安装Frida | Frida使用 | 修改Frida-trace源码

越狱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后,将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] + "]`"
  • 13
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值