iOS——APP安全防護

本文只是用于記錄,在工作中遇到有人攻擊我們APP,我們所做的一些事。

  • 在無意間,查看數(shù)據(jù)庫數(shù)據(jù),發(fā)現(xiàn)有許多數(shù)據(jù),像是利用腳本提交的。至于怎么看出來的,在這兒不好說,畢竟是公司項目。但是有一點是可以確定的,就是他的設備是iOS設備,那么說明我們的APP存在安全隱患。
  • 最開始,我們的APP,數(shù)據(jù)層沒有進行任何形式的加密,后端也沒有對ip進行校驗,同時網(wǎng)絡層,也沒有做HTTPS加密,同時,APP本地更沒有做安全加固。

于是乎,我們想到的第一件事,就是對重要的數(shù)據(jù)進行加密處理,請求包數(shù)據(jù)進行了MD5(可參考:http://www.itdecent.cn/p/d7fcb503bd15),本地用戶敏感數(shù)據(jù)進行鑰匙串保存(demo:https://github.com/deng690990/SF_KeyChain),同時我們?yōu)榫W(wǎng)絡請求加上了HTTPS證書校驗。但是很悲劇的事情發(fā)生了,我們做的一切僅僅維持了一周,又有腳本數(shù)據(jù)出現(xiàn)在了數(shù)據(jù)庫。

這個時候,我們意識到,別人可能是對我們的IPA包植入了動態(tài)庫,同時結(jié)合靜態(tài)分析,把我們app的主要邏輯代碼摸清了,以至于人家能用腳本直接提交數(shù)據(jù)。下面我就來談談我們是怎么一步步防住了對方。

一、埋點,采集數(shù)據(jù)。

埋點可以用三方,也可以用自己的服務器,友盟就有自定義事件采集。

  • 埋點需要采集的信息主要是:重簽名,越獄設備進行的操作,還有動態(tài)調(diào)試等。
一、檢測越獄機:

封裝一個工具類,頭文件導入:

//防越獄相關
#import <sys/stat.h>
#import <dlfcn.h>
#import <stdlib.h>
#import <mach-o/dyld.h>
#define kApplicationIdentifier @"這里是你的證書的組織標識,可在本地鑰匙串查看"
/**
 以下方法,防止越獄設備,防止hook,防止代碼注入,命名故意錯誤方式命名,防止別人逆向
 */
+ (BOOL)Youmeng1 {
    //可能存在hook了NSFileManager方法,此處用底層C stat去檢測
    //    /Library/MobileSubstrate/MobileSubstrate.dylib 最重要的越獄文件,幾乎所有的越獄機都會安裝MobileSubstrate
    //    /Applications/Cydia.app/ /var/lib/cydia/絕大多數(shù)越獄機都會安裝
    struct stat stat_info;
    if (0 == stat("/Library/MobileSubstrate/MobileSubstrate.dylib", &stat_info)) {
        return YES;
    }
    if (0 == stat("/Applications/Cydia.app", &stat_info)) {
        return YES;
    }
    if (0 == stat("/var/lib/cydia/", &stat_info)) {
        return YES;
    }
    if (0 == stat("/var/cache/apt", &stat_info)) {
        return YES;
    }
    if (0 == stat("/var/lib/apt", &stat_info)) {
        return YES;
    }
    if (0 == stat("/etc/apt", &stat_info)) {
        return YES;
    }
    if (0 == stat("/bin/bash", &stat_info)) {
        return YES;
    }
    if (0 == stat("/bin/sh", &stat_info)) {
        return YES;
    }
    if (0 == stat("/usr/sbin/sshd", &stat_info)) {
        return YES;
    }
    if (0 == stat("/usr/libexec/ssh-keysign", &stat_info)) {
        return YES;
    }
    if (0 == stat("/etc/ssh/sshd_config", &stat_info)) {
        return YES;
    }
    return NO;
}
+ (BOOL)Youmeng2 {
    //可能存在stat也被hook了,可以看stat是不是出自系統(tǒng)庫,有沒有被攻擊者換掉
    //這種情況出現(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)庫,肯定被攻擊了
        if (strcmp(dylib_info.dli_fname, "/usr/lib/system/libsystem_kernel.dylib")) {   //不相等,肯定被攻擊了,相等為0
            return YES;
        }
    }
    return NO;
}
+ (BOOL)Youmeng3 {
    //列出所有已鏈接的動態(tài)庫:
    //通常情況下,會包含越獄機的輸出結(jié)果會包含字符串: 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"]) {
            return YES;
        }
    }
    return NO;
}
+ (BOOL)Youmeng4 {
    //如果攻擊者給MobileSubstrate改名,但是原理都是通過DYLD_INSERT_LIBRARIES注入動態(tài)庫
    //那么可以,檢測當前程序運行的環(huán)境變量
    char *env = getenv("DYLD_INSERT_LIBRARIES");
    if (env != NULL) {
        return YES;
    }
    return NO;
}
  • 這里我是故意對方法名亂命名,是為了混淆逆向人員
二、檢測重簽名:
/**
 以下是防重簽的方法
 */
+(void)Youmeng5{
    //重簽名檢測
    NSString *teamIdentifier = bundleTeamIdentifier();
    if ([teamIdentifier isNotEmptyObject] && ![teamIdentifier isEqualToString:kApplicationIdentifier]) {
        [MobClick updateDataWithString:@"APPResign"];
        #ifdef __arm64__
            asm volatile(
                         "mov x0,#0\n"
                         "mov w16,#1\n"
                         "svc #0x80\n"
                         );
        #endif
        #ifdef __arm__
            asm volatile(
                         "mov r0,#0\n"
                         "mov r12,#1\n"
                         "svc #0x80\n"
                         );
        #endif
    }
}
/// 拿到證書里的組織標識
static NSString *bundleTeamIdentifier(void)
{
    NSString *mobileProvisionPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"embedded.mobileprovision"];
    FILE *fp=fopen([mobileProvisionPath UTF8String],"r");
    char ch;
    if(fp==NULL) {
        return NULL;
    }
    NSMutableString *str = [NSMutableString string];
    while((ch=fgetc(fp))!=EOF) {
        [str appendFormat:@"%c",ch];
    }
    fclose(fp);

    NSString *teamIdentifier = nil;
    NSRange teamIdentifierRange = [str rangeOfString:@"<key>com.apple.developer.team-identifier</key>"];
    if (teamIdentifierRange.location != NSNotFound) {
        NSInteger location = teamIdentifierRange.location + teamIdentifier.length;
        NSInteger length = [str length] - location;
        if (length > 0 && location >= 0) {
            NSString *newStr = [str substringWithRange:NSMakeRange(location, length)];;
            NSArray *val = [newStr componentsSeparatedByString:@"</string>"];
            NSString *v = [val firstObject];
            NSRange startRange = [v rangeOfString:@"<string>"];

            NSInteger newLocation = startRange.location + startRange.length;
            NSInteger newLength = [v length] - newLocation;
            if (newLength > 0 && location >= 0) {
                teamIdentifier = [v substringWithRange:NSMakeRange(newLocation, newLength)];
            }
        }
    }
    return teamIdentifier;
}
三、混淆方法名,防止別人class_dump。這個可以用宏定義去混淆,也可以用三方庫。
四、防止動態(tài)調(diào)試:
  • 防止反調(diào)試的方法最好用一個私有庫來做,為什么呢?將代碼放到framework里,逆向成本高得多,當然并不是不可能破解,畢竟沒有絕對的安全。
  • 頭文件導入#import <sys/sysctl.h>
static __attribute__((always_inline)) void workwell() {
#ifdef __arm64__
    asm volatile(
                 "mov x0,#0\n"
                 "mov w16,#1\n"
                 "svc #0x80\n"
                 );
#endif
    
#ifdef __arm__
    asm volatile(
                 "mov r0,#0\n"
                 "mov r12,#1\n"
                 "svc #0x80\n"
                 );
#endif
}

//檢測調(diào)試
BOOL isDlnaInitialize(){
    int name[4];//里面放字節(jié)碼。查詢的信息
    name[0] = CTL_KERN;//內(nèi)核查詢
    name[1] = KERN_PROC;//查詢進程
    name[2] = KERN_PROC_PID;//傳遞的參數(shù)是進程的ID
    name[3] = getpid();//PID的值
    
    struct kinfo_proc info;//接受查詢結(jié)果的結(jié)構(gòu)體
    size_t info_size = sizeof(info);
    if(sysctl(name, 4, &info, &info_size, 0, 0)){
//        NSLog(@"查詢失敗");
        return NO;
    }
    //看info.kp_proc.p_flag 的第12位。如果為1,表示調(diào)試狀態(tài)。
    //(info.kp_proc.p_flag & P_TRACED)
    
    return ((info.kp_proc.p_flag & P_TRACED) != 0);
}

static dispatch_source_t timer;
void dlnaInitialize(){
    timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 30.0 * NSEC_PER_SEC, 0.0 * NSEC_PER_SEC);
    dispatch_source_set_event_handler(timer, ^{
        if (isDlnaInitialize()) {
            workwell();
        }
    });
    dispatch_resume(timer);
}

static __attribute__((always_inline)) void UPnPInitialize() {
#ifdef __arm64__
    asm volatile(
                 "mov x0,#26\n"
                 "mov x1,#31\n"
                 "mov x2,#0\n"
                 "mov x3,#0\n"
                 "mov x16,#0\n"http://中斷根據(jù)x16 里面的值,跳轉(zhuǎn)syscall
                 "svc #0x80\n"http://這條指令就是觸發(fā)中斷(系統(tǒng)級別的跳轉(zhuǎn)?。?    );
#endif
}

+ (void)load {
    UPnPInitialize();
    dlnaInitialize();
}

  • 強調(diào)一下這幾句代碼的作用:利用匯編代碼,強制退出程序,比exit(0)要安全一些,至少不會被hook。
#ifdef __arm64__
    asm volatile(
                     "mov x0,#0\n"
                     "mov w16,#1\n"
                     "svc #0x80\n"
                     );
    #endif
    #ifdef __arm__
        asm volatile(
                     "mov r0,#0\n"
                     "mov r12,#1\n"
                     "svc #0x80\n"
                     );
    #endif
}

這是目前我們做了的技術防護。還有一部分是邏輯防護,這個就得根據(jù)項目的實際情況來,比如我們的項目,就是一個用戶只能一臺設備一個ip,當用戶提交的數(shù)據(jù)不滿足任意條件就會失敗。同時,我們在登錄模塊加了圖形驗證碼,還有短信驗證碼,就是一旦檢測到用戶換了設備,只能手機驗證碼登錄。
本來我們還想做防hook操作,可是這一步已經(jīng)攔住了破我們app的人。這個時候,我們就對數(shù)據(jù)庫里,腳本提交的數(shù)據(jù),做了清理,進一步讓那些依靠逆向人員提交假數(shù)據(jù)的人和逆向人員之間出現(xiàn)了信任問題,從此,太平了。希望以后我們的APP能一帆風順。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 有時候公司APP有安全方面的要求,那APP安全防護到底有幾種方式,應該如何去做?下面介紹簡單的幾種供參考。 一、U...
    杰小冷_4957閱讀 1,778評論 0 1
  • 一、iOS安全攻防 1.本地數(shù)據(jù)攻防 1.1 文件存儲 每個App的文件都保存在一個沙盒目錄中。每個沙盒都包含Do...
    zgsddzwj閱讀 1,669評論 3 4
  • 谷歌在自己官方博客發(fā)布公告稱,將從今年6月開始禁止網(wǎng)絡廣告推廣數(shù)字貨幣、ICO,以及其他投機性金融工具。 過去幾年...
    64b274c6b741閱讀 146評論 0 0
  • 尋訪訊(李彬) 近日,北和鎮(zhèn)一位小女孩為自己七歲患病的妹妹籌款和烏石鎮(zhèn)三小孩被燒傷的事,以及其家庭遭遇,...
    生活主題閱讀 836評論 1 1
  • 登上華山中峰抬頭望去,但見奇峰聳立,直插天際,令人望而生畏。關鍵是還有一千多米才能登頂,腿肚子已經(jīng)開始轉(zhuǎn)筋了,只好...
    張瀾風閱讀 233評論 0 2

友情鏈接更多精彩內(nèi)容