黑客软件破解系列之七:iOS应用逆向与破解——越狱环境下的砸壳、静态分析与动态调试
黑客软件破解深度论文系列之七: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逆向的独特挑战和应对策略。维度AndroidiOS
应用分发官方商店为主,侧载(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压缩包)解压后的典型结构: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)和免费试用延长是黑客最关注的领域。
目标类型说明典型手段
内购破解(IAP)免费获得付费订阅、一次性购买内容Hook paymentQueue、伪造购买回执
VIP解锁使用高级功能、去广告修改isPremium返回值
免费试用延长无限使用试用期修改购买日期、Hook到期检查
去除越狱检测让应用在越狱设备上正常运行Hook越狱检测函数
功能限制移除解锁被禁用的功能修改条件分支、替换资源
第二章 越狱环境的搭建——iOS逆向的基础2.1 什么是越狱越狱(Jailbreak)是移除iOS系统对用户的限制(主要是代码签名强制检查和文件系统沙盒)的过程。越狱后,黑客可以:
[*]以root权限运行任意代码
[*]安装未签名的应用(包括修改版应用)
[*]访问整个文件系统(包括其他应用的沙盒目录)
[*]使用调试器(lldb)附加到任意进程
[*]动态注入代码(Cydia Substrate、Frida)
越狱是iOS逆向的先决条件(除极少数使用checkm8漏洞的硬件级调试外)。没有越狱,无法从加密的App Store应用中提取可分析的文件,也无法对应用进行动态调试。2.2 越狱工具的选择(按iOS版本)
iOS版本推荐越狱工具类型特点
12.0 - 14.8unc0ver半越狱(半不完美)稳定,支持A8-A13芯片
12.0 - 14.3Taurine半越狱较新,界面现代
14.0 - 14.8(A12+)unc0ver 8.x半越狱支持较新设备
15.0 - 15.4.1Dopamine半越狱目前最佳选择
16.0 - 16.5(A12+)palera1n(仅checkm8设备)半不完美仅iPhone X及以下
术语解释:
[*]完美越狱(Untethered):重启后仍保持越狱状态。iOS 9.1后几乎没有。
[*]半不完美越狱(Semi-tethered):重启后失去越狱状态,需重新运行工具激活。
[*]不完美越狱(Tethered):重启后必须连接电脑引导,否则无法开机(极少见)。
目前主流越狱都是半不完美越狱——重启后设备正常启动但失去越狱权限,需要重新运行越狱应用激活。2.3 越狱后的必装组件成功越狱后,Cydia或Sileo(包管理器)会自动安装。以下工具和软件包是iOS逆向的必备:
包名用途
OpenSSH通过SSH连接设备(默认root密码alpine,建议修改)
Apple File Conduit "2"在PC端通过iFunbox等工具访问完整文件系统
Filza File Manager设备上的文件管理器,可浏览/修改应用沙盒
PreferenceLoader在设置中加载插件
Cydia SubstrateCydia的运行时Hook框架(类似Xposed)
Frida跨平台Hook框架,强大且现代
debugserver调试服务器(从Xcode拷贝签名后使用)
ldid伪造代码签名工具
MTerminal终端模拟器
安装命令示例(通过SSH):bash
ssh root@device_ipapt updateapt install frida
2.4 越狱检测与绕过许多应用(特别是银行应用、游戏、付费软件)会检测设备是否越狱,若检测到则拒绝运行。开发者常用检测方法包括:
[*]检查常见越狱文件是否存在:/Applications/Cydia.app、/usr/sbin/sshd
[*]检查是否能从沙盒外读取系统文件
[*]检查fork()系统调用是否能成功(非越狱设备沙盒进程无法fork)
[*]检查URL Scheme cydia://
黑客绕过方法:
[*]使用越狱屏蔽插件:如Liberty Lite、Shadow、FlyJB X,这些插件会Hook系统API,向应用隐藏越狱痕迹。
[*]手动Patch应用:在二进制中搜索越狱检测相关的字符串和API调用,修改判断逻辑。
我们将在后续章节详细展示Frida Hook绕过越狱检测的方法。第三章 砸壳(Decryption)——获得可分析的二进制3.1 FairPlay加密原理当用户从App Store下载应用时,Apple的FairPlay DRM系统会使用设备的唯一密钥对应用进行加密。因此,不同设备上下载的同一应用虽然都是加密的,但加密密钥不同。这意味着:
[*]无法直接在其他设备上运行从一台设备拷贝的加密应用
[*]静态分析工具(IDA、Hopper)看到的只是加密后的乱码
[*]必须先“砸壳”——在内存中获取运行时的解密镜像
砸壳的本质:当应用运行时,iOS内核会将加密的代码段解密到内存中执行。黑客可以在应用启动后、解密完成后,将内存中的原始二进制数据dump出来,保存到一个新文件中。这个dump出来的文件就是砸壳后的Mach-O,可以像普通二进制一样被分析。3.2 砸壳工具的选择与使用工具对比:
工具特点状态
frida-ios-dump基于Frida,无需设备端安装额外工具,操作简单推荐
CrackerXI图形化工具,一键砸壳,支持App Store和本地IPA稳定
Clutch经典命令行工具,速度快,但不支持iOS 13+部分设备较老
bF基于Cydia Substrate,早期工具已不更新
Flex主要用于补丁制作,砸壳功能有限不推荐
3.3 使用frida-ios-dump砸壳(详细步骤)前提:越狱iOS设备,已安装Frida。步骤1:在设备上安装Frida(如果尚未安装):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:bash
git clone https://github.com/AloneMonkey/frida-ios-dumpcd frida-ios-dumppip install -r requirements.txt
步骤3:列出已安装的应用:bash
python dump.py -l
输出示例:text
Bundle identifier Display namecom.spotify.client Spotifycom.tinyspeck.tweetie2 Tweetiecom.example.MyPremiumApp MyPremiumApp
步骤4:砸壳目标应用:bash
python dump.py com.example.MyPremiumApp
脚本的工作流程:
[*]在设备上启动目标应用。
[*]Frida注入到应用进程。
[*]遍历内存中的所有已映射的Mach-O镜像。
[*]将未被加密的镜像或已解密的镜像dump到本地。
[*]自动生成MyPremiumApp.ipa(已砸壳)。
步骤5:解压IPA获得Mach-O:bash
unzip MyPremiumApp.ipa -d MyPremiumApp_unpacked# 可执行文件位于 Payload/MyPremiumApp.app/MyPremiumApp
3.4 验证砸壳是否成功使用file命令检查:bash
file Payload/MyPremiumApp.app/MyPremiumApp
输出可能显示:
[*]砸壳前(若直接查看加密文件):encrypted字样
[*]砸壳后:Mach-O 64-bit executable arm64
使用otool检查加密标志: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应用的静态分析工具主要有:
工具价格特点
IDA Pro$1,789起行业标准,ARM64反编译质量高,支持Obj-C伪代码
Hopper Disassembler$99macOS原生,性价比高,支持伪代码输出
Ghidra免费NSA开源,功能接近IDA,但ARM64支持稍弱
Radare2免费命令行工具,学习曲线陡峭,功能强大
对于初学者,Hopper是性价比之选。对于专业黑客,IDA Pro是不可替代的。本文以Hopper为主进行讲解,关键概念同样适用于IDA。4.2 Hopper的基础操作步骤1:加载二进制
打开Hopper,将砸壳后的Mach-O文件拖入。选择架构(通常是ARM64)。步骤2:等待分析完成
Hopper会自动识别函数、解析Objective-C类结构、构建控制流图。大型应用可能需要数分钟。步骤3:导航界面
[*]程序集列表(左侧):按地址顺序列出所有函数。
[*]标签列表(左侧):Objective-C类和方法、字符串、导入函数。
[*]反汇编视图(中央):ARM64汇编代码。
[*]伪代码视图:按或右键→Produce Pseudo-Code,生成类C代码。
步骤4:搜索关键内容
[*]字符串搜索:Search→Search in All Sections→String,输入failed、success、premium、license等。
[*]方法名搜索:Objective-C方法签名格式为-,搜索例如-。
4.3 Objective-C的特殊性与C/C++相比,Objective-C是高度动态的语言。所有方法调用在运行时通过objc_msgSend分派。这意味着:
[*]方法名保留在二进制中(因为运行时需要根据名称查找实现)。这使得黑客可以直接搜索方法名定位代码。
[*]类的继承关系、属性、协议信息都保留在二进制中。class-dump工具可以提取所有这些头文件。
class-dump的使用:bash
class-dump -H MyPremiumApp -o headers/
输出:每个类一个.h文件,包含所有方法声明、属性和实例变量。例如,一个头文件可能包含: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中搜索-,伪代码输出: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脚本批量解密。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(或在越狱源中直接安装):bash
# 从iOS设备使用Cydia安装"Debugserver"包(许多越狱源提供)apt install debugserver
步骤2:为debugserver添加任务端口权限(如果需要调试任意进程):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,附加到目标进程: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为例):bash
iproxy 1234 1234
现在Mac的localhost:1234将转发到设备:1234。步骤2:启动lldb,连接远程服务器:bash
lldb(lldb) platform select remote-ios(lldb) platform connect connect://localhost:1234(lldb) process attach --pid 1234 # 或 --name MyApp
步骤3:设置断点:lldb
(lldb) breakpoint set --name "-"(lldb) breakpoint set --address 0x100012345(lldb) breakpoint set --selector validatePurchase:
步骤4:常用lldb命令:
命令作用
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,函数开头是:assembly
0x100012345:subsp, sp, #0x200x100012349:strx0, , #0x10]0x10001234D:bl imp___stubs__objc_msgSend; 调用UserDefaults...0x100012400:cmpw0, #0x00x100012404:b.eq 0x100012420 ; 如果w0==0则跳过返回0x100012408:movw0, #0x10x10001240C:ret
我们想要让它立即返回1。在lldb中,可以修改第一条指令直接mov w0, #0x1然后ret。方法一:修改内存(临时):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后修改寄存器: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):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方法,强制返回YESjavascript
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属性getterjavascript
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:打印所有方法的调用(追踪)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 绕过越狱检测的通用脚本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可以在运行时枚举所有加载的类和实例,这对于分析复杂应用非常有用。列出所有类:javascript
var count = ObjC.enumerateLoadedClasses({ onMatch: function(className) { console.log(className); }, onComplete: function() {}});
查找特定类的实例: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砸壳:bash
python dump.py com.apple.Notes
[*]class-dump提取头文件: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破解内购思路:模拟支付成功回调,跳过真实支付。javascript
if (ObjC.available) { // Hook SKPaymentQueue的add方法,阻止真实支付 var SKPaymentQueue = ObjC.classes.SKPaymentQueue; Interceptor.attach(SKPaymentQueue["- addPayment:".implementation, { onEnter: function(args) { console.log("Payment request intercepted"); // 直接调用支付成功回调,不实际发起支付 var payment = ObjC.Object(args[2); var productId = payment.productIdentifier(); console.log("Product 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: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中找到:objective-c
- (NSInteger)daysLeftInTrial { NSDate *installDate = [ objectForKey:@"install_date"]; if (!installDate) { installDate = ; [ setObject:installDate forKey:@"install_date"]; } NSInteger daysSinceInstall = ]; NSInteger daysRemaining = 7 - daysSinceInstall; return daysRemaining > 0 ? daysRemaining : 0;}
9.3 Hook安装日期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数据任务回调,修改响应体。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的代码签名机制非常严格:任何对可执行文件或应用包内文件的修改都会破坏签名,导致应用无法启动。除非设备已越狱(绕过代码签名检查),否则必须对修改后的应用重新签名。签名要求:
[*]使用开发证书(需要Apple开发者账号,免费账号也可)
[*]在Entitlements.plist中包含应用所需的权限(如iCloud、推送通知)。
步骤概览(非越狱设备):
[*]砸壳应用(必须是已解密的)。
[*]使用codesign移除原签名(codesign --remove-signature MyApp.app)。
[*]修改二进制、资源文件等。
[*]重新签名:bash
codesign -f -s "iPhone Developer: xxx" --entitlements entitlements.plist MyApp.app
[*]打包为IPA,使用ios-deploy或Xcode安装。
对于越狱设备,可以直接将修改后的应用复制到/Applications/目录,然后用uicache刷新图标,无需签名(越狱后系统忽略签名检查)。11.2 注入dylib(Tweak开发)越狱环境下,使用Theos工具链可以开发Cydia Substrate插件(tweak)。Tweak通过动态注入dylib的方式修改应用行为,不需要修改原始二进制。一个简单的tweak例子(Logos语法):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调试
页:
[1]