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,新生成的包會自動替換原有文件。
  • 第七步:安裝重簽名后的ipa文件

    • 最新的iTunes已經不能給iPhone安裝APP了,所以我們可以使用各種助手或者iTools進行安裝。
  • 參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM