如何檢測(cè)越獄手機(jī)一直是iOS應(yīng)用安全防護(hù)的第一道門(mén)檻。
在應(yīng)用開(kāi)發(fā)過(guò)程中,我們希望知道設(shè)備是否越獄,正以什么權(quán)限運(yùn)行程序,好對(duì)應(yīng)采取一些防御和安全提示措施。
一般我們通過(guò)一些常規(guī)的防御性代碼,去做這種檢測(cè),當(dāng)然,這樣的檢測(cè)有一定的誤報(bào)概率,但是對(duì)于APP的開(kāi)發(fā)者來(lái)講,需要確定一個(gè)原則,哪怕是越獄手機(jī)檢測(cè)成未越獄,也不能將未越獄的手機(jī)檢測(cè)成越獄手機(jī)。
首先我們進(jìn)行常規(guī)的文件路徑檢測(cè):
- (void)isOk0 {
NSString *cydiaPath = @"/Applications/Cydia.app";
NSString *aptPath = @"/private/var/lib/apt/";
NSString *applications = @"/User/Applications/";
NSString *Mobile = @"/Library/MobileSubstrate/MobileSubstrate.dylib";
NSString *bash = @"/bin/bash";
NSString *sshd =@"/usr/sbin/sshd";
NSString *sd = @"/etc/apt";
if ([[NSFileManager defaultManager] fileExistsAtPath:cydiaPath]) {
exit(0);
}
if ([[NSFileManager defaultManager] fileExistsAtPath:aptPath]) {
exit(0);
}
if([[NSFileManager defaultManager] fileExistsAtPath:cydiaPath]) {
exit(0);
}
if([[NSFileManager defaultManager] fileExistsAtPath:aptPath]) {
exit(0);
}
if ([[NSFileManager defaultManager] fileExistsAtPath:applications]){
exit(0);
}
if ([[NSFileManager defaultManager] fileExistsAtPath:Mobile]){
exit(0);
}
if ([[NSFileManager defaultManager] fileExistsAtPath:bash]){
exit(0);
}
if ([[NSFileManager defaultManager] fileExistsAtPath:sshd]){
exit(0);
}
if ([[NSFileManager defaultManager] fileExistsAtPath:sd]){
exit(0);
}
}
這里直接exit(0)是為了防止一些繞過(guò)策略,比如替換方法,導(dǎo)致檢測(cè)代碼被繞過(guò)。如果檢測(cè)到手機(jī)越獄,直接退出軟件。
當(dāng)然了,攻擊者可以直接通過(guò)替換系統(tǒng)的fileExistsAtPath函數(shù),讓他一直返回false,從而繞過(guò)軟件路徑的檢測(cè)。這時(shí)候就需要一些C語(yǔ)言的函數(shù)去做更加精確的檢測(cè)。
以下函數(shù)需要引用頭文件:
#import <sys/stat.h>
#import <dlfcn.h>
#import <stdlib.h>
#import <mach-o/dyld.h>
- (void)isOK1 {
//可能存在hook了NSFileManager方法,此處用底層C stat去檢測(cè)
// /Library/MobileSubstrate/MobileSubstrate.dylib 最重要的越獄文件,幾乎所有的越獄機(jī)都會(huì)安裝MobileSubstrate
// /Applications/Cydia.app/ /var/lib/cydia/絕大多數(shù)越獄機(jī)都會(huì)安裝
struct stat stat_info;
if (0 == stat("/Library/MobileSubstrate/MobileSubstrate.dylib", &stat_info)) {
exit(0);
}
if (0 == stat("/Applications/Cydia.app", &stat_info)) {
exit(0);
}
if (0 == stat("/var/lib/cydia/", &stat_info)) {
exit(0);
}
if (0 == stat("/var/cache/apt", &stat_info)) {
exit(0);
}
if (0 == stat("/var/lib/apt", &stat_info)) {
exit(0);
}
if (0 == stat("/etc/apt", &stat_info)) {
exit(0);
}
if (0 == stat("/bin/bash", &stat_info)) {
exit(0);
}
if (0 == stat("/bin/sh", &stat_info)) {
exit(0);
}
if (0 == stat("/usr/sbin/sshd", &stat_info)) {
exit(0);
}
if (0 == stat("/usr/libexec/ssh-keysign", &stat_info)) {
exit(0);
}
if (0 == stat("/etc/ssh/sshd_config", &stat_info)) {
exit(0);
}
}
可能存在stat也被hook了,可以看stat是不是出自系統(tǒng)庫(kù),有沒(méi)有被攻擊者換掉,這種情況出現(xiàn)的可能性不大,如果結(jié)果不是 /usr/lib/system/libsystem_kernel.dylib 的話(huà),那就100%被攻擊了。
- (void)isOK2 {
//可能存在stat也被hook了,可以看stat是不是出自系統(tǒng)庫(kù),有沒(méi)有被攻擊者換掉
//這種情況出現(xiàn)的可能性很小
int ret;
Dl_info dylib_info;
int (*func_stat)(const char *,struct stat *) = stat;
if ((ret = dladdr(&func_stat, &dylib_info))) {
NSLog(@"lib:%s",dylib_info.dli_fname); //如果不是系統(tǒng)庫(kù),肯定被攻擊了
if (strcmp(dylib_info.dli_fname, "/usr/lib/system/libsystem_kernel.dylib")) { //不相等,肯定被攻擊了,相等為0
exit(0);
}
}
}
可以檢索自己的應(yīng)用程序是否被鏈接了異常動(dòng)態(tài)庫(kù),列出所有已鏈接的動(dòng)態(tài)庫(kù),通常情況下,會(huì)包含越獄機(jī)的輸出結(jié)果會(huì)包含字符串: Library/MobileSubstrate/MobileSubstrate.dylib :
- (void)isOK3 {
//列出所有已鏈接的動(dòng)態(tài)庫(kù):
//通常情況下,會(huì)包含越獄機(jī)的輸出結(jié)果會(huì)包含字符串: Library/MobileSubstrate/MobileSubstrate.dylib 。
uint32_t count = _dyld_image_count();
for (uint32_t i = 0 ; i < count; ++i) {
NSString *name = [[NSString alloc]initWithUTF8String:_dyld_get_image_name(i)];
if ([name containsString:@"Library/MobileSubstrate/MobileSubstrate.dylib"]) {
exit(0);
}
}
}
如果攻擊者給MobileSubstrate改名,但是原理都是通過(guò)DYLD_INSERT_LIBRARIES注入動(dòng)態(tài)庫(kù),我們可以查看一下環(huán)境變量:
- (void)isOK4 {
//如果攻擊者給MobileSubstrate改名,但是原理都是通過(guò)DYLD_INSERT_LIBRARIES注入動(dòng)態(tài)庫(kù)
//那么可以,檢測(cè)當(dāng)前程序運(yùn)行的環(huán)境變量
char *env = getenv("DYLD_INSERT_LIBRARIES");
if (env != NULL) {
exit(0);
}
}
雖然這里用到的是C語(yǔ)言檢測(cè)函數(shù),但這些函數(shù)被hook的可能性也是存在的,比如fishhook。。
如果真有大佬手動(dòng)hook這些函數(shù)或者直接修改二進(jìn)制的話(huà),那也沒(méi)啥好防的,大佬們隨便吧。。
所以也印證了那句話(huà):沒(méi)有絕對(duì)的安全,你唯一能做的就是拖延攻擊者的腳步。
對(duì)于這些函數(shù),不建議單獨(dú)寫(xiě)方法,容易被hook掉,所以最好是寫(xiě)在不能被hook的函數(shù)里,比如application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions。。。
誰(shuí)TM會(huì)hook程序的初始化函數(shù)。。
另外越獄檢測(cè)函數(shù)最好不要出現(xiàn)Jailbreak,或者canijailbreak,或者AntiJailbreak這種字段,很容易被定位到。