雖然公司的項(xiàng)目目前還不算健壯,安全問(wèn)題對(duì)于大部分小公司來(lái)說(shuō)似乎并沒(méi)什么必要,不過(guò)要攻擊的話(huà),我有十足的把握,我們是無(wú)法承受沖擊的。嘿嘿嘿~不過(guò)帶著一顆入坑iOS的心思,搜集了一下資料后,還是做了一些嘗試。
iOS App安全防范總結(jié):
1.防止抓包篡改數(shù)據(jù)
2.防止反編譯
3.阻止動(dòng)態(tài)調(diào)試
4.防止二次打包
關(guān)鍵檢測(cè):越獄檢測(cè)
-
OK,下面是正文開(kāi)始。
1.防止抓包篡改數(shù)據(jù)
對(duì)于抓包,利用神器charles的操作會(huì)在另外的文章單獨(dú)介紹。如果不懂以下為利用charles抓包。
charles抓包教程
若別人真想抓你程序包,該如何防范呢?我目前只能說(shuō),let it go ~ let it go~隨他抓,隨他抓。因?yàn)榛旧现灰胱ト〕绦蛟L問(wèn)的數(shù)據(jù),基本上是能抓取到的。對(duì)于iOS來(lái)說(shuō),目前我是做了兩種操作。
1)判斷是否設(shè)置了代理
對(duì)于抓包,現(xiàn)在的手段基本是設(shè)置代理,所以我們可以通過(guò)判斷是否設(shè)置了代理的方式來(lái)進(jìn)行下一步的防范。
#在網(wǎng)絡(luò)請(qǐng)求前插入這個(gè)方法,再根據(jù)需求做相應(yīng)的防范
+ (BOOL)getDelegateStatus
{
NSDictionary *proxySettings = CFBridgingRelease((__bridge CFTypeRef _Nullable)((__bridge NSDictionary *)CFNetworkCopySystemProxySettings()));
NSArray *proxies = CFBridgingRelease((__bridge CFTypeRef _Nullable)((__bridge NSArray *)CFNetworkCopyProxiesForURL((__bridge CFURLRef)[NSURL URLWithString:@"http://www.google.com"], (__bridge CFDictionaryRef)proxySettings)));
NSDictionary *settings = [proxies objectAtIndex:0];
NSLog(@"host=%@", [settings objectForKey:(NSString *)kCFProxyHostNameKey]);
NSLog(@"port=%@", [settings objectForKey:(NSString *)kCFProxyPortNumberKey]);
NSLog(@"type=%@", [settings objectForKey:(NSString *)kCFProxyTypeKey]);
if ([[settings objectForKey:(NSString *)kCFProxyTypeKey] isEqualToString:@"kCFProxyTypeNone"])
{
//沒(méi)有設(shè)置代理
return NO;
} else {
//設(shè)置代理了
return YES;
}
}
2)RSA
通過(guò)與后臺(tái)的配合,設(shè)置公鑰與私鑰,對(duì)請(qǐng)求數(shù)據(jù)和返回?cái)?shù)據(jù)進(jìn)行加密。這里另外起一篇單獨(dú)介紹。
2.防止反編譯(防止class-dump、hopper反編)
越獄檢測(cè)
一般能拿到自己ipa包都需要有一臺(tái)越獄的手機(jī)
判斷設(shè)備是否安裝了越獄常用工具:
一般安裝了越獄工具的設(shè)備都會(huì)存在以下文件:
/Applications/Cydia.app
/Library/MobileSubstrate/MobileSubstrate.dylib
/bin/bash
/usr/sbin/sshd
/etc/apt判斷設(shè)備上是否存在cydia應(yīng)用
是否有權(quán)限讀取系統(tǒng)應(yīng)用列表
沒(méi)有越獄的設(shè)備是沒(méi)有讀取所有應(yīng)用名稱(chēng)的權(quán)限檢測(cè)當(dāng)前程序運(yùn)行的環(huán)境變量 DYLD_INSERT_LIBRARIES
非越獄手機(jī)DYLD_INSERT_LIBRARIES獲取到的環(huán)境變量為NULL。
綜上所述,檢查設(shè)備是否越獄
+ (BOOL)isJailbroken {
// 檢查是否存在越獄常用文件
NSArray *jailFilePaths = @[@"/Applications/Cydia.app",
@"/Library/MobileSubstrate/MobileSubstrate.dylib",
@"/bin/bash",
@"/usr/sbin/sshd",
@"/etc/apt"];
for (NSString *filePath in jailFilePaths) {
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
return YES;
}
}
// 檢查是否安裝了越獄工具Cydia
if([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"cydia://package/com.example.package"]]){
return YES;
}
// 檢查是否有權(quán)限讀取系統(tǒng)應(yīng)用列表
if ([[NSFileManager defaultManager] fileExistsAtPath:@"/User/Applications/"]){
NSArray *applist = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:@"/User/Applications/"
error:nil];
NSLog(@"applist = %@",applist);
return YES;
}
// 檢測(cè)當(dāng)前程序運(yùn)行的環(huán)境變量
char *env = getenv("DYLD_INSERT_LIBRARIES");
if (env != NULL) {
return YES;
}
return NO;
}
代碼混淆
這里生成混淆代碼的方法我們通過(guò)shell腳本來(lái)實(shí)現(xiàn),同時(shí)我們需要一個(gè)文檔來(lái)寫(xiě)入我們需要進(jìn)行混淆的方法名或是變量名。
-
打開(kāi)終端,cd到文件所在目錄,使用
touch confuse.sh
touch func.list
此時(shí)將目錄中的.sh和.list文件拖入項(xiàng)目中
-
寫(xiě)入shell腳本
在項(xiàng)目中找到剛剛拖進(jìn)來(lái)的.sh文件,在confuse.sh中寫(xiě)入腳本
#!/bin/bash
# 這是Shell腳本,如果不懂shell,自行修煉:http://www.runoob.com/linux/linux-shell.html
# 以下使用sqlite3進(jìn)行增加數(shù)據(jù),如果不了解sqlite3命令,自行修煉:http://www.runoob.com/sqlite/sqlite-tutorial.html
#數(shù)據(jù)表名
TABLENAME="CodeObfuscationOC"
#數(shù)據(jù)庫(kù)名
SYMBOL_DB_FILE="CodeObfuscation.db"
#要被替換的方法列表文件
STRING_SYMBOL_FILE="$PROJECT_DIR/ConfusionDemo/func.list"
#被替換后的宏定義在此文件里
HEAD_FILE="$PROJECT_DIR/$PROJECT_NAME/CodeObfuscation.h"
#維護(hù)數(shù)據(jù)庫(kù)方便日后做bug排查
createTable()
{
echo "create table $TABLENAME(src text,des text);" | sqlite3 $SYMBOL_DB_FILE
}
insertValue()
{
echo "insert into $TABLENAME values('$1','$2');" | sqlite3 $SYMBOL_DB_FILE
}
query()
{
echo "select * from $TABLENAME where src='$1';" | sqlite3 $SYMBOL_DB_FILE
}
#生成隨機(jī)16位名稱(chēng)
randomString()
{
openssl rand -base64 64 | tr -cd 'a-zA-Z' | head -c 16
}
#刪除舊數(shù)據(jù)庫(kù)文件
rm -f $SYMBOL_DB_FILE
#刪除就宏定義文件
rm -f $HEAD_FILE
#創(chuàng)建數(shù)據(jù)表
createTable
#touch命令創(chuàng)建空文件,根據(jù)指定的路徑
touch $HEAD_FILE
echo '#ifndef CodeObfuscation_h
#define CodeObfuscation_h' >> $HEAD_FILE
echo "http://confuse string at `date`" >> $HEAD_FILE
#使用cat將方法列表文件里的內(nèi)容全部讀取出來(lái),形成數(shù)組,然后逐行讀取,并進(jìn)行替換
cat "$STRING_SYMBOL_FILE" | while read -ra line;
do
if [[ ! -z "$line" ]]
then
random=`randomString`
echo $line $random
#將生成的隨機(jī)字符串插入到表格中
insertValue $line $random
#將生成的字符串寫(xiě)入到宏定義文件中,變量是$HEAD_FILE
echo "#define $line $random" >> $HEAD_FILE
fi
done
echo "#endif" >> $HEAD_FILE
sqlite3 $SYMBOL_DB_FILE .dump
-
添加run script命令

-
然后添加$PROJECT_DIR/ConfusionDemo/confuse.sh
-
給腳本授權(quán)
接下來(lái)還是在我們項(xiàng)目的文件夾下,通過(guò)終端給我們的腳本賦予最高權(quán)限
chmod 777 confuse.sh
-
添加預(yù)編譯文件PCH

然后配置PCH文件

-
添加$PROJECT_DIR/ConfusionDemo/PrefixHeader.pch
-
生成CodeObfuscation.h文件
這時(shí)候我們編譯一下代碼,會(huì)發(fā)現(xiàn)項(xiàng)目中多出了一個(gè)CodeObfuscation.h文件(如果沒(méi)有,可到項(xiàng)目文件夾中找,我的就是在文件夾里找到的- -,然后拖進(jìn)項(xiàng)目)。這個(gè)文件就是替換方法名的文件,我們?cè)赑CH文件中引入他。
-
在func.list中添加準(zhǔn)備替換的方法名
在項(xiàng)目中點(diǎn)開(kāi)之前拖進(jìn)來(lái)的func.list文件,然后在里面加入自己想要混淆的方法名
//此處方法名為項(xiàng)目中自己編碼的方法名,不可混淆系統(tǒng)方法名
viewControllerTestMethodA
viewControllerTestMethodB
viewControllerTestMethodC
viewControllerMethodWithParameter
testString
testArray
testMutDic
-
結(jié)果
編譯之后
#ifndef CodeObfuscation_h
#define CodeObfuscation_h
#define viewControllerTestMethodA CTBxmOXAbJYekhnH
#define viewControllerTestMethodB RnPEjnXygFXLdikO
#define viewControllerTestMethodC IzHlDYOpaAFYFTXa
#define viewControllerMethodWithParameter nWqyalBcfoUSRVpc
#define testString MNPoVYdmCcklAnCO
#define testArray kHMRxPlGXGeqekxL
#define testMutDic hphPSODIvbBFSTHX
#endif
看到 CodeObfuscation有這種變化,恭喜你,已經(jīng)代碼混淆成功。即使通過(guò)class-dump反編出來(lái)的,也只是一堆亂碼。
-
需要注意的幾點(diǎn)
不可以混淆iOS中的系統(tǒng)方法;
不可以混淆iOS中init等初始化方法;
不可以混淆xib的文件,會(huì)導(dǎo)致找不到對(duì)應(yīng)文件;
不可以混淆storyboard中用到的類(lèi)名;
混淆有風(fēng)險(xiǎn),有可能會(huì)被App Store以2.1大禮包拒掉。
3.阻止動(dòng)態(tài)調(diào)試
GDB、LLDB是Xcode內(nèi)置的動(dòng)態(tài)調(diào)試工具。使用GDB、LLDB可以動(dòng)態(tài)的調(diào)試你的應(yīng)用程序(通過(guò)下斷點(diǎn)、打印等方式,查看參數(shù)、返回值、函數(shù)調(diào)用流程等)。
為了阻止hackers使用調(diào)試器 GDB、LLDB來(lái)攻擊你的App,你可以在main.m文件中插入以下代碼:
#import <dlfcn.h>
#import <sys/types.h>
typedef int (*ptrace_ptr_t)(int _request, pid_t _pid, caddr_t _addr, int _data);
#if !defined(PT_DENY_ATTACH)
#define PT_DENY_ATTACH 31
#endif // !defined(PT_DENY_ATTACH)
void disable_gdb() {
void* handle = dlopen(0, RTLD_GLOBAL | RTLD_NOW);
ptrace_ptr_t ptrace_ptr = dlsym(handle, "ptrace");
ptrace_ptr(PT_DENY_ATTACH, 0, 0, 0);
dlclose(handle);
}
int main(int argc, char *argv[]) {
// Don't interfere with Xcode debugging sessions.
#if !(DEBUG)
disable_gdb();
#endif
@autoreleasepool {
return UIApplicationMain(argc, argv, nil,
NSStringFromClass([MyAppDelegate class]));
}
}
4.防止二次打包
iOS 和 OS X 的應(yīng)用和框架包含了二進(jìn)制代碼和所需要的資源文件(如:圖片、不同的語(yǔ)言文件、XIB/Storyboard文件、profile文件等),在通過(guò)開(kāi)發(fā)者私鑰簽名程序包時(shí),對(duì)于可執(zhí)行文件( Mach-O ),會(huì)將簽名直接寫(xiě)入到該文件中,而對(duì)于其他的資源文件,會(huì)統(tǒng)一寫(xiě)到 _CodeSignature 文件下的 CodeResources 文件中,它僅僅是一個(gè) plist 格式文件。
這個(gè)列表文件中不光包含了文件和它們的簽名的列表,還包含了一系列規(guī)則,這些規(guī)則決定了哪些資源文件應(yīng)當(dāng)被設(shè)置簽名。伴隨 OS X 10.10 DP 5 和 10.9.5 版本的發(fā)布,蘋(píng)果改變了代碼簽名的格式,也改變了有關(guān)資源的規(guī)則。如果你使用10.9.5或者更高版本的 codesign 工具,在 CodeResources 文件中會(huì)有4個(gè)不同區(qū)域,其中的 rules 和 files 是為老版本準(zhǔn)備的,而 files2 和 rules2 是為新的第二版的代碼簽名準(zhǔn)備的。最主要的區(qū)別是在新版本中你無(wú)法再將某些資源文件排除在代碼簽名之外,在過(guò)去你是可以的,只要在被設(shè)置簽名的程序包中添加一個(gè)名為 ResourceRules.plist 的文件,這個(gè)文件會(huì)規(guī)定哪些資源文件在檢查代碼簽名是否完好時(shí)應(yīng)該被忽略。但是在新版本的代碼簽名中,這種做法不再有效。所有的代碼文件和資源文件都必須 設(shè)置簽名,不再可以有例外。在新版本的代碼簽名規(guī)定中,一個(gè)程序包中的可執(zhí)行程序包,例如擴(kuò)展 (extension),是一個(gè)獨(dú)立的需要設(shè)置簽名的個(gè)體,在檢查簽名是否完整時(shí)應(yīng)當(dāng)被單獨(dú)對(duì)待。
有些hacker可能會(huì)通過(guò)篡改你的程序包(包括資源文件和二進(jìn)制代碼)加入一些廣告或則修改你程序的邏輯,然后重新簽名打包,由于第三方hacker獲取不到簽名證書(shū)的私鑰,因此會(huì)替換掉程序包中簽名相關(guān)的文件embedded.mobileprovision,我們可以直接檢查此文件是否被修改,來(lái)判斷是否被二次打包,如果程序被篡改,則退出程序。
檢測(cè)embedded.mobileprovision是否被篡改:
// 校驗(yàn)值,可通過(guò)上一次打包獲取
#define PROVISION_HASH @"w2vnN9zRdwo0Z0Q4amDuwM2DKhc="
static NSDictionary * rootDic=nil;
void checkSignatureMsg()
{
NSString *newPath=[[NSBundle mainBundle]resourcePath];
if (!rootDic) {
rootDic = [[NSDictionary alloc] initWithContentsOfFile:[newPath stringByAppendingString:@"/_CodeSignature/CodeResources"]];
}
NSDictionary*fileDic = [rootDic objectForKey:@"files2"];
NSDictionary *infoDic = [fileDic objectForKey:@"embedded.mobileprovision"];
NSData *tempData = [infoDic objectForKey:@"hash"];
NSString *hashStr = [tempData base64EncodedStringWithOptions:0];
if (![PROVISION_HASH isEqualToString:hashStr]) {
abort();//退出應(yīng)用
}
}
參考
https://blog.csdn.net/u011656331/article/details/81120420
https://mp.weixin.qq.com/s/zD5EtFpSzKQ0h-ORTC9WqQ