iOS逆向工程专栏 第8篇:iOS应用动态分析与Hook技术

iOS逆向工程专栏 第8篇:iOS应用动态分析与Hook技术

作者:自学不成才

在前两篇文章中,我们深入探讨了Mach-O文件格式和静态分析方法。尽管静态分析能够提供应用结构的全景视图,但仍有许多问题无法仅通过静态分析解决,例如运行时行为、动态加载的代码和复杂的加密逻辑。这就是动态分析发挥作用的地方。本文将详细介绍iOS应用的动态分析技术和Hook方法,帮助您在应用运行时观察和修改其行为。

动态分析与静态分析的区别

动态分析的优势

相比静态分析,动态分析具有以下优势:

  1. 观察真实行为:直接观察应用在运行时的实际行为
  2. 绕过混淆:不受代码混淆和加密的影响
  3. 获取运行时状态:观察内存状态、对象值和执行流程
  4. 交互式分析:可以实时修改参数和返回值,测试不同场景
  5. 处理动态特性:能够分析反射、动态加载和JIT编译等技术

动态分析的局限性

动态分析也有其局限性:

  1. 覆盖率问题:只能分析执行到的代码
  2. 环境依赖:需要特定环境和条件
  3. 抗调试机制:应用可能检测调试行为并改变自身行为
  4. 操作繁琐:设置环境和工具较为复杂
  5. 时序性:时序相关的问题可能难以复现

动态分析环境搭建

越狱设备准备

最理想的动态分析环境是越狱的iOS设备:

  1. 设备选择

    • 推荐使用较旧的设备,如iPhone 8/X系列(A11芯片)
    • 确保设备可以越狱(通常iOS 14.3-14.8较为稳定)
  2. 越狱工具

    • Checkra1n:适用于A7-A11设备
    • Unc0ver:支持多个iOS版本
    • Taurine:iOS 14专用越狱工具
  3. 基本设置

    # 安装OpenSSH(越狱后必装)
    apt-get update
    apt-get install openssh
    
    # 修改默认密码(默认为alpine)
    passwd root
    passwd mobile
    

非越狱环境选项

如果无法获得越狱设备,可以考虑以下替代方案:

  1. 开发者模式

    • 需要Apple开发者账号
    • 使用Xcode和开发证书重签名并注入调试代码
  2. 模拟器

    • 使用Xcode iOS模拟器
    • 功能有限,无法完全模拟真实设备
  3. 特殊工具

    • 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:);
    
    // 执行Hook
    MSHookMessageEx(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代理服务器,用于监控网络请求:

  1. 设置步骤

    • 在电脑上安装并运行Charles
    • 配置iOS设备使用Charles作为HTTP代理
    • 安装Charles SSL证书以监控HTTPS流量
  2. 主要功能

    • 查看请求和响应的完整内容
    • 修改请求和响应数据
    • 保存会话以供后续分析
    • 断点调试网络请求
Wireshark

Wireshark是深度网络分析工具:

  1. 使用方法

    • 通过USB网络接口捕获iOS设备流量
    • 使用适当的过滤器隔离目标应用流量
    • 分析底层协议和加密握手
  2. 优势

    • 低级协议分析能力
    • 可以观察加密前的原始数据

动态分析实战技巧

应用启动分析

观察应用启动过程是理解应用架构的好方法:

// 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() {
    // 监控CommonCrypto
    var CommonCrypto = Module.findExportByName(null, "CCCrypt");
    if (CommonCrypto) {
        Interceptor.attach(CommonCrypto, {
            onEnter: function(args) {
                // CCCrypt参数解析
                var op = args[0].toInt32(); // 0 = kCCEncrypt, 1 = kCCDecrypt
                var 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); // UIControlStateNormal
                if (title.toString().toLowerCase().includes("login")) {
                    console.log("[+] Found login button, clicking...");
                    subview.sendActionsForControlEvents_(1 << 6); // UIControlEventTouchUpInside
                    return 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) {
            // 如果检测到越狱文件,返回false
            if (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. 分析结果与安全问题

通过上述分析,我们可能发现以下安全问题:

  1. 明文存储敏感信息:应用可能在内存中明文存储信用卡信息
  2. 弱加密:使用可预测或硬编码的加密密钥
  3. 缺乏完整性检查:没有验证支付请求是否被篡改
  4. 服务器端验证不足:仅依赖客户端验证金额和交易信息
  5. 生物认证绕过:生物认证可以被客户端修改绕过

动态分析的最佳实践

组织和记录

良好的组织和记录对动态分析至关重要:

  1. 创建分析计划

    • 确定分析目标和范围
    • 列出要研究的关键功能
    • 准备测试数据和场景
  2. 详细记录

    • 记录所有发现和观察结果
    • 保存重要的代码片段和修改
    • 截图记录关键UI状态和流程
  3. 标准化工作流

    • 建立一套一致的分析步骤
    • 创建可重用的脚本模板
    • 维护工具和环境配置

组合静态和动态分析

静态和动态分析互为补充:

  1. 先静态后动态

    • 先通过静态分析了解应用结构
    • 确定关键函数和逻辑点
    • 使用动态分析验证假设和理论
  2. 迭代分析

    • 动态发现影响静态理解
    • 静态分析帮助定位新的动态分析目标
    • 不断迭代优化理解
  3. 结果交叉验证

    • 使用静态分析验证动态观察
    • 使用动态结果指导静态分析深度

法律和伦理考虑

在进行应用分析时,务必注意法律和伦理界限:

  1. 遵守法律

    • 只分析自己有权分析的应用
    • 不对生产环境的应用进行测试
    • 不发布破解工具或侵犯知识产权的内容
  2. 负责任披露

    • 发现安全漏洞时,遵循负责任的披露原则
    • 私下联系开发者,给予修复时间
    • 明确披露目的是提高安全性
  3. 教育目的

    • 将分析结果用于教育和学习
    • 不利用漏洞进行非法活动
    • 帮助提高整体移动应用安全性

总结

动态分析和Hook技术是iOS逆向工程中强大的武器,它们允许我们在应用运行时观察、修改和控制其行为。通过本文介绍的工具和技术,您可以:

  • 设置动态分析环境
  • 使用Frida、cycript等工具进行运行时分析
  • 监控和修改应用的关键行为
  • 绕过安全控制和限制
  • 了解应用的真实运行逻辑

掌握这些技术需要时间和实践,但它们提供了静态分析无法企及的洞察力。通过综合运用静态和动态分析,您可以全面理解iOS应用的内部工作原理,发现安全漏洞,或者满足合法的功能修改需求。

在下一篇文章中,我们将深入探讨iOS应用保护技术和防护措施,包括代码混淆、反调试、完整性校验等,以及如何应对这些保护机制。


版权声明:
本文仅供学术研究和技术探讨使用。在实践中应用本文技术时,请遵守相关法律法规和道德准则。作者不对读者使用本文内容产生的任何后果负责。未经授权,请勿转载或用于商业用途。

作者:自学不成才
本文为iOS逆向工程专栏的第8篇文章,版权所有,未经许可请勿转载。

你可能感兴趣的:(iOS逆向工程专栏,-,揭秘苹果的封闭花园,ios,cocoa,macos)