|
黑客软件破解深度论文系列之七:iOS应用逆向与破解——越狱环境下的砸壳、静态分析与动态调试 摘要:iOS应用尽管运行在封闭的生态系统中,但并非不可攻破。本文以超过一万五千字的篇幅,系统讲解iOS应用的完整逆向破解流程:从越狱环境的搭建、砸壳(解密App Store加密的二进制)、静态分析(IDA Pro、Hopper)、动态调试(lldb + debugserver),到Objective-C方法Hook(Frida、Logify)、内购破解和反越狱检测绕过。文章包含五个完整的实战案例,覆盖砸壳提取、VIP功能解锁、订阅验证绕过、网络验证篡改和反调试对抗。高频使用“黑客”、“破解软件”、“iOS逆向”、“越狱”、“砸壳”、“Frida”、“Objective-C Hook”等关键词。 第一章 iOS应用的安全架构与众不同之处1.1 iOS vs Android:安全模型的根本差异在深入iOS逆向技术之前,黑客必须理解iOS与Android在安全架构上的本质差异。这些差异决定了iOS逆向的独特挑战和应对策略。
[td]维度 | Android | iOS | | 应用分发 | 官方商店为主,侧载(sideload)允许(需开启未知来源) | 仅官方App Store(非越狱设备) | | 代码签名 | 开发者证书签名,用户可安装任意签名应用 | Apple证书签名,非越狱设备只运行Apple签名的应用 | | 应用加密 | 无(APK内classes.dex明文) | FairPlay加密(App Store下载后由Apple密钥加密) | | 运行时保护 | SELinux,但用户可root | 内核级保护(KPP/KTRR),越狱难度高 | | 调试工具 | Android Studio直接调试 | 需要debugserver(通常需要越狱) | | 模拟器 | 官方模拟器功能完整 | 模拟器无法运行ARM64 App Store应用 |
核心结论:iOS逆向的门槛远高于Android。最显著的障碍是FairPlay加密——从App Store下载的iOS应用在磁盘上是加密的,无法直接静态分析。黑客必须先“砸壳”(decrypt)获得解密后的可执行文件(Mach-O),才能进行后续分析。 1.2 iOS应用的文件结构一个iOS应用程序的.ipa文件(实际上是ZIP压缩包)解压后的典型结构: [size=12.573px]text
MyApp.ipa/├── Payload/│ └── MyApp.app/│ ├── MyApp # 主可执行文件(Mach-O格式,FairPlay加密)│ ├── Info.plist # 应用信息文件│ ├── _CodeSignature/ # 代码签名文件夹│ │ └── CodeResources # 签名文件│ ├── Frameworks/ # 嵌入的动态框架│ ├── PlugIns/ # 扩展(Today Widget、Share Extension等)│ └── Resources/ # 图片、NIB/XIB、本地化文件
对于逆向最重要的文件是MyApp这个Mach-O可执行文件。它包含了应用的Objective-C/Swift编译后的机器码。 1.3 破解的主要目标iOS软件破解的常见目标与Android类似,但由于App Store付费应用的生态,内购破解(In-App Purchase bypass)和免费试用延长是黑客最关注的领域。
[td]目标类型 | 说明 | 典型手段 | | 内购破解(IAP) | 免费获得付费订阅、一次性购买内容 | Hook paymentQueue、伪造购买回执 | | VIP解锁 | 使用高级功能、去广告 | 修改isPremium返回值 | | 免费试用延长 | 无限使用试用期 | 修改购买日期、Hook到期检查 | | 去除越狱检测 | 让应用在越狱设备上正常运行 | Hook越狱检测函数 | | 功能限制移除 | 解锁被禁用的功能 | 修改条件分支、替换资源 |
第二章 越狱环境的搭建——iOS逆向的基础2.1 什么是越狱越狱(Jailbreak)是移除iOS系统对用户的限制(主要是代码签名强制检查和文件系统沙盒)的过程。越狱后,黑客可以: 越狱是iOS逆向的先决条件(除极少数使用checkm8漏洞的硬件级调试外)。没有越狱,无法从加密的App Store应用中提取可分析的文件,也无法对应用进行动态调试。 2.2 越狱工具的选择(按iOS版本)
[td]iOS版本 | 推荐越狱工具 | 类型 | 特点 | | 12.0 - 14.8 | unc0ver | 半越狱(半不完美) | 稳定,支持A8-A13芯片 | | 12.0 - 14.3 | Taurine | 半越狱 | 较新,界面现代 | | 14.0 - 14.8(A12+) | unc0ver 8.x | 半越狱 | 支持较新设备 | | 15.0 - 15.4.1 | Dopamine | 半越狱 | 目前最佳选择 | | 16.0 - 16.5(A12+) | palera1n(仅checkm8设备) | 半不完美 | 仅iPhone X及以下 |
术语解释: 完美越狱(Untethered):重启后仍保持越狱状态。iOS 9.1后几乎没有。 半不完美越狱(Semi-tethered):重启后失去越狱状态,需重新运行工具激活。 不完美越狱(Tethered):重启后必须连接电脑引导,否则无法开机(极少见)。
目前主流越狱都是半不完美越狱——重启后设备正常启动但失去越狱权限,需要重新运行越狱应用激活。 2.3 越狱后的必装组件成功越狱后,Cydia或Sileo(包管理器)会自动安装。以下工具和软件包是iOS逆向的必备:
[td]包名 | 用途 | | OpenSSH | 通过SSH连接设备(默认root密码alpine,建议修改) | | Apple File Conduit "2" | 在PC端通过iFunbox等工具访问完整文件系统 | | Filza File Manager | 设备上的文件管理器,可浏览/修改应用沙盒 | | PreferenceLoader | 在设置中加载插件 | | Cydia Substrate | Cydia的运行时Hook框架(类似Xposed) | | Frida | 跨平台Hook框架,强大且现代 | | debugserver | 调试服务器(从Xcode拷贝签名后使用) | | ldid | 伪造代码签名工具 | | MTerminal | 终端模拟器 |
安装命令示例(通过SSH): [size=12.573px]bash
ssh root@device_ipapt updateapt install frida
2.4 越狱检测与绕过许多应用(特别是银行应用、游戏、付费软件)会检测设备是否越狱,若检测到则拒绝运行。开发者常用检测方法包括: 黑客绕过方法: 我们将在后续章节详细展示Frida Hook绕过越狱检测的方法。 第三章 砸壳(Decryption)——获得可分析的二进制3.1 FairPlay加密原理当用户从App Store下载应用时,Apple的FairPlay DRM系统会使用设备的唯一密钥对应用进行加密。因此,不同设备上下载的同一应用虽然都是加密的,但加密密钥不同。这意味着: 砸壳的本质:当应用运行时,iOS内核会将加密的代码段解密到内存中执行。黑客可以在应用启动后、解密完成后,将内存中的原始二进制数据dump出来,保存到一个新文件中。这个dump出来的文件就是砸壳后的Mach-O,可以像普通二进制一样被分析。 3.2 砸壳工具的选择与使用工具对比:
[td]工具 | 特点 | 状态 | | frida-ios-dump | 基于Frida,无需设备端安装额外工具,操作简单 | 推荐 | | CrackerXI | 图形化工具,一键砸壳,支持App Store和本地IPA | 稳定 | | Clutch | 经典命令行工具,速度快,但不支持iOS 13+部分设备 | 较老 | | bF | 基于Cydia Substrate,早期工具 | 已不更新 | | Flex | 主要用于补丁制作,砸壳功能有限 | 不推荐 |
3.3 使用frida-ios-dump砸壳(详细步骤)前提:越狱iOS设备,已安装Frida。 步骤1:在设备上安装Frida(如果尚未安装): [size=12.573px]bash
# 通过SSH连接设备ssh root@192.168.1.100# 添加Frida源echo "deb https://build.frida.io/ packages/ ./" >> /etc/apt/sources.list.d/frida.list# 安装apt updateapt install frida
步骤2:在PC端安装frida-ios-dump: [size=12.573px]bash
git clone https://github.com/AloneMonkey/frida-ios-dumpcd frida-ios-dumppip install -r requirements.txt
步骤3:列出已安装的应用: [size=12.573px]bash
python dump.py -l
输出示例: [size=12.573px]text
Bundle identifier Display namecom.spotify.client Spotifycom.tinyspeck.tweetie2 Tweetiecom.example.MyPremiumApp MyPremiumApp
步骤4:砸壳目标应用: [size=12.573px]bash
python dump.py com.example.MyPremiumApp
脚本的工作流程: 步骤5:解压IPA获得Mach-O: [size=12.573px]bash
unzip MyPremiumApp.ipa -d MyPremiumApp_unpacked# 可执行文件位于 Payload/MyPremiumApp.app/MyPremiumApp
3.4 验证砸壳是否成功使用file命令检查: [size=12.573px]bash
file Payload/MyPremiumApp.app/MyPremiumApp
输出可能显示: 使用otool检查加密标志: [size=12.573px]bash
otool -l Payload/MyPremiumApp.app/MyPremiumApp | grep -A 4 LC_ENCRYPTION_INFO
如果cryptid为1表示仍加密,为0表示已解密(砸壳成功)。 3.5 砸壳失败的常见原因应用使用反调试/反注入保护:某些应用(如银行应用)会检测Frida的注入并退出。需要先绕过这些保护,可以使用frida-gadget注入或Hook反调试函数。 应用链接了静态框架:frida-ios-dump可能无法自动dump某些静态库。可以尝试用CrackerXI的“完整IPA砸壳”模式。 iOS版本过高:检查frida-ios-dump是否支持当前iOS版本,必要时更新脚本。
第四章 静态分析——IDA Pro与Hopper实战4.1 选择反汇编工具砸壳后获得的是ARM64架构的Mach-O可执行文件。分析iOS应用的静态分析工具主要有:
[td]工具 | 价格 | 特点 | | IDA Pro | $1,789起 | 行业标准,ARM64反编译质量高,支持Obj-C伪代码 | | Hopper Disassembler | $99 | macOS原生,性价比高,支持伪代码输出 | | Ghidra | 免费 | NSA开源,功能接近IDA,但ARM64支持稍弱 | | Radare2 | 免费 | 命令行工具,学习曲线陡峭,功能强大 |
对于初学者,Hopper是性价比之选。对于专业黑客,IDA Pro是不可替代的。本文以Hopper为主进行讲解,关键概念同样适用于IDA。 4.2 Hopper的基础操作步骤1:加载二进制
打开Hopper,将砸壳后的Mach-O文件拖入。选择架构(通常是ARM64)。 步骤2:等待分析完成
Hopper会自动识别函数、解析Objective-C类结构、构建控制流图。大型应用可能需要数分钟。 步骤3:导航界面 步骤4:搜索关键内容 4.3 Objective-C的特殊性与C/C++相比,Objective-C是高度动态的语言。所有方法调用在运行时通过objc_msgSend分派。这意味着: class-dump的使用: [size=12.573px]bash
class-dump -H MyPremiumApp -o headers/
输出:每个类一个.h文件,包含所有方法声明、属性和实例变量。 例如,一个头文件可能包含: [size=12.573px]objective-c
@interface PurchaseManager : NSObject- (BOOL)isPurchasedProduct NSString *)productId;- (void)restorePurchases;@property (assign, nonatomic) BOOL hasPremium;@end
黑客可以立即定位目标方法:isPurchasedProduct:或hasPremium属性的getter/setter。 4.4 分析关键方法假设我们需要破解VIP功能。在头文件中找到了UserManager类有一个isVIP方法。 Hopper中搜索-[UserManager isVIP],伪代码输出: [size=12.573px]c
bool -[UserManager isVIP(void *self, void *_cmd) { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults; BOOL premiumFlag = [defaults boolForKey:@"premium_flag"; if (premiumFlag != 0) { return 1; } // 检查购买日期 NSDate *purchaseDate = [defaults objectForKey:@"purchase_date"; if (purchaseDate && [purchaseDate timeIntervalSinceNow > 0) { return 1; } return 0;}
破解思路:修改该方法,让它无条件返回1(true)。 在Hopper中找到该函数的起始地址(如0x100012345),记录机器码。后续动态调试时需要修改这个地址的内容。 4.5 字符串加密的识别与处理iOS应用中常见的字符串加密模式:使用stringWithFormat:拼接片段,或使用自定义解密函数。 识别特征:
搜索可疑的CFStringCreateWithBytes调用,或看到大量看似随机的字节数据区域,然后有一个函数遍历这些字节进行XOR操作。 解密方法:
提取解密函数的算法(通常是一个循环XOR固定密钥),然后将所有加密字符串的密文拷贝出来,用Python或Hopper脚本批量解密。 [size=12.573px]python
# 示例:XOR 0x55解密enc_data = [0xE5, 0xC7, 0xE2, 0xC9, 0xCE, 0xDB, 0xC5, 0x00decoded = ''.join(chr(b ^ 0x55) for b in enc_data if b)print(decoded) # "License"
第五章 动态调试——lldb与debugserver5.1 配置debugserveriOS的动态调试依赖于debugserver——一个运行在设备上的调试服务端。越狱后,可以从Xcode中拷贝debugserver并签名使其可以在任何进程中运行。 步骤1:从已安装的Xcode中拷贝debugserver(或在越狱源中直接安装): [size=12.573px]bash
# 从iOS设备使用Cydia安装"Debugserver"包(许多越狱源提供)apt install debugserver
步骤2:为debugserver添加任务端口权限(如果需要调试任意进程): [size=12.573px]bash
# 使用ldid伪造签名ldid -e /usr/bin/debugserver > ent.xmlecho "<key>task_for_pid-allow</key><true/>" >> ent.xmlldid -Sent.xml /usr/bin/debugserver
步骤3:在设备上启动debugserver,附加到目标进程: [size=12.573px]bash
# 附加到正在运行的进程(按PID)debugserver *:1234 -a 1234# 或启动新进程并调试debugserver *:1234 /path/to/MyApp.app/MyApp
5.2 在Mac上使用lldb连接设备前提:设备与Mac在同一Wi-Fi网络或通过USB(需通过usbmuxd映射端口)。 步骤1:通过USB映射设备端口(以设备端口1234为例): [size=12.573px]bash
iproxy 1234 1234
现在Mac的localhost:1234将转发到设备:1234。 步骤2:启动lldb,连接远程服务器: [size=12.573px]bash
lldb(lldb) platform select remote-ios(lldb) platform connect connect://localhost:1234(lldb) process attach --pid 1234 # 或 --name MyApp
步骤3:设置断点: [size=12.573px]lldb
(lldb) breakpoint set --name "-[UserManager isVIP]"(lldb) breakpoint set --address 0x100012345(lldb) breakpoint set --selector validatePurchase:
步骤4:常用lldb命令:
[td]命令 | 作用 | | c | 继续执行 | | s | 单步进入(Step into) | | n | 单步跳过(Step over) | | p $x0 | 打印寄存器值 | | po (id)$x0 | 打印Objective-C对象 | | memory read <addr> | 读取内存 | | memory write <addr> <value> | 写入内存 | | reg write x0 1 | 修改寄存器 | | dis -s <addr> | 反汇编当前地址 |
5.3 修改程序行为——实战案例沿用之前的isVIP函数,地址0x100012345,函数开头是: [size=12.573px]assembly
0x100012345: sub sp, sp, #0x200x100012349: str x0, [sp, #0x10]0x10001234D: bl imp___stubs__objc_msgSend ; 调用UserDefaults...0x100012400: cmp w0, #0x00x100012404: b.eq 0x100012420 ; 如果w0==0则跳过返回0x100012408: mov w0, #0x10x10001240C: ret
我们想要让它立即返回1。在lldb中,可以修改第一条指令直接mov w0, #0x1然后ret。 方法一:修改内存(临时): [size=12.573px]lldb
(lldb) memory write 0x100012345 0x20 0x00 0x80 0xD2 0xC0 0x03 0x5F 0xD6
(ARM64指令mov x0, #1的编码为0x20 0x00 0x80 0xD2,ret的编码为0xC0 0x03 0x5F 0xD6) 方法二:使用breakpoint后修改寄存器: [size=12.573px]lldb
(lldb) breakpoint set --address 0x100012400(lldb) breakpoint command add> reg write x0 0> continue> DONE(lldb) c
这样每次执行到isVIP的末尾比较处,都会强制将返回值改为0(但实际上我们需要返回1,此处是示例)。 5.4 反调试绕过许多应用会检测是否被调试(ptrace(PT_DENY_ATTACH, ...)),检测到则退出。 绕过方法:
方法一:使用lldb的ptrace绕过插件
在lldb中加载anti-anti-debug脚本,自动拦截ptrace调用。 方法二:Hook ptrace系统调用(Frida): [size=12.573px]javascript
var ptrace = Module.findExportByName(null, "ptrace");Interceptor.attach(ptrace, { onEnter: function(args) { if (args[0 == 31) { // PT_DENY_ATTACH console.log("Blocking ptrace(PT_DENY_ATTACH)"); args[0 = -1; // 无效命令 } }});
方法三:Patch二进制
在IDA中找到ptrace的调用指令,将其改为mov x0, #0(20 00 80 D2)或直接NOP掉。 第六章 Frida在iOS逆向中的应用6.1 为什么选择FridaFrida是跨平台的动态Hook框架,对于iOS逆向,它的优势包括: 不需要重新签名和打包应用程序 支持Objective-C方法的直接Hook 支持Native函数Hook 可以动态枚举类和模块 支持脚本实时修改,无需重启应用
6.2 Frida Hook Objective-C方法示例1:Hook isPremium方法,强制返回YES [size=12.573px]javascript
if (ObjC.available) { var UserManager = ObjC.classes.UserManager; // Hook实例方法 Interceptor.attach(UserManager["- isPremium".implementation, { onLeave: function(retval) { console.log("Original return value: " + retval); retval.replace(ptr(1)); // 返回YES } });}
示例2:Hook属性getter [size=12.573px]javascript
var PremiumManager = ObjC.classes.PremiumManager;// 获取属性getter的实现var getter = PremiumManager["- hasUnlimitedAccess".implementation;Interceptor.attach(getter, { onLeave: function(retval) { console.log("hasUnlimitedAccess called, forcing YES"); retval.replace(ptr(1)); }});
示例3:打印所有方法的调用(追踪) [size=12.573px]javascript
function traceMethods(className) { var cls = ObjC.classes[className; var methods = cls.$ownMethods; for (var i = 0; i < methods.length; i++) { var methodName = methods[i; Interceptor.attach(cls[methodName.implementation, { onEnter: function(args) { console.log(className + " " + methodName + " called"); } }); }}traceMethods("UserManager");
6.3 绕过越狱检测的通用脚本[size=12.573px]javascript
// 隐藏越狱文件检查var fileManager = ObjC.classes.NSFileManager;Interceptor.attach(fileManager["- fileExistsAtPath:".implementation, { onEnter: function(args) { var path = ObjC.Object(args[2).toString(); if (path.indexOf("/Applications/Cydia.app") !== -1 || path.indexOf("/usr/sbin/sshd") !== -1 || path.indexOf("/bin/bash") !== -1) { console.log("Blocking check for: " + path); // 强制返回NO this.shouldReturn = true; } }, onLeave: function(retval) { if (this.shouldReturn) { retval.replace(ptr(0)); } }});// 绕过fork检测var forkPtr = Module.findExportByName(null, "fork");if (forkPtr) { Interceptor.attach(forkPtr, { onEnter: function(args) { console.log("fork detected, returning -1"); this.returnValue = -1; }, onLeave: function(retval) { if (this.returnValue !== undefined) { retval.replace(ptr(this.returnValue)); } } });}
6.4 使用Frida的ObjC API动态探索Frida可以在运行时枚举所有加载的类和实例,这对于分析复杂应用非常有用。 列出所有类: [size=12.573px]javascript
var count = ObjC.enumerateLoadedClasses({ onMatch: function(className) { console.log(className); }, onComplete: function() {}});
查找特定类的实例: [size=12.573px]javascript
function findInstance(className) { var cls = ObjC.classes[className; var instances = [; ObjC.choose(cls, { onMatch: function(instance) { instances.push(instance); console.log("Found instance: " + instance); }, onComplete: function() { console.log("Total instances: " + instances.length); } });}
第七章 实战案例(一):砸壳并分析系统备忘录应用7.1 目标Notes.app(系统备忘录),分析其iCloud同步逻辑(学习目的,不做非法修改)。 7.2 步骤确认应用位置:系统应用位于/Applications/。 砸壳:系统应用默认未加密?实际上iOS 10+系统应用也加密了。使用frida-ios-dump砸壳: [size=12.573px]bash
python dump.py com.apple.Notesclass-dump提取头文件: [size=12.573px]bash
class-dump -H Notes -o notes_headers/Hopper分析:找到NotesDataManager类,查看syncAllNotes方法,理解其网络交互逻辑。
第八章 实战案例(二):破解IAP内购验证8.1 目标应用PhotoEditorPro.ipa——一款照片编辑应用,提供订阅解锁专业功能。内购使用StoreKit框架。 8.2 StoreKit内购流程回顾应用创建SKPayment(包含产品ID)。 调用SKPaymentQueue.default().add(payment)。 用户确认支付,App Store处理交易。 交易完成后,调用paymentQueue(_:updatedTransactions  代理方法。 应用在代理方法中检查transactionState == .purchased,调用finishTransaction。 解锁付费功能(通常是保存一个UserDefaults标志)。
8.3 Frida Hook破解内购思路:模拟支付成功回调,跳过真实支付。 [size=12.573px]javascript
if (ObjC.available) { // Hook SKPaymentQueue的add方法,阻止真实支付 var SKPaymentQueue = ObjC.classes.SKPaymentQueue; Interceptor.attach(SKPaymentQueue["- addPayment:".implementation, { onEnter: function(args) { console.log(" ayment request intercepted"); // 直接调用支付成功回调,不实际发起支付 var payment = ObjC.Object(args[2); var productId = payment.productIdentifier(); console.log(" roduct ID: " + productId); // 模拟交易完成 var transaction = ObjC.classes.SKPaymentTransaction.alloc().init(); transaction.setTransactionState_(1); // SKPaymentTransactionStatePurchased transaction.setPayment_(payment); // 获取代理对象 var delegate = SKPaymentQueue.defaultQueue().transactionObserver(); if (delegate) { delegate["paymentQueue:updatedTransactions:"(SKPaymentQueue.defaultQueue(), [transaction); } } }); // Hook finishTransaction,防止应用检查真实状态 Interceptor.attach(SKPaymentQueue["- finishTransaction:".implementation, { onEnter: function(args) { console.log("Transaction finished successfully"); } });}
8.4 本地激活标志修改即使内购回调被模拟,应用可能还会通过UserDefaults或Keychain存储激活状态。找到存储标志的代码并Hook: [size=12.573px]javascript
var NSUserDefaults = ObjC.classes.NSUserDefaults;Interceptor.attach(NSUserDefaults["- setBool:forKey:".implementation, { onEnter: function(args) { var key = ObjC.Object(args[3).toString(); if (key == "premium_purchased") { console.log("Intercepted setBool for key: " + key); // 确保存储为YES args[2 = ptr(1); } }});
第九章 实战案例(三):免费试用期延长9.1 目标应用FitnessApp.ipa——7天试用期,到期后需订阅。 9.2 定位到期检查代码搜索字符串trial、expir、days left。在Hopper中找到: [size=12.573px]objective-c
- (NSInteger)daysLeftInTrial { NSDate *installDate = [[NSUserDefaults standardUserDefaults] objectForKey "install_date"]; if (!installDate) { installDate = [NSDate date]; [[NSUserDefaults standardUserDefaults] setObject:installDate forKey "install_date"]; } NSInteger daysSinceInstall = [self daysBetween:installDate and:[NSDate date]]; NSInteger daysRemaining = 7 - daysSinceInstall; return daysRemaining > 0 ? daysRemaining : 0;}
9.3 Hook安装日期[size=12.573px]javascript
var NSUserDefaults = ObjC.classes.NSUserDefaults;Interceptor.attach(NSUserDefaults["- objectForKey:".implementation, { onEnter: function(args) { var key = ObjC.Object(args[2).toString(); if (key == "install_date") { console.log("Intercepted reading install_date"); // 伪造安装日期为昨天 var lastWeek = ObjC.classes.NSDate.dateWithTimeIntervalSinceNow_(-24*60*60); this.fakeDate = lastWeek; } }, onLeave: function(retval) { if (this.fakeDate !== undefined) { var retObj = ObjC.Object(retval); console.log("Original install date: " + retObj); retval.replace(this.fakeDate); console.log("Fake install date set."); } }});
运行后,应用认为昨天刚安装,试用期还有6天。 第十章 实战案例(四):动态修改网络响应10.1 目标CloudMusicApp.ipa——流媒体音乐应用,付费订阅才能离线下载。启动时向服务器请求用户状态,返回{"subscriber":false,"download_enabled":false}。 10.2 Hook NSURLSession/NSURLConnection大多数iOS应用使用NSURLSession发送网络请求。Hook数据任务回调,修改响应体。 [size=12.573px]javascript
var NSURLSessionDataTask = ObjC.classes.NSURLSessionDataTask;// 更简单的方法:Hook NSURLSession的dataTaskWithRequest:completionHandler:var NSURLSession = ObjC.classes.NSURLSession;Interceptor.attach(NSURLSession["- dataTaskWithRequest:completionHandler:".implementation, { onEnter: function(args) { var request = ObjC.Object(args[2); var url = request.URL().absoluteString(); if (url.indexOf("/api/user/status") !== -1) { var handler = args[3; // completionHandler block // 包装block,修改响应 this.modifiedHandler = Block_copy(handler); var original = this.modifiedHandler; var newHandler = ObjC.Block.implement(function(data, response, error) { // 解析data,修改订阅状态 var json = ObjC.classes.NSJSONSerialization.JSONObjectWithData_options_error(data, 0, NULL); if (json) { json.setObject_forKey_(ObjC.classes.NSNumber.numberWithBool_(true), "subscriber"); json.setObject_forKey_(ObjC.classes.NSNumber.numberWithBool_(true), "download_enabled"); var newData = ObjC.classes.NSJSONSerialization.dataWithJSONObject_options_error(json, 0, NULL); original(newData, response, error); } else { original(data, response, error); } }); args[3 = newHandler; } }});
10.3 SSL Pinning绕过补充对于启用了证书固定的应用,上述Hook仍然会被SSL层拒绝。需要配合前面的bypass_ssl.js一起使用。 第十一章 重打包与注入11.1 直接修改二进制再重签名iOS的代码签名机制非常严格:任何对可执行文件或应用包内文件的修改都会破坏签名,导致应用无法启动。除非设备已越狱(绕过代码签名检查),否则必须对修改后的应用重新签名。 签名要求: 步骤概览(非越狱设备): 对于越狱设备,可以直接将修改后的应用复制到/Applications/目录,然后用uicache刷新图标,无需签名(越狱后系统忽略签名检查)。 11.2 注入dylib(Tweak开发)越狱环境下,使用Theos工具链可以开发Cydia Substrate插件(tweak)。Tweak通过动态注入dylib的方式修改应用行为,不需要修改原始二进制。 一个简单的tweak例子(Logos语法): [size=12.573px]objective-c
%hook UserManager- (BOOL)isPremium { return YES;}%end
编译后生成.dylib,安装到/Library/MobileSubstrate/DynamicLibraries/,重启应用生效。 第十二章 总结本文以超过一万五千字的篇幅,全面系统地讲解了iOS应用的逆向破解技术。从越狱环境的搭建、砸壳获得可分析二进制、静态分析(Hopper/IDA)、动态调试(lldb),到Frida Hook、内购破解、试用期延长、网络响应篡改,再到重打包与tweak注入。 iOS逆向的门槛高于Android,主要障碍在于FairPlay加密和严格的代码签名机制。然而,越狱设备上的调试环境和Frida等工具的发展,使得黑客仍然能够有效地分析和修改iOS应用。对于开发者而言,理解这些技术是设计抗破解产品的前提;对于安全研究者,掌握iOS逆向有助于发现应用中的漏洞和隐私问题。 后续本系列将继续探讨硬件加密锁(Dongle)的破解与模拟技术。 关键词:iOS逆向;越狱;砸壳;Frida;黑客;破解软件;Objective-C Hook;内购破解;lldb调试
|