iOS逆向-支付宝基金之统计实时收益

前言:现在全民买基的情况下,女票也买了一些,对于买基新手来说,总是想打开支付宝看看到底今天是赚是赔,女票说支付宝收益第二天才显示太慢了,要是能打开直接看到收益就好了,作为一个合格的程序员,怎么能不满足这小小的需求?写篇记录一下过程。

效果图:
iOS逆向-支付宝基金之统计实时收益_第1张图片

工具:

  1. CrackerXI+
  2. 爱思助手
  3. Class-dump
  4. MonkeyDev

一、砸壳

(一)越狱机安装CrackerXI+
  1. 在cydia 中添加 源地址 http://cydia.iphonecake.com 或者 http://apt.cydiami.com ,添加成功后,在cydia中搜索CrackerXI+并安装。
  2. 打开CrackerXI+,在settings中设置CrackerXI Hook为enable
    iOS逆向-支付宝基金之统计实时收益_第2张图片

(二)砸壳

  1. 先在App store下载安装蚂蚁财富,然后打开CrackerXI+选中蚂蚁财富,然后选择 YES,FULL IPA 开始砸壳
  2. 砸壳完毕会显示砸壳后IPA的路径的路径/var/mobile/Documents/CrackerXI/
  3. 打开爱思助手或者用ssh链接都可以,按照路径导出ipa包就可以了。

iOS逆向-支付宝基金之统计实时收益_第3张图片
终端输入以下命令 可以查看有么有砸成功:

otool -l 可执行文件路径 | grep crypt

iOS逆向-支付宝基金之统计实时收益_第4张图片
cryptid 0 砸壳成功

二、安装MonkeyDev

git地址:https://github.com/AloneMonkey/MonkeyDev

准备

  1. 安装最新的theos
	sudo git clone --recursive https://github.com/theos/theos.git /opt/theos
  1. 安装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)"

安装成功效果图:
iOS逆向-支付宝基金之统计实时收益_第5张图片

三、逆向分析

(一)Class-dump导出头文件

class-dump -H [.app文件的路径] -o [输出文件夹路径]

导出如下:
iOS逆向-支付宝基金之统计实时收益_第6张图片

(二)新建monkeyDev工程

把IPA放入工程指定目录iOS逆向-支付宝基金之统计实时收益_第7张图片
运行程序分析界面:

iOS逆向-支付宝基金之统计实时收益_第8张图片

页面是一个WKWebView,我们只需要获取H5页面请求后的数据就可以了。然后我们用safari开发模式看一下页面,结果在控制台看到了接口打印数据,正是我们需要的数据,线程回调结果:

iOS逆向-支付宝基金之统计实时收益_第9张图片
NSOperation结合NSURLConnection,搜索头文件,找到一个 DTURLRequestOperation文件,继承了NSURLConnectionDataDelegate, NSURLProtocolClient, NSURLSessionDataDelegate协议,还包含了request和response,一看就知道是和wkwebview请求有关,尝试拦截一下。
iOS逆向-支付宝基金之统计实时收益_第10张图片
iOS逆向-支付宝基金之统计实时收益_第11张图片
看到可以拦截到WKWebview的请求,下一步从众多请求中找出我们需要的请求数据就可以了。

iOS逆向-支付宝基金之统计实时收益_第12张图片

四、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

你可能感兴趣的:(iOS,基金,iOS,逆向,支付宝,蚂蚁财富)