App应用完整性校验:越狱检测、重签名检测,文件hash检测
App应用完整性校验
-
大概做一下检测,以判断App是否被篡改,是否越狱等,做一些特殊处理。
-
越狱检测
-
Mach-O文件检测
-
重签名检测
-
资源文件hash检测
越狱检测
- 可以检测当前设备是否越狱,在关键性业务判断给出提示强制退出以免造成安全问题,这里的关键性业务可能是需要自己定义范围,比如牵扯到用户敏感信息等业务。
// 越狱检测
const char* jailbreak_tool_pathes[] = {
"/Applications/Cydia.app",
"/Library/MobileSubstrate/MobileSubstrate.dylib",
"/bin/bash",
"/usr/sbin/sshd",
"/etc/apt"
};
#define ARRAY_SIZE(a) sizeof(a)/sizeof(a[0])
- (BOOL)isJailBroken
{
if ([self isSimulator] == YES)
{
return NO;
}
for (int i=0; i<ARRAY_SIZE(jailbreak_tool_pathes); i++) {
if ([[NSFileManager defaultManager] fileExistsAtPath:[NSString stringWithUTF8String:jailbreak_tool_pathes[i]]]) {
NSLog(@"The device is jail broken!");
return YES;
}
}
NSLog(@"The device is NOT jail broken!");
return NO;
}
- (BOOL)isSimulator {
#if TARGET_OS_SIMULATOR
return YES;
#else
return NO;
#endif
}
Mach-O文件检测
- 通过检测SignerIdentity判断是Mach-O文件否被篡改。原理是:SignerIdentity的值在info.plist中是不存在的,开发者不会加上去,苹果也不会,只是当ipa包被反编译后篡改文件再次打包,需要伪造SignerIdentity。所以只要被攻击篡改东西如果重新运行到手机上就会出现这个东西。
//判断Mach-O文件否被篡改
- (BOOL)checkMach_O
{
NSBundle *bundle = [NSBundle mainBundle];
NSDictionary *info = [bundle infoDictionary];
if ([info objectForKey: @"SignerIdentity"] != nil) {
//存在这个key,则说明被二次打包了
return YES;
}
return NO;
}
重签名检测
- 由于要篡改App必然重签名,至于为什么重签名,是因为苹果做了校验改动了任何东西校验失败是直接闪退的,其实原理也是校验文件的hash值。签名打包过程会出现这个embedded.mobileprovision文件,这个文件有teamID的一个东西我们可以校验是否是我们自己的团队的teamID来判断。或者判断BundleID 是否被修改。
/**
重签名检测
@param provisionID 开发者的teamid
*/
- (BOOL)checkCodeSignWithProvisionID:(NSString *)provisionID
{
// 描述文件路径
NSString *embeddedPath = [[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"];
if ([[NSFileManager defaultManager] fileExistsAtPath:embeddedPath]) {
// 读取application-identifier
NSString *embeddedProvisioning = [NSString stringWithContentsOfFile:embeddedPath encoding:NSASCIIStringEncoding error:nil];
NSArray *embeddedProvisioningLines = [embeddedProvisioning componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
for (int i = 0; i < [embeddedProvisioningLines count]; i++) {
if ([[embeddedProvisioningLines objectAtIndex:i] rangeOfString:@"application-identifier"].location != NSNotFound) {
NSInteger fromPosition = [[embeddedProvisioningLines objectAtIndex:i+1] rangeOfString:@"<string>"].location+8;
NSInteger toPosition = [[embeddedProvisioningLines objectAtIndex:i+1] rangeOfString:@"</string>"].location;
NSRange range;
range.location = fromPosition;
range.length = toPosition - fromPosition;
NSString *fullIdentifier = [[embeddedProvisioningLines objectAtIndex:i+1] substringWithRange:range];
// NSLog(@"%@", fullIdentifier);
NSArray *identifierComponents = [fullIdentifier componentsSeparatedByString:@"."];
NSString *appIdentifier = [identifierComponents firstObject];
// 对比签名ID
if (![appIdentifier isEqual:provisionID])
{
return NO;
}
else
{
return YES;
}
}
}
}
return YES;
}
文件hash检测
- 我们对Plist文件以及App 的icon资源文件做hash值校验。网上一些对_CodeSignature的CodeResources以及App二进制文件的校验做法有问题。因为Xcode打包过程不同环境造成的hash值不一样,可以看出不同环境打包过程造成的hash值不一样的选项。所以我们必须过滤掉变化的文件。检测Plist文件以及App Icon资源文件这些东西。
- 也可以选择一些比较重要的资源进行hash校验
- 比如启动图、icon等,可以将hash值写在代码里,启动APP进行校验
//生成资源文件名及对应的hash的字典
-(NSDictionary *)getBundleFileHash {
NSMutableDictionary * dicHash = [NSMutableDictionary dictionary];
// 对这些文件进行校验
NSArray *fileArr = @[
@"launchGIF.gif",
@"AppIcon29x29@2x.png",
@"AppIcon29x29@3x.png",
@"AppIcon40x40@2x.png",
@"AppIcon40x40@3x.png",
@"AppIcon60x60@2x.png",
@"AppIcon60x60@3x.png",
@"LaunchImage-568h@2x.png",
@"LaunchImage-700-568h@2x.png",
@"LaunchImage-700@2x.png",
@"LaunchImage-800-667h@2x.png",
@"LaunchImage-800-Portrait-736h@3x.png",
@"LaunchImage-1100-Portrait-2436h@3x.png",
@"LaunchImage@2x.png"
];
for (NSString * fileName in fileArr) {
// 对应的文件生成hash
NSString * HashString = [MD5 getMD5WithFilePath:[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:fileName]];
NSLog(@"%@-%@",fileName,HashString);
if (HashString != nil) {
[[hashDict objectForKey:fileName] isEqualToString:HashString];
}
}
//所有资源文件的hash就保存在这数组里
return dicHash;
}
- 使用MD5生成文件hash值
#import <CommonCrypto/CommonDigest.h>
@implementation MD5
+ (NSString*)getMD5WithData:(NSData *)data {
if (!data) {
return nil;//判断sourceString如果为空则直接返回nil。
}
//需要MD5变量并且初始化
CC_MD5_CTX md5;
CC_MD5_Init(&md5);
//开始加密(第一个参数:对md5变量去地址,要为该变量指向的内存空间计算好数据,第二个参数:需要计算的源数据,第三个参数:源数据的长度)
CC_MD5_Update(&md5, data.bytes, (CC_LONG)data.length);
//声明一个无符号的字符数组,用来盛放转换好的数据
unsigned char result[CC_MD5_DIGEST_LENGTH];
//将数据放入result数组
CC_MD5_Final(result, &md5);
//将result中的字符拼接为OC语言中的字符串,以便我们使用。
NSMutableString *resultString = [NSMutableString string];
for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
[resultString appendFormat:@"%02x",result[i]];
}
// NSLog(@"resultString=========%@",resultString);
return resultString;
}
// 计算文件的hash
+(NSString*)getMD5WithFilePath:(NSString*)path {
NSData *data = [NSData dataWithContentsOfFile:path];
return [self getMD5WithData:data];
}
@end
- app启动后检测
// 关键资源hash值检测
- (BOOL)hasFileChanged {
NSArray *fileArr = @[
@"launchGIF.gif",
@"AppIcon29x29@2x.png",
@"AppIcon29x29@3x.png",
@"AppIcon40x40@2x.png",
@"AppIcon40x40@3x.png",
@"AppIcon60x60@2x.png",
@"AppIcon60x60@3x.png",
@"LaunchImage-568h@2x.png",
@"LaunchImage-700-568h@2x.png",
@"LaunchImage-700@2x.png",
@"LaunchImage-800-667h@2x.png",
@"LaunchImage-800-Portrait-736h@3x.png",
@"LaunchImage-1100-Portrait-2436h@3x.png",
@"LaunchImage@2x.png"
];
// 对一些文件进行hash检验
NSDictionary *hashDict = @{
@"AppIcon29x29@2x.png":@"4b9692293a06d28865f9c4575db18616",
@"AppIcon29x29@3x.png":@"395fbb12c1d1fdc6e7ba957148b0a2c1",
@"AppIcon40x40@2x.png" :@"ddd599c7f7bd13c4e7bcd1ad63b9b014",
@"AppIcon40x40@3x.png" :@"a62854365df9c784fbab913cfc0d0d24",
@"AppIcon60x60@2x.png" :@"7076eaca82975a63be2d05b194cf1490",
@"AppIcon60x60@3x.png" :@"07be79eb6fa2d4e5ae45b8d9e9b9074e",
@"LaunchImage-1100-Portrait-2436h@3x.png" :@"f68d3a4ef5fecb9caebfc966c43088fe",
@"LaunchImage-568h@2x.png":@"41d02d33a9f121b6513340f38f135ee3",
@"LaunchImage-700-568h@2x.png":@"41d02d33a9f121b6513340f38f135ee3",
@"LaunchImage-700@2x.png" :@"8dd40fd3ae453344dcc8de70813aa1c4",
@"LaunchImage-800-667h@2x.png" :@"515af0633b2fc6e3fe69ddd251cc9411",
@"LaunchImage-800-Portrait-736h@3x.png" :@"72bf97143d51f377acb048db25824eaa",
@"LaunchImage@2x.png" :@"8dd40fd3ae453344dcc8de70813aa1c4",
@"launchGIF.gif" :@"4c7985622b2987304a9a9ab89947e91d"
};
BOOL changed = NO;
for (NSString * fileName in fileArr) {
NSString * HashString = [MD5 getMD5WithFilePath:[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:fileName]];
NSLog(@"%@-%@",fileName,HashString);
if (HashString != nil) {
if([[hashDict objectForKey:fileName] isEqualToString:HashString]) {
changed = NO;
}else{
return YES;
}
}else{
return YES;
}
}
return changed;
}
检测时机
- 看具体需求,可以在启动时检测一次,或者在某些需要的地方进行检测,至于是退出APP还是提示用户,看需求了。
#pragma mark - 安全检测
if([self isJailBroken]) {
NSLog(@"已越狱");
DQAlertView * alert = [[DQAlertView alloc] initWithTitle:@"温馨提示" message:@"手机已越狱,清注意安全!" delegate:self cancelButtonTitle:nil otherButtonTitles:@"知道了", nil];
[alert show];
}else{
NSLog(@"未越狱");
}
if([self checkMach_O]) {
NSLog(@"二次打包");
DQAlertView * alert = [[DQAlertView alloc] initWithTitle:@"温馨提示" message:@"APP完整性被破坏,清注意安全!" delegate:self cancelButtonTitle:nil otherButtonTitles:@"知道了", nil];
[alert show];
}else{
NSLog(@"Mach_O 正常");
}
if([self checkCodeSignWithProvisionID:@"L76K5AFQYD"]) {
NSLog(@"teamid 正常");
}else{
NSLog(@"APP被篡改-重签名");
DQAlertView * alert = [[DQAlertView alloc] initWithTitle:@"温馨提示" message:@"APP被篡改,清注意安全!" delegate:self cancelButtonTitle:nil otherButtonTitles:@"知道了", nil];
[alert show];
}
if([self hasFileChanged]) {
DQAlertView * alert = [[DQAlertView alloc] initWithTitle:@"温馨提示" message:@"APP被篡改,清注意安全!" delegate:self cancelButtonTitle:nil otherButtonTitles:@"知道了", nil];
[alert show];
}else{
NSLog(@"APP资源文件正常");
}
- 刚刚的代码添加了,还需要自己去重签名测试
APP重新签名打包ipa
-
安装 fastlane
-
第一步:用Xcode新建一个工程,Bundle identifier不要和手机中已有的的APP重复,然后用自己的证书打包出ipa文件。
-
第二步:获取mobileprovision文件。将ipa文件后缀改为zip解压
-
第三步:安装Homebrew,安装过可跳到第五步
- 在终端先后执行下面2命令行安装,等待进度完毕
xcode-select --install
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
-
第四步:安装ruby
- `在终端执行下面命令安装ruby,等待进度完毕(输完密码可能在较短时间无反应)
brew install ruby
-
第五步:安装sigh脚本
-
执行下面安装命令
sudo gem install fastlane
-
附:Sigh脚本GitHub地址。
-
-
第六步:使用sigh脚本开始重新签名
- 1、在终端输入fastlane sigh resign ipa路径,回车
- ipa路径=>把要签名的ipa文件(路径、包名不要有中文)拖到终端窗口上,即可快速获取
- 2、填写Signing Identity:第一步中脚本会列出电脑中的证书,选择要用的证书的SHA-1即可
- 如:27AF89640E0F32910815581CHB8L8281C71E8EEC8。完成后回车
- 3、把项目的配置文件.mobileprovision文件(第二步中的文件)拖到终端窗口上,回车
- 4、好了,resign脚本会自动更改bundel id,签名并重新打包。
- 完成后提示Successfully signed,新生成的包会自动替换原有文件。
- 1、在终端输入fastlane sigh resign ipa路径,回车
-
第七步:安装重签名后的ipa文件
- 最新的iTunes已经不能给iPhone安装APP了,所以我们可以使用各种助手或者iTools进行安装。
本文来自博客园,作者:struggle_time,转载请注明原文链接:https://www.cnblogs.com/songliquan/p/16019416.html