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進行安裝。