iOS逆向-支付宝基金查看实时收益变动
前言:现在全民买基的情况下,女票也买了一些,对于买基新手来说,总是想打开支付宝看看到底今天是赚是赔,女票说支付宝收益第二天才显示太慢了,要是能打开直接看到收益就好了,作为一个合格的程序员,怎么能不满足这小小的需求?写篇记录一下过程。
效果图:
工具:
- CrackerXI+
- 爱思助手
- Class-dump
- MonkeyDev
一、砸壳
(一)越狱机安装CrackerXI+
- 在cydia 中添加 源地址 http://cydia.iphonecake.com 或者 http://apt.cydiami.com ,添加成功后,在cydia中搜索CrackerXI+并安装。
-
打开CrackerXI+,在settings中设置CrackerXI Hook为enable
(二)砸壳
- 先在App store下载安装蚂蚁财富,然后打开CrackerXI+选中蚂蚁财富,然后选择 YES,FULL IPA 开始砸壳
-
砸壳完毕会显示砸壳后IPA的路径的路径
/var/mobile/Documents/CrackerXI/
- 打开爱思助手或者用ssh链接都可以,按照路径导出ipa包就可以了。
终端输入以下命令 可以查看有么有砸成功:
otool -l 可执行文件路径 | grep crypt
cryptid 0 砸壳成功
二、安装MonkeyDev
git地址:https://github.com/AloneMonkey/MonkeyDev
准备
- 安装最新的theos
sudo git clone --recursive https://github.com/theos/theos.git /opt/theos
- 安装ldid(如安装theos过程安装了ldid,跳过)
brew install ldid
安装
你可以通过以下命令选择指定的Xcode进行安装:
sudo xcode-select -s /Applications/Xcode-beta.app
默认安装的Xcode为:
xcode-select -p
执行安装命令:
sudo /bin/sh -c "$(curl -fsSL https://raw.githubusercontent.com/AloneMonkey/MonkeyDev/master/bin/md-install)"
卸载
sudo /bin/sh -c "$(curl -fsSL https://raw.githubusercontent.com/AloneMonkey/MonkeyDev/master/bin/md-uninstall)"
更新
sudo /bin/sh -c "$(curl -fsSL https://raw.githubusercontent.com/AloneMonkey/MonkeyDev/master/bin/md-update)"
安装成功效果图:
三、逆向分析
(一)Class-dump导出头文件
class-dump -H [.app文件的路径] -o [输出文件夹路径]
导出如下:
(二)新建monkeyDev工程
把IPA放入工程指定目录
运行程序分析界面:
页面是一个WKWebView,我们只需要获取H5页面请求后的数据就可以了。然后我们用safari开发模式看一下页面,结果在控制台看到了接口打印数据,正是我们需要的数据,线程回调结果:
NSOperation结合NSURLConnection,搜索头文件,找到一个 DTURLRequestOperation文件,继承了NSURLConnectionDataDelegate, NSURLProtocolClient, NSURLSessionDataDelegate协议,还包含了request和response,一看就知道是和wkwebview请求有关,尝试拦截一下。
看到可以拦截到WKWebview的请求,下一步从众多请求中找出我们需要的请求数据就可以了。
四、Hook
线程名称包含在请求头中,根据线程名筛选需要的请求结果:
// 自选关注的基金信息
com.alipay.wealthbffweb.fund.optional.queryV3
// 持有的基金信息
com.alipay.wealthbffweb.fund.commonAsset.queryAssetDetail
筛选要求获取的请求结果
// 获取请求返回数据 +(void)hookWithOperation:(DTRpcOperation *)operation{ @try { NSDictionary *allHTTPHeaderFields = operation.request.allHTTPHeaderFields; NSString *type = allHTTPHeaderFields[@"Operation-Type"]; if([type isEqualToString: @"com.alipay.wealthbffweb.fund.optional.queryV3"]){ // 计算收益 [self calculateIncomeWithOperation:operation]; }else if([type isEqualToString: @"com.alipay.wealthbffweb.fund.commonAsset.queryAssetDetail"]){ // 获取份额 [self getFundSharesWithOperation:operation]; } } @catch (NSException *exception) { NSLog(@"error:%@", exception); } } #pragma mark - holdingMap // 份额 本地保存路径 + (NSString *)FundSharesDicPath { static NSString *path; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; path = [documentsDirectory stringByAppendingPathComponent:@"FundSharesDic.plist"]; }); return path; } + (void)loadFundSharesDic { NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithContentsOfFile:self.FundSharesDicPath]; if (!dic) { dic = NSMutableDictionary.dictionary; } fundSharesDic = dic; } /// 读取本地保存的份额 + (NSString *)getFundSharesWithFundCode:(NSString *)fundCode { //fundCode 基金代码 if (fundSharesDic == nil) { [self loadFundSharesDic]; } return fundSharesDic[fundCode]; } #pragma mark - handle // 计算收益 + (void)calculateIncomeWithOperation:(DTRpcOperation *)operation { NSDictionary *json = [NSJSONSerialization JSONObjectWithData:operation.responseData options:NSJSONReadingMutableLeaves error:nil]; if (json && [json isKindOfClass:NSDictionary.class]) { // 解析数据 NSMutableDictionary *m_json = [json mutableCopy]; NSMutableDictionary *result = [m_json[@"result"] mutableCopy]; NSArray *optionalList = result[@"optionalList"]; NSMutableArray *modifyList = NSMutableArray.array; NSMutableArray *incomes = NSMutableArray.array; for (NSDictionary *obj in optionalList) { NSMutableDictionary *model = [obj mutableCopy]; BOOL holdingPosition = [model[@"holdingPosition"] boolValue]; if (holdingPosition) { // 获取份额 NSString *value = [self getFundSharesWithFundCode:model[@"fundCode"]]; // 获取时间 NSString *netValueReportDate = model[@"netValueReportDate"]; NSString *estimateDate = model[@"estimateDate"]; // 有效份额 才参与统计 if (value.doubleValue > 0) { NSMutableArray *contentTags = NSMutableArray.array; // 预估时间不等于网络净值时间时 统计收益 if ([netValueReportDate isKindOfClass:NSString.class] && [estimateDate isKindOfClass:NSString.class]) { NSString *netValue = model[@"netValue"]; if ( ![netValueReportDate isEqualToString:estimateDate]) { NSString *estimateNetValue = model[@"estimateNetValue"]; // 没有预估时 忽略收益 if (estimateNetValue.doubleValue && netValue.doubleValue) { double income = (estimateNetValue.doubleValue - netValue.doubleValue) * value.doubleValue; [incomes addObject:@(income)]; [contentTags addObject:@{ @"visible": @YES, @"text": [NSString stringWithFormat:@"收益:%0.2f", income], @"type": @"BULL_FUND", }]; } } else { NSString *dayOfGrowth = model[@"dayOfGrowth"]; if (dayOfGrowth.length) { NSString *modifyWorth = [NSString stringWithFormat:@"%0.4f",netValue.doubleValue / (1 + dayOfGrowth.doubleValue)]; double income = (netValue.doubleValue - modifyWorth.doubleValue) * value.doubleValue; [incomes addObject:@(income)]; [contentTags addObject:@{ @"visible": @YES, @"text": [NSString stringWithFormat:@"净收:%0.2f", income], @"type": @"BULL_FUND", }]; } } } if (!contentTags.count) { [contentTags addObject:@{ @"visible": @YES, @"text": [NSString stringWithFormat:@"份额:%@", value], @"type": @"BULL_FUND", }]; } model[@"contentTags"] = contentTags; } else { model[@"contentTags"] = @[ @{ @"visible": @YES, @"text": @"点击读取份额", @"type": @"BULL_FUND", }, ]; } } [modifyList addObject:model]; } result[@"optionalList"] = modifyList; m_json[@"result"] = result; NSData *jsonData = [NSJSONSerialization dataWithJSONObject:m_json options:NSJSONWritingPrettyPrinted error:nil]; operation.responseData = jsonData; if (incomes.count) { NSDecimalNumber *sum = [incomes valueForKeyPath:@"@sum.doubleValue"]; NSString *desc = [NSString stringWithFormat:@"有效统计%ld只,当前总收益%0.2f", incomes.count, sum.doubleValue]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ UILabel *label = UILabel.new; UIWindow *keyWindow = UIApplication.sharedApplication.keyWindow; label.frame = CGRectMake(20, CGRectGetMaxY(keyWindow.frame) - 118, CGRectGetMaxX(keyWindow.frame) - 40, 40); label.layer.cornerRadius = 5.f; label.layer.masksToBounds = YES; label.backgroundColor = [UIColor redColor]; label.text = desc; label.textAlignment = NSTextAlignmentCenter; label.textColor = UIColor.whiteColor; label.font = [UIFont systemFontOfSize:16]; [keyWindow addSubview:label]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int6![在这里插入图片描述](https://img-blog.csdnimg.cn/20200807142125730.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NjYwMjc3Mw==,size_16,color_FFFFFF,t_70) 4_t)(2.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [label removeFromSuperview]; }); }); } } } // 获取份额 + (void)getFundSharesWithOperation:(DTRpcOperation *)operation { NSDictionary *json = [NSJSONSerialization JSONObjectWithData:operation.responseData options:NSJSONReadingMutableLeaves error:nil]; if (json && [json isKindOfClass:NSDictionary.class]) { // 解析数据 NSMutableDictionary *result = json[@"result"]; NSString *availableShare = result[@"availableShare"]; // 设置本地记录份额 if (availableShare.doubleValue > 0) { NSString *fundCode = result[@"fundCode"]; if (fundSharesDic == nil) { [self loadFundSharesDic]; } fundSharesDic[fundCode] = availableShare; [fundSharesDic writeToFile:self.FundSharesDicPath atomically:YES]; } } }
最终效果:
最后放上项目地址:
https://github.com/FORMAT-qi/hookAntWealth
砸过壳的app下载地址:
链接: https://pan.baidu.com/s/1UUQhm9ymM74d0UtbnqrEew
密码: n49h
@end
本文地址:https://blog.csdn.net/weixin_46602773/article/details/107831917