前言:上一篇我們講了如何通過重簽名目標ipa文件以及修改Math-O來實現動態注入。知道如何攻擊,是爲了找出攻擊細節來實現防禦。這一篇我們來討論下如何防禦動態注入。
相信瞭解了動態注入過程的同學都會有這樣的體會:動態注入的核心步驟有2點:
- 重簽名
- 添加自己的framework
反重籤
之前在講重簽名時,我留了一個伏筆:
重簽名時,打包的證書不是原始ipa開發團隊的,證書變了,bundle id自然必須改變。
既然注入必須重籤,重籤bundle id必然要變,那能不能通過比較bundle id來判斷當前運行的app是否被重簽了呢?
答案當然是肯定的!
實際上,每個運行的app都會把bundle id 寫進embedded.mobileprovision文件。並且這個文件存在於main bundle中,可以被當作普通資源文件訪問到。
+ (void)compareBundleID {
NSString *embeddedPath = [[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"];
if ([[NSFileManager defaultManager] fileExistsAtPath:embeddedPath])
{
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];
NSArray *identifierComponents = [fullIdentifier componentsSeparatedByString:@"."];
NSString *appIdentifier = [identifierComponents firstObject];
// 對比簽名ID
if ([appIdentifier caseInsensitiveCompare:@"預設的bundle id"] != NSOrderedSame) {
NSLog(@"!--- wrong appIdentifier: %@", appIdentifier);
// exit(0);
}
NSLog(@"!--- appIdentifier: %@", appIdentifier);
break;
}
}
}
}
通過獲取embedded.mobileprovision中現在的bundle id,和預先設定的bundle id進行比較。如果發現不符合,那麼可以確定,現在運行的app已經被重籤,進而採取進一步的攻防策略。
進程白名單
通過比較bundle id的方式來應對動態注入確實簡單粗暴,但還有沒有其它方式?估計很多同學已經想到了:既然動態注入要添加framework,那是否可以獲取當前app加載的framework列表來判斷是否被注入了呢?
+ (void)dyldWhiteList{
int count = _dyld_image_count();
for (int i = 0; i < count; i++) {
//遍歷拿到庫名稱!
const char * imageName = _dyld_get_image_name(i);
NSString* dyldName = [[NSString alloc]initWithUTF8String:imageName];
NSLog(@"imageName: %@", dyldName);
}
}
通過以上代碼獲取系統中運行的庫:
我們發覺打印的庫實在太多了,修改一下上面的代碼,過濾掉系統庫:
if([dyldName containsString:@"/System/Library/Frameworks/"] || [dyldName containsString:@"/System/Library/PrivateFrameworks/"] || [dyldName containsString:@"/usr/lib/system/"])
{
continue;
}
剩下的庫已經不多了,很容易在其中找到我們動態注入的庫
接下來的工作就很簡單了,我們只要記錄項目正常運行時運行的庫名單,隨後定期去和項目中運行的庫名單進行比較,就能識別出是否有被動態注入
後記
關於動態注入,其實還有很多內容可以講,今後再累積一些內容後會和大家分享。關於整個系列,後面想開篇分享一些和越獄有關的知識。