前言:现在全民买基的情况下,女票也买了一些,对于买基新手来说,总是想打开支付宝看看到底今天是赚是赔,女票说支付宝收益第二天才显示太慢了,要是能打开直接看到收益就好了,作为一个合格的程序员,怎么能不满足这小小的需求?写篇记录一下过程。
工具:
/var/mobile/Documents/CrackerXI/
otool -l 可执行文件路径 | grep crypt
git地址:https://github.com/AloneMonkey/MonkeyDev
sudo git clone --recursive https://github.com/theos/theos.git /opt/theos
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 -H [.app文件的路径] -o [输出文件夹路径]
页面是一个WKWebView,我们只需要获取H5页面请求后的数据就可以了。然后我们用safari开发模式看一下页面,结果在控制台看到了接口打印数据,正是我们需要的数据,线程回调结果:
NSOperation结合NSURLConnection,搜索头文件,找到一个 DTURLRequestOperation文件,继承了NSURLConnectionDataDelegate, NSURLProtocolClient, NSURLSessionDataDelegate协议,还包含了request和response,一看就知道是和wkwebview请求有关,尝试拦截一下。
看到可以拦截到WKWebview的请求,下一步从众多请求中找出我们需要的请求数据就可以了。
线程名称包含在请求头中,根据线程名筛选需要的请求结果:
// 自选关注的基金信息
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