Frida - 在IOS平台的逆向笔记

入门

if (ObjC.available) {
    console.log("ObjectC yes!")
}

进去先判断一下是不是ObjC环境,别干错了。

逆向分析

IOS frida逆向有两种方式去获取一个函数什么的东西,一个是拖进IDA(转圈圈两年半),一个是dump出一些函数。

FCiOS.findAllByPattern("-[*MSF* *]").forEach(function (item: any) {
    console.log(item["name"])
})

这段代码就匹配了和MSF相关的所有方法和类,然后把他们的完整名字打印出来。

注入后方法入参的读取

let MSFRequestModel = ObjC.classes.MSFRequestModel
const initWithCmdV1 = MSFRequestModel["- initWithCmd:uin:timeout:data:traceInfo:options:"]
const initWithCmdV2 = MSFRequestModel["- initWithCmd:uin:timeout:buffer:traceInfo:options:"]
Interceptor.attach(initWithCmdV1.implementation, {
    onEnter: function (args) {
        const cmd = new ObjC.Object(args[2]);
        const data = new ObjC.Object(args[5]);
        //console.log('--------------------------')
        //console.log("CMD => " + cmd)
        //console.log("BUF => " + data)
        //console.log('--------------------------\n')
    }
});

objc的方法会带有参数的名称(按顺序)

前两个参数也就是args[0]args[1],分别是ID和SEL类型。

图中把cmd转换为了ObjC对象,在objc中不是所有的方法传递的参数都是对象,如果是数字,直接打印即可。

Tips: 数字看起来像指针的可以尝试new ObjC.Object !

读取一个对象的字段

const sendPacketImplV2 = ObjC.classes.MsfAdapter["- sendPacketImplV2:"];
Interceptor.attach(sendPacketImplV2.implementation, {
    onEnter: function (args) {
        const customObj = new ObjC.Object(args[2]); // 获取ObjC对象
        const ivarList = customObj.$ivars; // 获取变量列表
        console.log('--------------------------')
        if (customObj._cmd == "trpc.o3.report.Report.SsoReport") return // 读取了里面有cmd字段

        for (var key in ivarList) {
            if (key == '_secSign' || key == '_secDeviceToken' || key == '_secExtra' || key == '_data'
            ) {
                console.log(`send param: ${key}=${printHex(ivarList[key])}`);
            } else if ( key == "_cmd" ){
                console.log(`sendPacketV2: ${key}=${ivarList[key]}`);
            }
        }
        console.log('--------------------------\n')
    }
});
let getSecurityInfo = ObjC.classes.FGESDK["+ getSecurityInfoWithModuleId:data:uin:seq:"]
Interceptor.attach(getSecurityInfo.implementation, {
    onEnter: function (args) {
        console.log(`getSecurityInfo: ---------------------`);
        let cmd = new ObjC.Object(args[2])
        let data = new ObjC.Object(args[3])
        let uin = new ObjC.Object(args[4])
        let seq = args[5].toInt32() // 纯数字场景

        console.log(args[0])
        console.log(args[1])
        console.log(args[2])
        console.log(data.handle)

        console.log(uin)
        console.log(seq)
        console.log('--------------------------\n')
    }
});

使用指针Hook

大差不差,网上教程很多。

Interceptor.attach(baseAddr.add("0x9465f98"), {
    onEnter: function (args) {
        console.log("\n----------------------- 0x9465f98")
        console.log("UIN = " + new ObjC.Object(args[0])) // uin
        console.log("MODULE_ID = " + new ObjC.Object(args[1])) // module_id
        let salt = args[2].readByteArray(args[3].toInt32())
        console.log("SALT = " + FCCommon.arrayBuffer2Hex(salt))
        console.log("----------------------\n")
    }
})

小工具

生成NSData数据

function rawNSData(buffer: ArrayBuffer) {
    const buf = Memory.alloc(buffer.byteLength);
    for (let i = 0; i < buffer.byteLength; i++) {
        // @ts-ignore
        Memory.writeU8(buf.add(i), i);
    }
    return ObjC.classes.NSData.dataWithBytes_length_(buf, buffer.byteLength);
}

let buffer = new ArrayBuffer(2048); // 创建一个长度为 2048 的 buffer
let view = new Uint32Array(buffer); // 将 buffer 视为一个 32 位整数的序列
view[0] = 123456;
req["- setData:"].call(req, rawNSData(buffer)) // 生成后调用(动态方法)

构建NSString

let salt = hex2ArrayBuffer(params.salt.toLowerCase()) as ArrayBuffer
let data = params.data
let uin = params.uin

let tlv544 = FGESDK["+ signWithUserId:moduleId:data:"](
    ObjC.classes.NSString.stringWithString_(uin),
    ObjC.classes.NSString.stringWithString_(data),
    rawNSData(salt)
)