一个包任意切换不同测试环境是一直想做的。

以前开发任务少,打包少,就感觉和自己离的远。

这几天重新提起,于是用了三天时间,分析思路与实现。

记录一下这份喜悦。

首先mpaas客户端切换环境需要更改6个位置的配置代码文件:无线保镖图片,info.pllist,配置文件(纯文本),还有3个代码文件。

1.info.pllist 通过重写系统的NSBundle类的infoDictionary属性的set方法实现。这个在之前的博客中已经写过不再复述。链接:更改bundleid后蚂蚁金服MPSafeKeyboard的安全键盘不显示的解决办法

2.配置文件(纯文本)通过

方法一:fishhook系统open方法(实践表明只有第一次安装客户端才会走open)

方法二:hook系统NSBundle的- [pathForResource:ofType:],然后替换为相应文件

3.代码文件通过全局变量或沙盒标记,直接判断

4.无线保镖图片通过

方法一:对系统NSData由file创建方法

方法二:UIimage由file创建方法,

方法三:NSBundle路径获取方法,

方法四:open方法

方法五:hopper搜索图片名字

方法六:find.命令查找图片名字,确定其所在库

方法七:根据后来发现mpaas提供了支持不同环境读取不同图片的方法,得以实现。

5.切换环境后还要对上个环境在沙盒的缓存数据进行清除,实验表明切换后需要删掉沙盒 Preferences文件夹内容,即仅删除[NSUserDefaults standardUserDefaults]下所有内容即可

以下是详细过程

上来先找图片和配置文件的open方法,结果控制台把内容都打印完了也没走到我的open方法,但其他文件会走。那就是fishhook晚了,由main方法里改到了第一个执行的load方法里。知识点:每个类都有load,哪个类的load先走?

答案是:Build Phases的Compile Sources文件上下顺序。

放到这个load里面,果然我的代码比其他任何代码都先走,但还是打印完其他信息才走我的open,而且只有第一次卸载安装客户端才会。但是打印信息肯定用NSLog,直接替换为我的MyNSLog,这下找到了打印第一个配置信息的类。

竟然是写到了定位方法里。说明客户端在load阶段就将所有配置信息都完成了。

然后看看第二个打印

这个打印出了配置文件的内容,然后hopper一下这个方法,果然直接用的pathForResource

void +[MPLiteSettingService initialize](void * self, void * _cmd) {var_-48 = self;if ([self checkCoverInstallation] != 0x0) {rbx = [[NSUserDefaults standardUserDefaults] retain];[rbx removeObjectForKey:@"mPaaSConfig_meta"];[rbx release];rbx = [[NSUserDefaults standardUserDefaults] retain];[rbx synchronize];[rbx release];}rbx = [[NSBundle mainBundle] retain];r14 = [[rbx pathForResource:@"meta" ofType:@"config"] retain];[rbx release];rdx = r14;[MPLiteSettingService generateMetaConfigWithPath:rdx type:@"meta"];rbx = [[MPaaSInterface sharedInstance] retain];r13 = [rbx enableSettingService];[rbx release];if (r13 != 0x0) {var_-112 = r14;r14 = [[NSBundle mainBundle] retain];rbx = [[r14 pathForResource:@"Settings" ofType:@"bundle"] retain];[r14 release];var_-56 = rbx;if (rbx != 0x0) {rcx = @"bundle";rbx = [[NSUserDefaults standardUserDefaults] retain];rdx = @"mPaasSettingServiceRegistered";r14 = [[rbx objectForKey:rdx, rcx] retain];[rbx release];if (r14 == 0x0) {rbx = [[NSUserDefaults standardUserDefaults] retain];[rbx setObject:@"Registered" forKey:@"mPaasSettingServiceRegistered"];[rbx release];[var_-48 registerDefaultsFromSettingsWithPlist:@"Root.plist" forBundle:var_-56];rdx = @"Service.plist";rcx = var_-56;[var_-48 registerDefaultsFromSettingsWithPlist:rdx forBundle:rcx];rbx = [[NSUserDefaults standardUserDefaults] retain];[rbx synchronize];[rbx release];}rbx = [[NSUserDefaults standardUserDefaults] retain];rdx = @"kMPSelectedEnvironment";r13 = [[rbx objectForKey:rdx, rcx] retain];[rbx release];rsi = @selector(length);var_-48 = r13;rbx = _objc_msgSend;if (_objc_msgSend(r13, rsi, rdx, rcx) != 0x0) {var_-64 = r14;if ([var_-48 isEqualToString:@"Customizing"] != 0x0) {r15 = rbx;rax = (r15)(@class(NSMutableDictionary), @selector(alloc), @"Customizing");rax = (r15)(rax, @selector(init), @"Customizing");rdi = *__settings;*__settings = rax;[rdi release];rbx = [(r15)(@class(NSUserDefaults), @selector(standardUserDefaults), @"Customizing") retain];r14 = [(r15)(rbx, @selector(objectForKey:), @"rpcGW") retain];[rbx release];var_-104 = r14;r12 = @"";if (r14 == 0x0) {r12 = @"";r14 = r12;}(r15)(*__settings, @selector(setObject:forKey:), r14);rbx = [(r15)(@class(NSUserDefaults), @selector(standardUserDefaults), r14) retain];r14 = [(r15)(rbx, @selector(objectForKey:), @"logGW") retain];[rbx release];var_-96 = r14;if (r14 == 0x0) {r12 = @"";r14 = r12;}(r15)(*__settings, @selector(setObject:forKey:), r14, @"logGW");rbx = [(r15)(@class(NSUserDefaults), @selector(standardUserDefaults), r14, @"logGW") retain];r14 = [(r15)(rbx, @selector(objectForKey:), @"mpaasapi", @"logGW") retain];[rbx release];var_-88 = r14;if (r14 == 0x0) {r12 = @"";r14 = r12;}(r15)(*__settings, @selector(setObject:forKey:), r14, @"mpaasapi");rbx = [(r15)(@class(NSUserDefaults), @selector(standardUserDefaults), r14, @"mpaasapi") retain];r14 = [(r15)(rbx, @selector(objectForKey:), @"syncserver", @"mpaasapi") retain];[rbx release];var_-80 = r14;if (r14 == 0x0) {r12 = @"";r14 = r12;}(r15)(*__settings, @selector(setObject:forKey:), r14, @"syncserver");rbx = [(r15)(@class(NSUserDefaults), @selector(standardUserDefaults), r14, @"syncserver") retain];r14 = [(r15)(rbx, @selector(objectForKey:), @"syncport", @"syncserver") retain];[rbx release];var_-72 = r14;rdx = r14;if (r14 == 0x0) {r12 = @"";rdx = r12;}(r15)(*__settings, @selector(setObject:forKey:), rdx, @"syncport");rbx = [(r15)(@class(NSUserDefaults), @selector(standardUserDefaults), rdx, @"syncport") retain];r13 = [(r15)(rbx, @selector(objectForKey:), @"appId", @"syncport") retain];[rbx release];rdx = r13;if (r13 == 0x0) {r12 = @"";rdx = r12;}(r15)(*__settings, @selector(setObject:forKey:), rdx, @"appId");rbx = [(r15)(@class(NSUserDefaults), @selector(standardUserDefaults), rdx, @"appId") retain];r14 = [(r15)(rbx, @selector(objectForKey:), @"workspaceId", @"appId") retain];[rbx release];if (r14 != 0x0) {r12 = r14;}rsi = @selector(setObject:forKey:);rdx = r12;rcx = @"workspaceId";(r15)(*__settings, rsi, rdx, rcx);[r14 release];[r13 release];[var_-72 release];[var_-80 release];[var_-88 release];[var_-96 release];rdi = var_-104;}else {r15 = rbx;r14 = [(r15)(@class(NSBundle), @selector(bundleWithPath:), var_-56) retain];rbx = [(r15)(r14, @selector(pathForResource:ofType:), var_-48, @"config") retain];[r14 release];rsi = @selector(generateMetaConfigWithPath:type:);rdx = rbx;rcx = var_-48;(r15)(@class(MPLiteSettingService), rsi, rdx, rcx);rdi = rbx;}[rdi release];r14 = var_-64;}[var_-48 release];[r14 release];}[var_-56 release];r14 = var_-112;}NSLog(@"Setting service with values: %@", *__settings);[r14 release];return;
}

然后对这个pathForResource进行hook.创建新的分类NSBundle+YYY放到Build Phases的Compile Sources最上面。发现还是没拦截住,原因应该是NSBundle的分类按照主类所在的顺序执行,因此交换方法写在上面那个load里,但是这里。。。

有新发现。

交换后的方法还在分类写,执行交换的方法在上面load写,竟然没有问题。配置文件获取成功。

还有一张图片没找到

试了很多办法

然后记得以前find. 命令搜到过 图片名字就在那个库里,hopper就没有

在对整个工程find 搜matches竟然都没有。直接搜名字

然后搜出来了这个

//
//  MPaaSConfigInfo.h
//  APMPaaS
//
//  Created by yangwei on 17/4/25.
//  Copyright © 2017年 Alipay. All rights reserved.
//#import <Foundation/Foundation.h>@interface MPaaSConfigInfo : NSObject/***  当前App的无线保镖图片,是否需要区分平台。*  设置为NO,表示无线保镖SDK不需要区分平台,使用默认的 yw_1222.jpg 安全图片;*  设置为YES,表示无线保镖SDK需要区分平台,通过分配的 authCode,来指定在当前App中使用的无线保镖安全图片,并将图片设置为yw_1222_authCode.jpg;**  默认返回为NO。钱包中不需要关心;*  mPaaS用户一般也不需要修改,只有在 yw_1222.jpg 图片与其他平台发生冲突时(如同时使用 mPaaS与阿里百川相关服务),需要在 category 中重写此方法,返回YES来区分图片。** @return 默认返回NO*/
+ (BOOL)enableMPaaSAuthCode;/***  获取设置无线保镖接口的authCode值**  根据 < enableMPaaSAuthCode > 的返回值,若为NO,此方法返回nil;若为 YES,此方法返回 @"1000";*  钱包中默认为nil,mPaaS 用户根据需要重写 < enableMPaaSAuthCode > 方法**  @return 默认返回 nil*/
+ (NSString *)openSecurityAuthCode;@end

这个图片就使用蚂蚁提供的来做了。

但还想看看为啥找不到?

对enableMPaaSAuthCode进行跟踪。因为它会把名字拼到一起

bool +[MPaaSConfigInfo enableMPaaSAuthCode](void * self, void * _cmd) {rax = 0x1 & 0xff;return rax;
}

继续看看哪里调用了enableMPaaSAuthCode

只有

void * +[MPaaSConfigInfo openSecurityAuthCode](void * self, void * _cmd) {if ([MPaaSConfigInfo enableMPaaSAuthCode] != 0x0) {rbx = @"1000";[rbx retain];}else {rbx = 0x0;}rax = [rbx autorelease];return rax;
}

然后找openSecurityAuthCode

0000000102085728         dq         0x1018c8f10                                 ; @selector(openSecurityAuthCode),
"openSecurityAuthCode", DATA XREF=
-[DTRpcOperation signRequest:]+1241,
-[DTRpcOperation signRequest:]+1584,
-[DTURLRequestOperation rpcV1Sign:newSign:request:]+568,-[DTURLRequestOperation rpcV1Sign:newSign:request:]+832,+[APSyncUtils signString:appKey:]+272,+[NSString safetySignatureWithInputStr:]+265

看哪个呢这个吧[DTRpcOperation signRequest:]

void -[DTRpcOperation signRequest:](void * self, void * _cmd, void * arg2) {r13 = self;r14 = [arg2 retain];r15 = [[r13 request] retain];rbx = [[r13 httpBodyParameters] retain];[rbx release];if (rbx != 0x0) {rbx = [[OpenSecurityGuardManager getInstance] retain];r12 = [[rbx getSecureSignatureComp] retain];[rbx release];if (r12 != 0x0) {var_88 = r12;var_78 = r15;var_68 = r14;var_70 = [[NSMutableString string] retain];rbx = [[r13 method] retain];r14 = [[rbx operationType] retain];[r14 release];[rbx release];COND = r14 == 0x0;r14 = var_70;r13 = r13;if (!COND) {r12 = [[r13 method] retain];rbx = [[r12 operationType] retain];rcx = rbx;[r14 appendFormat:@"Operation-Type=%@"];[rbx release];[r12 release];}r15 = [[r13 httpBodyParameters] retain];r12 = var_68;rbx = [[r15 objectForKey:@"requestData"] retain];[r15 release];var_90 = rbx;if (rbx != 0x0) {rbx = [var_90 retain];if ([r13 isProtocolBuffers] == 0x0) {r14 = [[rbx dataUsingEncoding:0x4] retain];r15 = [[r14 base64EncodedStringWithOptions:0x0] retain];[rbx release];[r14 release];rbx = r15;r14 = var_70;}COND = [r14 length] == 0x0;rcx = @"&";if (COND) {rcx = @"";}[r14 appendFormat:@"%@Request-Data=%@"];[rbx release];}if (r12 != 0x0) {COND = [r14 length] == 0x0;rcx = @"&";if (COND) {rcx = @"";}[r14 appendFormat:@"%@Ts=%@"];}NSLog(@"Rpc sign string v2:\n%@", r14);r14 = [[DTRpcInterface sharedInstance] retain];rbx = [[r13 request] retain];rdx = rbx;var_80 = [[r14 signKeyForRequest:rdx] retain];[rbx release];[r14 release];r12 = [DTRpcUtils useNewSign];rbx = [[r13 customAppKey] retain];r15 = [rbx length];[rbx release];if (r15 != 0x0) {r15 = var_78;rbx = [[r13 customAppKey] retain];[var_80 release];var_80 = rbx;rcx = @"appkey";rdx = rbx;[r15 setValue:rdx forHTTPHeaderField:rcx];var_60 = @"input";var_58 = var_70;rbx = [[NSDictionary dictionaryWithObjects:rdx forKeys:rcx count:0x1] retain];r14 = [[OpenSecurityGuardParamContext createParamContextWithAppKey:var_80 paramDict:rbx requestType:*_OPEN_ENUM_SIGN_COMMON_MD5] retain];[rbx release];r12 = [[MPaaSConfigInfo openSecurityAuthCode] retain];rsi = @selector(signRequest:authCode:);rdx = r14;rbx = [_objc_msgSend(var_88, rsi) retain];[r12 release];if (rbx != 0x0) {rsi = @selector(setValue:forHTTPHeaderField:);rdx = rbx;_objc_msgSend(r15, rsi);}[rbx release];rdi = r14;}else {r15 = var_78;if (r12 != 0x0) {var_50 = @"input";var_40 = var_70;*(&var_50 + 0x8) = @"atlas";*(&var_40 + 0x8) = @"a";rax = [NSDictionary dictionaryWithObjects:rdx forKeys:rcx count:0x2];rax = [rax retain];var_A0 = rax;r12 = [[OpenSecurityGuardParamContext createParamContextWithAppKey:var_80 paramDict:rax requestType:*_OPEN_ENUM_SIGN_ATLAS] retain];var_98 = r12;rbx = [[MPaaSConfigInfo openSecurityAuthCode] retain];r14 = [[var_88 signRequest:r12 authCode:rbx] retain];[rbx release];if (r14 != 0x0) {[r15 setValue:r14 forHTTPHeaderField:@"Sign"];[r15 setValue:@"1" forHTTPHeaderField:@"SignType"];NSLog(@"%@", cfstring_a);}rsi = r14;rdx = var_70;NSLog(@"sign v2:%@,content:%@", rsi, rdx);[r14 release];[var_98 release];rdi = var_A0;}else {var_60 = @"input";var_58 = var_70;rbx = [[NSDictionary dictionaryWithObjects:rdx forKeys:rcx count:0x1] retain];r14 = [[OpenSecurityGuardParamContext createParamContextWithAppKey:var_80 paramDict:rbx requestType:*_OPEN_ENUM_SIGN_COMMON_MD5] retain];[rbx release];r12 = [[MPaaSConfigInfo openSecurityAuthCode] retain];rsi = @selector(signRequest:authCode:);rdx = r14;rbx = [_objc_msgSend(var_88, rsi) retain];[r12 release];if (rbx != 0x0) {rsi = @selector(setValue:forHTTPHeaderField:);rdx = rbx;_objc_msgSend(r15, rsi);}[rbx release];rdi = r14;}}[rdi release];r14 = var_68;r12 = var_88;if ([r13 isProtocolBuffers] == 0x0) {rbx = [[var_90 dataUsingEncoding:0x4] retain];[r15 setHTTPBody:rbx];[rbx release];}[var_80 release];[var_90 release];[var_70 release];}[r12 release];}[r15 release];[r14 release];if (*___stack_chk_guard != *___stack_chk_guard) {__stack_chk_fail();}return;
}

r12 = [[MPaaSConfigInfo openSecurityAuthCode] retain];

rsi = @selector(signRequest:authCode:);

rdx = r14;

rbx = [_objc_msgSend(var_88, rsi) retain];

[r12 release];

没看到调用就release了。

那肯定在signRequest:authCode这个方法里传值了

void * -[SecurityGuardOpenSecureSignature signRequest:authCode:](void * self, void * _cmd, void * arg2, void * arg3) {rax = loc_100b214f9(self, _cmd, arg2, arg3);return rax;
}

这个再点已经跳不过去了

加密的东西肯定保护得好,至此图片名字获取已失败告终

代码写完,配置一个新环境,代码写死后成功开启了。

现在需要一个全局变量控制加载哪个环境的参数。

还要加一个图形界面选择环境切换,重写蚂蚁封装的didfinishLaunch,结果黑屏  此方案kill

另一个方案,先进默认环境,点击某个按钮提示切换环境,选择对应环境做三个事

1.清上面说的NSUserDefaults内容和沙盒内所有文件

2.将环境名称写入本地,沙盒的其他地方新创建文件来做。

3.退出重新进

然后新打开app就是切换后的环境,和以前卸载重装一样

如果直接退出,下次进来则是不会清除上次的数据的,还可以按照上次设置的环境来用。

后记:

蚂蚁也提供了动态切换环境的方法,iOS 环境切换 - 移动开发平台 mPaaS - 阿里云

这个方法和上面的方式同时使用会使上面的失效,解决办法:hook方法pathForResource,在读取Settings.bundle的时候进行拦截

- (nullable NSString *)pathForResource:(nullable NSString *)name ofTypeS:(nullable NSString *)ext;

{

if ([name containsString:@"Settings"]) {

name = @"";

}

return [self pathForResource:name ofTypeS:ext];

}

再后记:

实现代码见下面这篇博客 拿来即用删掉即走:iOS客户端无侵入、一包任意环境切换实践篇__小呵呵的博客-CSDN博客

使用mpaas的iOS客户端如何一包支持任意环境切换(理论篇)相关推荐

  1. 使用BurpSuite对IOS客户端app抓包方法

    使用BurpSuite对IOS客户端app抓包方法 BurpSuite代理环境设置 设备 BurpSuite设置 打开BurpSuite>proxy>options 导出CA证书 传送文件 ...

  2. image加载图片 ui unity_【Unity游戏客户端框架搭建】四、资源管理之理论篇

    先抛出个问题: 我们在谈论资源管理的时候,是在讨论什么? 整理了一下内容,大概就以下几点: 目录规划 内存管理 包体大小控制 AB打包粒度 一.目录规划 在开始资源管理之前,先讲一下目录规划. 实际的 ...

  3. charles乱码_基于iOS的Charles抓包实践

    奇技指南 在应用开发过程中,通过抓包调试服务端接口的场景时常出现.Charles和Wireshark是开发过程中最常用的两款软件.那么今天,让我们以iOS为例,聊一聊Charles抓包. 本文来自36 ...

  4. 58 同城 iOS 客户端组件体积分析与统计实践

    [导读]目前 58 旗下存在租房.安居客.招聘.二手车.黄页等多个业务线,其中每个业务线在 58 APP 中存在一个或多个业务 pod.在研发层面上,58 同城其实早已实现了并行研发,不过,在并行研发 ...

  5. iOS 客户端基于 WebP 图片格式的流量优化(下)

    在iOS 客户端基于 WebP 图片格式的流量优化(上)这篇文章中,已经介绍了WebP格式图片的下载使用,仅仅只有这样还远远不够,还需要对已经下载的图片数据进行缓存. 曾经有句名言『计算机世界有两大难 ...

  6. 58同城iOS客户端Hybrid框架探索

    作者:杜艳新,刘文军.58同城iOS高级研发工程师,专注于App Hybrid框架的架构研发,主导了58同城App的Hybird混合研发的系统架构以及研发. 责编:唐小引,欢迎技术投稿.约稿.给文章纠 ...

  7. 58 同城 iOS 客户端组件化演变历程

    导语: 架构的演进是为业务不断发展服务的,架构不能脱离业务,这是最基本的出发点.58 同城 iOS 客户端随着业务量和用户量的持续增长,架构也是不断受到挑战,采用什么样的架构去适应这些变化,对技术人员 ...

  8. 58 同城 iOS 客户端 Hybrid 框架探索

    [CSDN 编者按]58 同城 iOS 客户端的 Hybrid 框架在最初设计和演进的过程中,遇到了许多问题.为此,整个 Hybrid 框架产生了很大的变化.本文作者将遇到的典型问题进行了总结,并重点 ...

  9. 详解Android/IOS平台下抓包工具使用以及抓取API接口

    抓包工具 Charles 主机允许代理模式 客户端设置代理 截获数据包 HTTPS 模仿一个app UI 请求接口封装 加密请求 Headers 参考项目 抓包(Packet Capture),实际上 ...

最新文章

  1. 关于element-ui 中使用Notice组件(Message、MessageBox、Notification)所遇到的坑
  2. 【数据库】防止SQL注入
  3. MIT自然语言处理第四讲:标注
  4. Android细节问题总结(一)
  5. 由浅入深:自己动手开发模板引擎——解释型模板引擎(二)
  6. UVA - 12083 Guardian of Decency (二分匹配)
  7. Hive SQL优化之 Count Distinct
  8. 27岁姑娘,去世前一天,留给世界这封信,看哭众多网友
  9. c语言中输入字符用什么作用是什么意思,C语言编程问题
  10. 特斯拉电动皮卡发布:马斯克称它能防弹、能上火星,结果还没扛过一钢球......
  11. SQL 与 Hibernate 性能相差悬殊
  12. Python知识点之Python进阶
  13. 英特尔®以太网700系列的动态设备个性化
  14. 【项目】区块链在电商领域中的应用-草莓糖(CMT)(二)
  15. shell脚本中shift的用法
  16. BZOJ1135: [POI2009]Lyz
  17. leetcode — 46. 全排列(不含重复数字)
  18. 中职计算机英语教学设计,中职英语教学设计三篇
  19. 微软官方工具_微软官方免费数据恢复工具Windows File Recovery帮你恢复电脑误删文件...
  20. 企业微信实现多开C++逆向

热门文章

  1. Wise-IoU: Bounding Box Regression Loss with Dynamic Focusing Mechanism
  2. 5年经验之谈 —— 功能测试和性能测试的区别是什么?
  3. 11月5G手机出货量超500万部,4款5G新机上市
  4. 乾坤:微应用非首次加载,需刷新才会重新加载
  5. Java 虚拟机内存分配机制
  6. word的交叉引用怎么是一串文字(REF_Ref133\r\h);word的文献引用为REF代码;域代码
  7. 2022年全球市场超声波总体规模、主要企业、主要地区、产品和应用细分研究报告
  8. 铁道部:儿童火车票购买身高标准调高10厘米
  9. Kaldi学习手记(三): Kaldi 的I/O机制
  10. 【计算机毕业设计】056教学资源库