iOS應用程式碼注入防護

xiaoxian發表於2019-01-16

在應用開發過程中,我們不僅僅需要完成正常的業務邏輯,考慮應用效能、程式碼健壯相關的問題,我們有時還需要考慮到應用安全的問題。
那麼應用安全的問題涉及到很多方面。比如防止靜態分析的,程式碼混淆、邏輯混淆;防止重簽名的,應用ID檢測、甚至是程式碼的HASH檢測等等。那麼這篇文章我想聊聊關於程式碼的注入檢測,因為發現隨著iOS系統的更新,我們防護的手段發生了一些變化。

程式碼注入的方式

程式碼注入的方式大致分為兩種

  • 越獄注入:通過修改DYLD_INSERT_LIBRARIES 環境變數的值,來插入動態庫並執行
  • 非越獄注入:

    • 直接將自定義的Framwork或者dylib庫打包進入APP並重簽名。
    • 利用yololib修改MachO檔案,新增庫路徑.在應用啟動時,dyld會載入並執行.

早期防護方式

在工程的Build Settings中找到Other Linker Flages 並新增欄位
-Wl,-sectcreate,__RESTRICT,__raestrict,/dev/null
此操作的作用是在可執行檔案中新增一個Section.我們使用MachOView分析如下:

image

當MachO檔案中擁有這個欄位,那麼我們通過越獄環境插入動態庫的方式就會失效.起到防護的作用.其原理在DYLD原始碼中可以分析到.

dyld原始碼分析

首先這裡分析的DYLD原始碼版本是519.2.2版本.
我們可以通過檢索DYLD_INSERT_LIBRARIES定位到_main函式載入插入動態庫的程式碼如下.

            // load any inserted libraries
        if  ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
            for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 
                loadInsertedDylib(*lib);
        }

但是早在這個環境變數判斷之前,dyld已經做了一個判斷

    if ( gLinkContext.processIsRestricted ) {
        pruneEnvironmentVariables(envp, &apple);
        // set again because envp and apple may have changed or moved
        setContext(mainExecutableMH, argc, argv, envp, apple);
    }

如果判斷出程式是restricted!也就是當前程式是限制插入動態庫的!就會呼叫pruneEnvironmentVariables函式移除相關的環境變數.
那麼我們的processIsRestricted值什麼時候為true呢?
繼續分析原始碼可以發現兩個關鍵函式影響其值.其中 hasRestrictedSegment 函式專門檢測RESTRICT段

// any processes with setuid or setgid bit set or with __RESTRICT segment is restricted
    if ( issetugid() || hasRestrictedSegment(mainExecutableMH) ) {
        gLinkContext.processIsRestricted = true;
    }

通過註釋也能發現.任意程式的__RESTRICT段設定為restricted動態庫插入將被限制.
我們進入到processIsRestricted函式內,實現如下.

#if __MAC_OS_X_VERSION_MIN_REQUIRED
static bool hasRestrictedSegment(const macho_header* mh)
{
    const uint32_t cmd_count = mh->ncmds;
    const struct load_command* const cmds = (struct load_command*)(((char*)mh)+sizeof(macho_header));
    const struct load_command* cmd = cmds;
    for (uint32_t i = 0; i < cmd_count; ++i) {
        switch (cmd->cmd) {
            case LC_SEGMENT_COMMAND:
            {
                const struct macho_segment_command* seg = (struct macho_segment_command*)cmd;

                //dyld::log("seg name: %s
", seg->segname);
                if (strcmp(seg->segname, "__RESTRICT") == 0) {
                    const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command));
                    const struct macho_section* const sectionsEnd = &sectionsStart[seg->nsects];
                    for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
                        if (strcmp(sect->sectname, "__restrict") == 0) 
                            return true;
                    }
                }
            }
            break;
        }
        cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
    }

    return false;
}

所以通過新增Other Linker Flags 在MachO中設定RESTRICT段賦值為restricted可以用來防護越獄的程式碼注入.
但是新版的dyld原始碼中去掉了__RESTRICT檢測.從iOS10開始,這種防護手段已失效

DYLD_INSERT_LIBRARIES 檢測

那麼既然dyld載入過程不再檢測__RESTRICT段了我們就手動的檢測DYLD_INSERT_LIBRARIES環境變數.通過函式可檢視當前程式環境變數的值.

  char *env = getenv("DYLD_INSERT_LIBRARIES");
  NSLog(@"%s",env);

在沒有插入動態庫時,env為null.
那麼一旦為自己的應用寫入外掛時,我們就可以看到控制檯的輸出

2019-01-03 19:20:37.285 antiInject[7482:630392] /Library/MobileSubstrate/MobileSubstrate.dylib

白名單檢測

那麼上面的檢測只可以檢測越獄環境中的程式碼注入,在非越獄環境中,逆向工程師可以利用yololib工具注入動態庫.所以我們可以檢索一下自己的應用程式所載入的動態庫是否是我們源程式所有

bool HKCheckWhitelist(){

    int count = _dyld_image_count();
    for (int i = 0; i < count; i++) {
        //遍歷拿到庫名稱!
       const char * imageName = _dyld_get_image_name(i);
        //判斷是否在白名單內,應用本身的路徑是不確定的,所以要除外.
        if (!strstr(libraries, imageName)&&!strstr(imageName, "/var/mobile/Containers/Bundle/Application")) {
            printf("該庫非白名單之內!!
%s",imageName);
            return NO;
        }
    }
    return YES;
}

其中libraries變數是<q style=”box-sizing: border-box;”>白名單</q>.

相關文章