iOS Hook框架:Cydia Substrate与Frida

说实话,iOS逆向工程里最核心的能力,就是Hook。没有Hook,你只能看看二进制文件发呆,有了Hook,你就能在运行时修改任何东西。今天我们来聊聊两个主流框架:Cydia Substrate和Frida。

我个人习惯把Substrate叫做「老派硬核」,把Frida叫做「现代瑞士军刀」。两者各有千秋,但都值得你掌握。

Cydia Substrate原理与Tweak开发

Cydia Substrate,以前叫MobileSubstrate,是越狱iOS上最经典的Hook框架。它的核心原理其实不复杂:

  • MSHookMessageEx:Hook Objective-C方法,替换IMP指针
  • MSHookFunction:Hook C/C++函数,通过跳板修改指令
  • MSFindSymbol:在内存中查找符号地址

说白了,Substrate就是在运行时把目标方法的实现指针,偷偷换成你的函数地址。等原方法被调用时,实际上跑的是你的代码。

核心流程:

  1. Substrate通过dyld加载到进程空间
  2. 扫描所有Objective-C类和方法列表
  3. 替换目标方法的IMP为你写的C函数
  4. 你的函数里可以调用%orig执行原逻辑

我在项目中遇到过一个问题:Substrate的Hook顺序很重要。如果你和另一个Tweak同时Hook同一个方法,后加载的会覆盖先加载的。嗯,这里要注意,依赖注入的顺序由Cydia的依赖系统控制。

Logos语法:%hook、%log、%orig

Logos是Substrate的预处理语法,它让你用更简洁的方式写Tweak。我刚开始用的时候觉得这玩意儿有点玄学,后来发现它其实就是宏展开。

%hook ClassName
- (void)methodName:(id)arg {
    %log; // 打印调用日志
    NSLog(@"Hook到了!参数: %@", arg);
    %orig; // 调用原始实现
}
%end

几个关键指令:

指令 作用
%hook 开始Hook一个类,后面跟类名
%log 自动打印方法名、参数、返回值到syslog
%orig 调用原始方法实现,可以传参
%new 给类添加新方法
%c 获取Class对象,等价于objc_getClass
%end 结束Hook块

避坑指南:我曾经在%orig里传错了参数类型,导致App直接闪退。记住,%orig的参数必须和原方法签名完全一致,少一个都不行。

Frida on iOS:Frida-Gadget注入与ObjC Bridge

Frida是后来崛起的跨平台Hook框架。它不需要越狱,通过Frida-Gadget动态库注入就能工作。你想想看,这对非越狱设备来说简直是福音。

Frida在iOS上的工作方式有两种:

  • Frida-Server:越狱设备上运行守护进程,通过USB/TCP通信
  • Frida-Gadget:把动态库注入到App中,无需越狱

我个人更常用Gadget方式,因为不需要越狱设备。操作步骤很简单:

  1. 下载Frida-Gadget.dylib
  2. 用optool或insert_dylib注入到App二进制
  3. 重签名并安装到设备
  4. 用frida-trace或Python脚本连接

Frida的ObjC Bridge非常强大,可以直接在JavaScript里操作Objective-C对象:

// Frida脚本示例:Hook Objective-C方法
if (ObjC.available) {
    var className = "SKPaymentQueue";
    var methodName = "- addPayment:";
    
    var hook = ObjC.classes[className][methodName];
    Interceptor.attach(hook.implementation, {
        onEnter: function(args) {
            console.log("[*] 拦截到 addPayment:");
            console.log("[*] 参数: " + ObjC.Object(args[2]));
            // 这里可以修改参数或阻止调用
        },
        onLeave: function(retval) {
            console.log("[*] 原始方法返回");
        }
    });
}

为什么会这样?因为Frida在底层做了ObjC Runtime的桥接,让你用JS就能调用objc_msgSend。说实话,我第一次看到这个的时候觉得太神奇了。

实战:编写Tweak绕过应用内购买

好了,理论讲完了,我们来点实际的。绕过IAP(应用内购买)是逆向工程的经典案例。你想想看,很多App的付费功能其实只是客户端校验,服务端根本不验证。

核心思路:Hook SKPaymentQueueaddPayment: 方法,让它什么都不做,然后手动调用 finishTransaction: 并伪造购买成功回调。

用Logos写Tweak:

%hook SKPaymentQueue
- (void)addPayment:(SKPayment *)payment {
    %log;
    NSLog(@"[IAP Bypass] 拦截到购买请求: %@", payment.productIdentifier);
    // 不调用%orig,直接返回
}

- (void)finishTransaction:(SKPaymentTransaction *)transaction {
    %log;
    NSLog(@"[IAP Bypass] 完成交易: %@", transaction.payment.productIdentifier);
    %orig;
}
%end

%hook SKPaymentTransactionObserver
- (void)paymentQueue:(SKPaymentQueue *)queue 
    updatedTransactions:(NSArray *)transactions {
    %log;
    // 伪造交易成功状态
    for (SKPaymentTransaction *t in transactions) {
        if (t.transactionState == SKPaymentTransactionStatePurchasing) {
            // 这里可以伪造一个成功的交易对象
            // 实际项目中需要更复杂的处理
        }
    }
    %orig;
}
%end

重要提醒:绕过IAP仅供学习和安全研究使用。实际App的支付验证通常有服务端签名校验,单纯客户端Hook不一定有效。我曾经见过一个App,客户端Hook后显示购买成功,但服务端返回403,因为票据验证失败了。

用Frida实现同样的功能:

// Frida IAP Bypass脚本
if (ObjC.available) {
    var SKPaymentQueue = ObjC.classes.SKPaymentQueue;
    
    // Hook addPayment:
    var addPayment = SKPaymentQueue['- addPayment:'];
    Interceptor.attach(addPayment.implementation, {
        onEnter: function(args) {
            console.log("[IAP] 拦截购买: " + 
                ObjC.Object(args[2]).productIdentifier());
            // 阻止实际购买
        }
    });
    
    // Hook updatedTransactions
    var observerProto = ObjC.protocols.SKPaymentTransactionObserver;
    // 实际需要找到实现了该协议的类
    console.log("[IAP] Frida脚本已加载");
}

嗯,这里要注意,实际项目中你还需要处理restoreCompletedTransactionstransactionReceipt的伪造。不过核心思路就是:让App以为购买成功了,但实际上你一分钱都没花。

我的经验:真正难搞的不是客户端Hook,而是服务端验证。很多App会把receipt data发到Apple服务器验证,这时候你需要在客户端Hook NSDatabase64EncodedStringWithOptions:,把真实的receipt替换成你从越狱设备上抓取的有效receipt。这招我用了好多次,成功率很高。

知识体系总览

下面这张图总结了本章的核心知识点和它们之间的关系:

iOS Hook框架知识体系 Cydia Substrate(越狱) Frida(越狱/非越狱) MSHookMessageEx MSHookFunction Logos语法(%hook等) Frida-Server Frida-Gadget ObjC Bridge(JS调OC) 实战:绕过应用内购买(IAP) 核心思路:Hook SKPaymentQueue → 阻止真实购买 → 伪造交易成功回调

这张图把两大框架的组件和关系梳理清楚了。Substrate走的是越狱路线,依赖Logos语法;Frida更灵活,支持非越狱设备,通过ObjC Bridge用JS操作原生代码。最终都汇聚到同一个实战目标:绕过IAP。

好了,这一章的内容就到这里。记住,Hook框架只是工具,真正重要的是你分析App逻辑的能力。多动手,多踩坑,你也能成为逆向高手。

公众号:蓝海资料掘金营,微信deep3321