【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: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');
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值