iOS安全和逆向系列教程 第8篇:iOS应用动态分析与Hook技术
iOS安全和逆向系列教程 第8篇:iOS应用动态分析与Hook技术
作者:自学不成才
在前两篇文章中,我们深入探讨了Mach-O文件格式和静态分析方法。尽管静态分析能够提供应用结构的全景视图,但仍有许多问题无法仅通过静态分析解决,例如运行时行为、动态加载的代码和复杂的加密逻辑。这就是动态分析发挥作用的地方。本文将详细介绍iOS应用的动态分析技术和Hook方法,帮助您在应用运行时观察和修改其行为。
动态分析与静态分析的区别
动态分析的优势
相比静态分析,动态分析具有以下优势:
- 观察真实行为:直接观察应用在运行时的实际行为
- 绕过混淆:不受代码混淆和加密的影响
- 获取运行时状态:观察内存状态、对象值和执行流程
- 交互式分析:可以实时修改参数和返回值,测试不同场景
- 处理动态特性:能够分析反射、动态加载和JIT编译等技术
动态分析的局限性
动态分析也有其局限性:
- 覆盖率问题:只能分析执行到的代码
- 环境依赖:需要特定环境和条件
- 抗调试机制:应用可能检测调试行为并改变自身行为
- 操作繁琐:设置环境和工具较为复杂
- 时序性:时序相关的问题可能难以复现
动态分析环境搭建
越狱设备准备
最理想的动态分析环境是越狱的iOS设备:
-
设备选择:
- 推荐使用较旧的设备,如iPhone 8/X系列(A11芯片)
- 确保设备可以越狱(通常iOS 14.3-14.8较为稳定)
-
越狱工具:
- Checkra1n:适用于A7-A11设备
- Unc0ver:支持多个iOS版本
- Taurine:iOS 14专用越狱工具
-
基本设置:
# 安装OpenSSH(越狱后必装) apt-get update apt-get install openssh# 修改默认密码(默认为alpine) passwd root passwd mobile
非越狱环境选项
如果无法获得越狱设备,可以考虑以下替代方案:
-
开发者模式:
- 需要Apple开发者账号
- 使用Xcode和开发证书重签名并注入调试代码
-
模拟器:
- 使用Xcode iOS模拟器
- 功能有限,无法完全模拟真实设备
-
特殊工具:
- Corellium:商业iOS虚拟化平台
- iSEP:研究级iOS安全评估平台
动态分析工具介绍
调试工具
LLDB
LLDB是Xcode内置的强大调试器,也可以单独使用:
# 通过USB连接到已启动的应用进程
lldb -p $(ps ax | grep "AppName" | grep -v grep | awk '{print $1}')# 使用调试服务器(越狱设备)
debugserver *:1234 -a "AppName"
lldb
(lldb) platform select remote-ios
(lldb) process connect connect://device-ip:1234
常用LLDB命令:
breakpoint set --name "-[ClassName methodName:]"
:设置断点po [object description]
:打印对象描述memory read --size 4 --format x --count 10 0x12345678
:读取内存expression [expression]
:执行表达式thread backtrace
:显示调用栈
cycript
cycript是一个允许开发者在运行的应用中注入JavaScript代码的工具:
# 安装(越狱设备)
apt-get install cycript# 连接到进程
cycript -p ProcessName
常用cycript命令:
// 获取应用委托
var delegate = [UIApplication sharedApplication].delegate// 查看视图层次
UIApp.keyWindow.recursiveDescription().toString()// 查找所有的视图控制器
var controllers = []
var findControllers = function(view) {if (view.nextResponder instanceof UIViewController) {controllers.push(view.nextResponder);}for (var i = 0; i < view.subviews.count; i++) {findControllers(view.subviews[i]);}
}
findControllers(UIApp.keyWindow)
controllers
Hook框架
Frida
Frida是一款强大的动态插桩工具,支持多平台:
# 在电脑上安装Frida
pip install frida-tools# 在越狱设备上安装Frida服务器
# 1. 下载对应版本的frida-server
# 2. 将其传输到设备并设置权限
scp frida-server-x.y.z-ios-arm64 root@device-ip:/usr/sbin/frida-server
ssh root@device-ip "chmod +x /usr/sbin/frida-server"# 启动Frida服务器
ssh root@device-ip "/usr/sbin/frida-server &"# 列出设备上的应用
frida-ps -Ua# 附加到运行中的应用
frida -U AppName
基本Frida脚本示例:
// hook-example.js
Java.perform(function() {// 拦截Objective-C方法var LoginManager = ObjC.classes.LoginManager;Interceptor.attach(LoginManager["- validateCredentials:password:"].implementation, {onEnter: function(args) {// 'this' 是目标对象// args[0] 是 'self'// args[1] 是 selector// args[2]开始是实际参数var username = ObjC.Object(args[2]).toString();var password = ObjC.Object(args[3]).toString();console.log("[+] validateCredentials:password: called");console.log(" Username: " + username);console.log(" Password: " + password);// 保存参数供onLeave使用this.username = username;},onLeave: function(retval) {// retval是返回值console.log("[+] validateCredentials:password: returned: " + retval);// 修改返回值(强制返回true)retval.replace(0x1);console.log("[+] Return value replaced to true");}});
});
使用上述脚本:
frida -U -l hook-example.js AppName
Substrate (Cydia Substrate)
Substrate是越狱环境下的底层Hook框架:
// 基本使用示例
#import <substrate.h>// 定义原始方法指针
static BOOL (*original_validateCredentials)(id self, SEL _cmd, NSString *username, NSString *password);// 定义替换方法
static BOOL replaced_validateCredentials(id self, SEL _cmd, NSString *username, NSString *password) {NSLog(@"[HOOK] validateCredentials called with username: %@ and password: %@", username, password);// 调用原始方法BOOL result = original_validateCredentials(self, _cmd, username, password);NSLog(@"[HOOK] Original method returned: %d", result);// 修改返回值return YES;
}%ctor {// 获取目标类和方法Class targetClass = objc_getClass("LoginManager");SEL targetSelector = @selector(validateCredentials:password:);// 执行HookMSHookMessageEx(targetClass, targetSelector, (IMP)replaced_validateCredentials, (IMP*)&original_validateCredentials);
}
编译并加载Substrate扩展:
# 编译为dylib
clang -dynamiclib -framework Foundation -framework UIKit -I/opt/theos/include -L/opt/theos/lib -lsubstrate -o hook.dylib hook.m# 使用ldid签名(越狱环境)
ldid -S hook.dylib# 注入到目标应用
DYLD_INSERT_LIBRARIES=/path/to/hook.dylib /Applications/AppName.app/AppName
Theos & Logos
Theos是一个跨平台的iOS开发工具链,Logos是其内置的简化Hook语法:
# 安装Theos
git clone --recursive https://github.com/theos/theos.git $THEOS# 创建新Tweak
$THEOS/bin/nic.pl
# 选择"iphone/tweak"模板
使用Logos语法编写Hook代码:
// Tweak.x
%hook LoginManager- (BOOL)validateCredentials:(NSString *)username password:(NSString *)password {NSLog(@"[HOOK] validateCredentials called with username: %@ and password: %@", username, password);// 调用原始方法BOOL result = %orig;NSLog(@"[HOOK] Original method returned: %d", result);// 修改返回值return YES;
}%end
编译并安装Tweak:
make
make package
make install
网络分析工具
Charles Proxy
Charles是一个HTTP代理服务器,用于监控网络请求:
-
设置步骤:
- 在电脑上安装并运行Charles
- 配置iOS设备使用Charles作为HTTP代理
- 安装Charles SSL证书以监控HTTPS流量
-
主要功能:
- 查看请求和响应的完整内容
- 修改请求和响应数据
- 保存会话以供后续分析
- 断点调试网络请求
Wireshark
Wireshark是深度网络分析工具:
-
使用方法:
- 通过USB网络接口捕获iOS设备流量
- 使用适当的过滤器隔离目标应用流量
- 分析底层协议和加密握手
-
优势:
- 低级协议分析能力
- 可以观察加密前的原始数据
动态分析实战技巧
应用启动分析
观察应用启动过程是理解应用架构的好方法:
// Frida脚本:监控应用委托方法
Interceptor.attach(ObjC.classes.UIApplication["- application:didFinishLaunchingWithOptions:"].implementation, {onEnter: function(args) {console.log("[+] Application is launching...");},onLeave: function(retval) {console.log("[+] Application launched");// 打印关键对象var app = ObjC.classes.UIApplication.sharedApplication();var delegate = app.delegate();var windows = app.windows();var rootVC = app.keyWindow().rootViewController();console.log("[+] App Delegate: " + delegate.$className);console.log("[+] Window count: " + windows.count());console.log("[+] Root ViewController: " + rootVC.$className);}
});
用户界面分析
理解视图层次和控制器结构:
// Frida脚本:监控视图控制器生命周期
function monitorViewControllerLifecycle() {var viewDidLoad = ObjC.classes.UIViewController["- viewDidLoad"];var viewWillAppear = ObjC.classes.UIViewController["- viewWillAppear:"];var viewDidAppear = ObjC.classes.UIViewController["- viewDidAppear:"];Interceptor.attach(viewDidLoad.implementation, {onEnter: function(args) {var controller = ObjC.Object(args[0]);console.log("[+] viewDidLoad: " + controller.$className);}});Interceptor.attach(viewWillAppear.implementation, {onEnter: function(args) {var controller = ObjC.Object(args[0]);console.log("[+] viewWillAppear: " + controller.$className);}});Interceptor.attach(viewDidAppear.implementation, {onEnter: function(args) {var controller = ObjC.Object(args[0]);console.log("[+] viewDidAppear: " + controller.$className);// 打印视图层次var view = controller.view();console.log(view.recursiveDescription().toString());}});
}monitorViewControllerLifecycle();
网络请求分析
监控和修改网络请求是动态分析的关键部分:
// Frida脚本:监控NSURLSession请求
function monitorURLSession() {var NSURLSession = ObjC.classes.NSURLSession;var createTask = NSURLSession["- dataTaskWithRequest:completionHandler:"];Interceptor.attach(createTask.implementation, {onEnter: function(args) {var request = ObjC.Object(args[2]);var url = request.URL().absoluteString().toString();var method = request.HTTPMethod().toString();console.log("[+] NSURLSession Request: " + method + " " + url);// 打印请求头var headers = request.allHTTPHeaderFields();var headerKeys = headers.allKeys();for (var i = 0; i < headerKeys.count(); i++) {var key = headerKeys.objectAtIndex_(i);var value = headers.objectForKey_(key);console.log(" " + key + ": " + value);}// 打印请求体var body = request.HTTPBody();if (body) {console.log("[+] Body:");console.log(ObjC.classes.NSString.alloc().initWithData_encoding_(body, 4).toString());}// 修改请求(示例:添加自定义头)var mutableRequest = request.mutableCopy();mutableRequest.setValue_forHTTPHeaderField_("CustomValue", "X-Custom-Header");args[2] = mutableRequest;}});// 监听响应var NSURLResponse = ObjC.classes.NSURLResponse;
}monitorURLSession();
加密逻辑分析
分析和修改加密逻辑,是攻破应用安全措施的关键:
// Frida脚本:监控常见加密API
function monitorCryptoAPIs() {// 监控CommonCryptovar CommonCrypto = Module.findExportByName(null, "CCCrypt");if (CommonCrypto) {Interceptor.attach(CommonCrypto, {onEnter: function(args) {// CCCrypt参数解析var op = args[0].toInt32(); // 0 = kCCEncrypt, 1 = kCCDecryptvar alg = args[1].toInt32(); // 加密算法var options = args[2].toInt32(); // 选项// 保存上下文信息this.op = op;this.dataIn = args[4]; // 输入数据this.dataInLength = args[5].toInt32(); // 输入长度this.dataOut = args[6]; // 输出缓冲区console.log("[+] CCCrypt called: " + (op === 0 ? "Encrypt" : "Decrypt"));console.log(" Algorithm: " + alg);console.log(" Input length: " + this.dataInLength);// 打印密钥(通常是第4个参数)var keyData = Memory.readByteArray(args[3], args[7].toInt32());console.log(" Key: " + hexdump(keyData));// 打印输入数据if (this.dataInLength > 0) {var inputData = Memory.readByteArray(this.dataIn, Math.min(this.dataInLength, 64));console.log(" Input data: " + hexdump(inputData));}},onLeave: function(retval) {// 打印操作结果console.log("[+] CCCrypt returned: " + retval);// 打印加密/解密结果if (this.op === 1) { // 如果是解密var outputData = Memory.readByteArray(this.dataOut, Math.min(this.dataInLength, 64));console.log(" Decrypted data: " + hexdump(outputData));// 尝试作为字符串打印try {var str = Memory.readUtf8String(this.dataOut);if (str && str.length > 0) {console.log(" As string: " + str);}} catch (e) {// 忽略非字符串数据}}}});}// 监控其他加密API...
}monitorCryptoAPIs();
本地存储分析
监控应用的数据持久化操作:
// Frida脚本:监控UserDefaults操作
function monitorUserDefaults() {var NSUserDefaults = ObjC.classes.NSUserDefaults;// 监控写入操作var setObject = NSUserDefaults["- setObject:forKey:"];Interceptor.attach(setObject.implementation, {onEnter: function(args) {var object = ObjC.Object(args[2]);var key = ObjC.Object(args[3]).toString();console.log("[+] NSUserDefaults setObject:forKey:");console.log(" Key: " + key);console.log(" Value: " + object.toString());}});// 监控读取操作var objectForKey = NSUserDefaults["- objectForKey:"];Interceptor.attach(objectForKey.implementation, {onEnter: function(args) {var key = ObjC.Object(args[2]).toString();this.key = key;console.log("[+] NSUserDefaults objectForKey:");console.log(" Key: " + key);},onLeave: function(retval) {var object = ObjC.Object(retval);console.log(" Value for key '" + this.key + "': " + object);}});
}monitorUserDefaults();
认证流程分析
破解认证流程是逆向工程中常见的任务:
// Frida脚本:模拟登录过程
function bypassAuthentication() {// 假设我们已经找到了认证管理器类var AuthManager = ObjC.classes.AuthManager;// 创建伪造的用户对象var createFakeUser = function() {var User = ObjC.classes.User;var user = User.alloc().init();user.setValue_forKey_("admin", "username");user.setValue_forKey_("YES", "isLoggedIn");user.setValue_forKey_("YES", "isPremium");return user;};// 替换当前用户Interceptor.attach(AuthManager["- currentUser"].implementation, {onLeave: function(retval) {if (retval.isNull()) {console.log("[+] Replacing null user with fake admin user");retval.replace(createFakeUser());}}});// 始终让登录成功Interceptor.attach(AuthManager["- loginWithUsername:password:completion:"].implementation, {onEnter: function(args) {var username = ObjC.Object(args[2]).toString();var password = ObjC.Object(args[3]).toString();var completion = args[4];console.log("[+] Login attempt:");console.log(" Username: " + username);console.log(" Password: " + password);// 拦截回调并强制成功var origBlock = new ObjC.Block(completion);var newBlock = function(success, error) {console.log("[+] Forcing login success");var fakeUser = createFakeUser();origBlock(true, null, fakeUser);};// 替换回调args[4] = newBlock;}});
}bypassAuthentication();
高级动态分析技术
内存dump和分析
从内存中提取敏感信息:
// Frida脚本:内存dump和分析
function dumpAppMemory() {// 获取所有内存范围Process.enumerateRanges('r--').forEach(function(range) {// 查找可能的密码模式var pattern = /password.*?=.*?["'](.*?)["']/i;var data = Memory.readUtf8String(range.base, range.size);var match = data.match(pattern);if (match) {console.log("[+] Potential password found at " + range.base);console.log(" " + match[0]);}});
}// 搜索特定内存区域
function searchMemoryForPattern(pattern) {var ranges = Process.enumerateRanges('r--');for (var i = 0; i < ranges.length; i++) {var range = ranges[i];Memory.scan(range.base, range.size, pattern, {onMatch: function(address, size) {console.log('[+] Pattern found at: ' + address.toString());// 读取周围内存var buf = Memory.readByteArray(address.sub(32), 64);console.log(hexdump(buf, {offset: 0, length: 64, header: true, ansi: true}));},onError: function(reason) {console.log('[!] Memory scan error: ' + reason);},onComplete: function() {console.log('[+] Memory scan complete');}});}
}// 使用示例
// searchMemoryForPattern('12 34 56 78');
方法跟踪和调用栈分析
追踪完整的方法调用链:
// Frida脚本:方法跟踪
function traceClass(targetClass) {var methods = ObjC.classes[targetClass].$ownMethods;methods.forEach(function(method) {var implementation = ObjC.classes[targetClass][method].implementation;Interceptor.attach(implementation, {onEnter: function(args) {var methodName = method;// 打印调用栈console.log("↪ " + targetClass + " " + methodName);console.log(Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\n"));}});});console.log("[+] Tracing " + methods.length + " methods in " + targetClass);
}// 使用示例
// traceClass("AuthManager");
自动化UI交互
自动执行UI操作,测试不同路径:
// Frida脚本:自动化UI交互
function autoLogin() {// 获取当前视图控制器var getTopViewController = function() {var app = ObjC.classes.UIApplication.sharedApplication();var rootVC = app.keyWindow().rootViewController();var topVC = rootVC;while (topVC.presentedViewController()) {topVC = topVC.presentedViewController();}return topVC;};// 查找登录按钮并点击var findAndClickLoginButton = function() {var topVC = getTopViewController();console.log("[+] Top ViewController: " + topVC.$className);// 假设我们知道按钮的标签var view = topVC.view();var subviews = view.subviews();for (var i = 0; i < subviews.count(); i++) {var subview = subviews.objectAtIndex_(i);if (subview.$className === "UIButton") {var title = subview.titleForState_(0); // UIControlStateNormalif (title.toString().toLowerCase().includes("login")) {console.log("[+] Found login button, clicking...");subview.sendActionsForControlEvents_(1 << 6); // UIControlEventTouchUpInsidereturn true;}}}return false;};// 查找文本字段并输入var findTextFieldAndTypeText = function(placeholder, text) {var topVC = getTopViewController();var view = topVC.view();var subviews = view.subviews();var findTextField = function(view, placeholder) {if (view.$className === "UITextField") {var field = view;if (field.placeholder().toString().toLowerCase().includes(placeholder.toLowerCase())) {return field;}}var subviews = view.subviews();for (var i = 0; i < subviews.count(); i++) {var result = findTextField(subviews.objectAtIndex_(i), placeholder);if (result) {return result;}}return null;};var textField = findTextField(view, placeholder);if (textField) {console.log("[+] Found text field with placeholder: " + placeholder);textField.setText_(text);return true;}return false;};// 执行自动登录setTimeout(function() {console.log("[+] Starting automated login...");if (findTextFieldAndTypeText("username", "admin")) {console.log("[+] Username entered");}if (findTextFieldAndTypeText("password", "password123")) {console.log("[+] Password entered");}setTimeout(function() {if (findAndClickLoginButton()) {console.log("[+] Login button clicked");}}, 500);}, 1000);
}// 使用示例
// autoLogin();
反调试检测绕过
许多应用实现了反调试措施,我们需要绕过它们:
// Frida脚本:绕过反调试检测
function bypassJailbreakDetection() {// 常见的越狱检测文件路径var jailbreakPaths = ["/Applications/Cydia.app","/Library/MobileSubstrate/MobileSubstrate.dylib","/bin/bash","/usr/sbin/sshd","/etc/apt"];// Hook NSFileManager的文件存在检查var NSFileManager = ObjC.classes.NSFileManager;Interceptor.attach(NSFileManager["- fileExistsAtPath:"].implementation, {onEnter: function(args) {var path = ObjC.Object(args[2]).toString();this.path = path;// 检查是否是越狱检测路径if (jailbreakPaths.indexOf(path) >= 0) {console.log("[!] Jailbreak detection: " + path);}},onLeave: function(retval) {// 如果检测到越狱文件,返回falseif (jailbreakPaths.indexOf(this.path) >= 0) {console.log("[+] Bypassing jailbreak detection");retval.replace(0x0); // 返回false}}});// Hook fork检测var fork = Module.findExportByName(null, "fork");if (fork) {Interceptor.attach(fork, {onLeave: function(retval) {console.log("[!] Fork detected, returning -1");retval.replace(-1); // 模拟fork失败}});}// 更多反调试检测绕过...
}// 使用示例
// bypassJailbreakDetection();
实战案例:分析支付应用
让我们通过一个完整的实战案例,演示如何对一个假设的支付应用进行动态分析。
1. 准备环境
首先,我们需要设置分析环境:
# 启动Frida服务器
ssh root@device-ip "/usr/sbin/frida-server &"### 1. 准备环境(续)```bash
# 列出设备上安装的应用
frida-ps -Ua# 找到目标应用(假设为"SecurePay")
frida -U SecurePay
2. 基本功能分析
首先,我们编写一个Frida脚本来分析应用的基本结构:
// secure-pay-analysis.js
function analyzeAppStructure() {// 1. 分析应用委托和主要类var app = ObjC.classes.UIApplication.sharedApplication();var delegate = app.delegate();console.log("[+] App Delegate: " + delegate.$className);console.log("[+] App Bundle ID: " + NSBundle.mainBundle().bundleIdentifier().toString());// 2. 列出所有自定义类console.log("\n[+] Custom Classes:");var count = 0;for (var className in ObjC.classes) {if (className.indexOf("SecurePay") !== -1) {console.log(" " + className);count++;}}console.log(" Total: " + count + " classes found");// 3. 分析视图控制器var allViewControllers = [];for (var className in ObjC.classes) {if (className.indexOf("ViewController") !== -1 && className.indexOf("SecurePay") !== -1) {allViewControllers.push(className);}}console.log("\n[+] Found " + allViewControllers.length + " view controllers:");allViewControllers.forEach(function(vc) {console.log(" " + vc);});
}analyzeAppStructure();
使用该脚本的输出,我们可以了解应用的基本结构和主要组件。
3. 追踪支付流程
接下来,我们分析支付流程,追踪从用户输入到网络请求的整个过程:
// secure-pay-flow.js
function tracePaymentFlow() {// 1. 监控支付按钮点击var PaymentViewController = ObjC.classes.SecurePayPaymentViewController;var paymentButtonMethod = PaymentViewController["- paymentButtonTapped:"];Interceptor.attach(paymentButtonMethod.implementation, {onEnter: function(args) {console.log("\n[+] Payment button tapped");// 打印当前视图控制器的状态var self = ObjC.Object(args[0]);var amountField = self.amountTextField();var cardField = self.cardNumberTextField();console.log(" Amount: " + amountField.text());console.log(" Card Number: " + cardField.text());}});// 2. 监控支付处理方法var PaymentManager = ObjC.classes.SecurePayPaymentManager;var processPaymentMethod = PaymentManager["- processPaymentWithCard:amount:completion:"];Interceptor.attach(processPaymentMethod.implementation, {onEnter: function(args) {var cardInfo = ObjC.Object(args[2]);var amount = ObjC.Object(args[3]).doubleValue();console.log("\n[+] Processing payment:");console.log(" Card: " + cardInfo.cardNumber());console.log(" Expiry: " + cardInfo.expiryDate());console.log(" CVV: " + cardInfo.securityCode());console.log(" Amount: " + amount);// 保存回调块以便稍后使用this.completionBlock = args[4];}});// 3. 监控加密方法var CryptoService = ObjC.classes.SecurePayCryptoService;var encryptMethod = CryptoService["- encryptCardData:withKey:"];Interceptor.attach(encryptMethod.implementation, {onEnter: function(args) {var cardData = ObjC.Object(args[2]);var encryptionKey = ObjC.Object(args[3]);console.log("\n[+] Encrypting card data:");console.log(" Card data: " + cardData.toString());console.log(" Encryption key: " + encryptionKey.toString());},onLeave: function(retval) {var encryptedData = ObjC.Object(retval);console.log(" Encrypted result: " + encryptedData.toString());}});// 4. 监控网络请求var NetworkService = ObjC.classes.SecurePayNetworkService;var sendRequestMethod = NetworkService["- sendPaymentRequest:completion:"];Interceptor.attach(sendRequestMethod.implementation, {onEnter: function(args) {var request = ObjC.Object(args[2]);console.log("\n[+] Sending payment request:");console.log(" URL: " + request.URL().absoluteString());console.log(" Method: " + request.HTTPMethod());var headers = request.allHTTPHeaderFields();var headerKeys = headers.allKeys();console.log(" Headers:");for (var i = 0; i < headerKeys.count(); i++) {var key = headerKeys.objectAtIndex_(i);var value = headers.objectForKey_(key);console.log(" " + key + ": " + value);}var body = request.HTTPBody();if (body) {var bodyString = ObjC.classes.NSString.alloc().initWithData_encoding_(body, 4).toString();console.log(" Body: " + bodyString);}}});console.log("[+] Payment flow tracing enabled. Perform a payment transaction...");
}tracePaymentFlow();
4. 修改支付行为
分析完成后,我们可以修改应用行为,例如篡改支付金额:
// secure-pay-modify.js
function modifyPaymentProcess() {// 修改支付金额var PaymentManager = ObjC.classes.SecurePayPaymentManager;var processPaymentMethod = PaymentManager["- processPaymentWithCard:amount:completion:"];Interceptor.attach(processPaymentMethod.implementation, {onEnter: function(args) {var cardInfo = ObjC.Object(args[2]);var originalAmount = ObjC.Object(args[3]).doubleValue();console.log("\n[+] Original payment amount: " + originalAmount);// 创建修改后的金额对象(例如,改为0.01)var NSNumber = ObjC.classes.NSNumber;var modifiedAmount = NSNumber.numberWithDouble_(0.01);console.log("[+] Modified payment amount: 0.01");// 替换参数args[3] = modifiedAmount;}});// 强制支付成功var NetworkService = ObjC.classes.SecurePayNetworkService;var handleResponseMethod = NetworkService["- handlePaymentResponse:error:forRequest:completion:"];Interceptor.attach(handleResponseMethod.implementation, {onEnter: function(args) {var response = ObjC.Object(args[2]);var error = ObjC.Object(args[3]);var completion = args[5];console.log("\n[+] Payment response received:");if (!response.isNull()) {console.log(" Response: " + response.toString());}if (!error.isNull()) {console.log(" Error: " + error.toString());// 创建成功响应var successResponse = ObjC.classes.NSDictionary.dictionaryWithObjectsAndKeys_("approved", "status","12345", "transactionId","Transaction approved", "message",null);// 替换参数,移除错误args[2] = successResponse;args[3] = ObjC.classes.NSNull.null();console.log("[+] Forced successful payment response");}}});console.log("[+] Payment modification active. Any payment will cost only $0.01 and always succeed.");
}modifyPaymentProcess();
5. 绕过生物识别认证
如果应用使用TouchID/FaceID进行支付确认,我们可以绕过它:
// secure-pay-biometric-bypass.js
function bypassBiometricAuthentication() {// 监控LocalAuthentication框架var LAContext = ObjC.classes.LAContext;var evaluatePolicy = LAContext["- evaluatePolicy:localizedReason:reply:"];Interceptor.attach(evaluatePolicy.implementation, {onEnter: function(args) {var reason = ObjC.Object(args[3]);console.log("\n[+] Biometric authentication requested:");console.log(" Reason: " + reason.toString());// 保存原始回调var originalBlock = new ObjC.Block(args[4]);// 创建新回调,始终返回成功var replacementBlock = function(success, error) {console.log("[+] Bypassing biometric authentication, forcing success");originalBlock(true, null);};// 替换回调参数args[4] = replacementBlock;}});console.log("[+] Biometric authentication bypass active");
}bypassBiometricAuthentication();
6. 分析结果与安全问题
通过上述分析,我们可能发现以下安全问题:
- 明文存储敏感信息:应用可能在内存中明文存储信用卡信息
- 弱加密:使用可预测或硬编码的加密密钥
- 缺乏完整性检查:没有验证支付请求是否被篡改
- 服务器端验证不足:仅依赖客户端验证金额和交易信息
- 生物认证绕过:生物认证可以被客户端修改绕过
动态分析的最佳实践
组织和记录
良好的组织和记录对动态分析至关重要:
-
创建分析计划:
- 确定分析目标和范围
- 列出要研究的关键功能
- 准备测试数据和场景
-
详细记录:
- 记录所有发现和观察结果
- 保存重要的代码片段和修改
- 截图记录关键UI状态和流程
-
标准化工作流:
- 建立一套一致的分析步骤
- 创建可重用的脚本模板
- 维护工具和环境配置
组合静态和动态分析
静态和动态分析互为补充:
-
先静态后动态:
- 先通过静态分析了解应用结构
- 确定关键函数和逻辑点
- 使用动态分析验证假设和理论
-
迭代分析:
- 动态发现影响静态理解
- 静态分析帮助定位新的动态分析目标
- 不断迭代优化理解
-
结果交叉验证:
- 使用静态分析验证动态观察
- 使用动态结果指导静态分析深度
法律和伦理考虑
在进行应用分析时,务必注意法律和伦理界限:
-
遵守法律:
- 只分析自己有权分析的应用
- 不对生产环境的应用进行测试
- 不发布破解工具或侵犯知识产权的内容
-
负责任披露:
- 发现安全漏洞时,遵循负责任的披露原则
- 私下联系开发者,给予修复时间
- 明确披露目的是提高安全性
-
教育目的:
- 将分析结果用于教育和学习
- 不利用漏洞进行非法活动
- 帮助提高整体移动应用安全性
总结
动态分析和Hook技术是iOS逆向工程中强大的武器,它们允许我们在应用运行时观察、修改和控制其行为。通过本文介绍的工具和技术,您可以:
- 设置动态分析环境
- 使用Frida、cycript等工具进行运行时分析
- 监控和修改应用的关键行为
- 绕过安全控制和限制
- 了解应用的真实运行逻辑
掌握这些技术需要时间和实践,但它们提供了静态分析无法企及的洞察力。通过综合运用静态和动态分析,您可以全面理解iOS应用的内部工作原理,发现安全漏洞,或者满足合法的功能修改需求。
在下一篇文章中,我们将深入探讨iOS应用保护技术和防护措施,包括代码混淆、反调试、完整性校验等,以及如何应对这些保护机制。
版权声明:
本文仅供学术研究和技术探讨使用。在实践中应用本文技术时,请遵守相关法律法规和道德准则。作者不对读者使用本文内容产生的任何后果负责。未经授权,请勿转载或用于商业用途。
作者:自学不成才
本文为iOS逆向工程专栏的第8篇文章,版权所有,未经许可请勿转载。