iOS 攻防(一)DYLD_INSERT_LIBRARIES

上篇文章中已經清楚了Tweak是通過DYLD_INSERT_LIBRARIES來插入動態(tài)庫的稽亏,那么它是怎么做到的呢到旦?這就需要去dyld源碼中探究了匆赃。

一北戏、 DYLD_INSERT_LIBRARIES原理

由于dyld源碼中b不同版本有變動师郑,需要分別看下新老版本的實現环葵。dyld源碼

1.1 dyld-519.2.2 源碼

打開dyld源碼工程,搜索DYLD_INSERT_LIBRARIES關鍵字宝冕,在dyld.cpp5906行有如下代碼:

// 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_INSERT_LIBRARIES不為空就循環(huán)加載插入動態(tài)庫张遭。

繼續(xù)查找在5692行:

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

這里判斷進程如果受限制(processIsRestricted不為空)執(zhí)行pruneEnvironmentVariablespruneEnvironmentVariables會移除DYLD_INSERT_LIBRARIES中的數據地梨,相當于被清空了帝璧。這樣插入的動態(tài)庫就不會被加載了。

既然越獄插件是通過DYLD_INSERT_LIBRARIES插入的湿刽,那么只要讓自己的進程受限就能起到保護作用了的烁。

搜索processIsRestricted = true是在4696行設置值的:

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

issetugid不能在上架的App中設置,那么就只能設置hasRestrictedSegment了诈闺,這里傳入的參數是主程序:

static bool hasRestrictedSegment(const macho_header* mh)
{
    //load command 數量
    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\n", seg->segname);
                //讀取__RESTRICT SEGMENT
                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) {
                        //讀取__restrict SECTION
                        if (strcmp(sect->sectname, "__restrict") == 0) 
                            return true;
                    }
                }
            }
            break;
        }
        cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
    }
        
    return false;
}

這段代碼的意思是判斷load commands中有沒有__RESTRICT SECTION渴庆,SECTION中有沒有__restrict SEGMENT

image.png

也就是說只要有這個SECTION就會開啟進程受限了雅镊。

1.2 dyld-851.27源碼

dyld2.cpp7120行中仍然有DYLD_INSERT_LIBRARIES的判斷:

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

processIsRestricted變成了一個函數(5391):

bool processIsRestricted()
{
#if TARGET_OS_OSX
    return !gLinkContext.allowEnvVarsPath;
#else
    return false;
#endif
}

這里可以看到只在OSX下才有效襟雷。

6667行也只有OSX下才有可能清空環(huán)境變量:

#if TARGET_OS_OSX
    if ( !gLinkContext.allowEnvVarsPrint && !gLinkContext.allowEnvVarsPath && !gLinkContext.allowEnvVarsSharedCache ) {
        pruneEnvironmentVariables(envp, &apple);
        // set again because envp and apple may have changed or moved
        setContext(mainExecutableMH, argc, argv, envp, apple);
    }
    else
#endif
    {
        checkEnvironmentVariables(envp);
        defaultUninitializedFallbackPaths(envp);
    }

hasRestrictedSegment也變成了OSX下專屬:

#if TARGET_OS_OSX
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\n", 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;
}
#endif

結論:iOS 10以前dyld會判斷主程序是否有__RESTRICT,__restrict來決定是否加載DYLD_INSERT_LIBRARIESiOS 10及以后并不會進行判斷直接進行了加載仁烹。

二耸弄、 DYLD_INSERT_LIBRARIES 攻防

2.1 iOS10以前攻防

2.1.1 RESTRIC段防護

Other Linker Flags中輸入-Wl,-sectcreate,__RESTRICT,__restrict,/dev/null

RESTRICT設置

sectcreate:意思是創(chuàng)建一個SEGEMNT__RESTRICT,__restrict,值為/dev/null

這么配置后在MachO文件中就有對應的SECTION了:

image.png

這樣通過DYLD_INSERT_LIBRARIES注入的庫就無效了卓缰。越獄手機上的插件就無效了计呈。(僅在iOS 10以下有效)。

2.1.2 修改二進制破解

針對RESTRIC的防護可以用二進制修改器將段名稱修改掉征唬,就可以繞過檢測了捌显。
修改Data中的任意一位這個值就變了:

image.png

image.png

image.png

修改后重簽就可以了。

2.1.3 防止RESTRICT被修改

針對RESTRICT被修改可以在代碼中判斷MachO中是否有對應的RESTRIC总寒,如果沒有就證明被修改了扶歪。參考dyld源碼修改判斷如下:

#import <mach-o/dyld.h>

#if __LP64__
    #define macho_header              mach_header_64
    #define LC_SEGMENT_COMMAND        LC_SEGMENT_64
    #define LC_SEGMENT_COMMAND_WRONG LC_SEGMENT
    #define LC_ENCRYPT_COMMAND        LC_ENCRYPTION_INFO
    #define macho_segment_command    segment_command_64
    #define macho_section            section_64
#else
    #define macho_header              mach_header
    #define LC_SEGMENT_COMMAND        LC_SEGMENT
    #define LC_SEGMENT_COMMAND_WRONG LC_SEGMENT_64
    #define LC_ENCRYPT_COMMAND        LC_ENCRYPTION_INFO_64
    #define macho_segment_command    segment_command
    #define macho_section            section
#endif

static bool hp_hasRestrictedSegment(const struct macho_header* mh) {
    const uint32_t cmd_count = mh->ncmds;
    const struct load_command* const cmds = (struct load_command*)(((char*)mh)+sizeof(struct 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;
                printf("seg->segname: %s\n",seg->segname);
                //dyld::log("seg name: %s\n", 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) {
                        printf("sect->sectname: %s\n",sect->sectname);
                        if (strcmp(sect->sectname, "__restrict") == 0)
                            return true;
                    }
                }
            }
            break;
        }
        cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
    }
    return false;
}

調用:

+ (void)load {
    //獲取主程序 macho_header
    const struct macho_header *header = _dyld_get_image_header(0);
    if (hp_hasRestrictedSegment(header)) {
        NSLog(@"沒有修改");
    } else {
        NSLog(@"被修改了");
    }
}

這樣就能知道RESTRICT有沒有被修改。要Hook檢測邏輯就需要找到hp_hasRestrictedSegment函數的地址進行inline hook摄闸∩屏或者找到調用hp_hasRestrictedSegment的地方妹萨,那么在檢測過程中就不能有明顯的特征。一般將結果告訴服務端炫欺∶吒保或者做一些破壞功能的邏輯,比如網絡請求相關的內容竣稽。

2.2 iOS10及以后攻防

2.2.1 使用DYLD源碼防護(黑白名單)

既然iOS10以上系統(tǒng)不進行判斷檢測了囱怕,那么我們可以自己掃描判斷哪些應該被加載哪些不能被加載。

#import <mach-o/dyld.h>

const char *whiteListLibStrs =
"/usr/lib/substitute-inserter.dylib/System/Library/Frameworks/Foundation.framework/Foundation/usr/lib/libobjc.A.dylib/usr/lib/libSystem.B.dylib/System/Library/Frameworks/UIKit.framework/UIKit/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation/System/Library/PrivateFrameworks/CoreAutoLayout.framework/CoreAutoLayout/usr/lib/libcompression.dylib/System/Library/Frameworks/CFNetwork.framework/CFNetwork/usr/lib/libarchive.2.dylib/usr/lib/libicucore.A.dylib/usr/lib/libxml2.2.dylib/usr/lib/liblangid.dylib/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit/usr/lib/libCRFSuite.dylib/System/Library/PrivateFrameworks/SoftLinking.framework/SoftLinking/usr/lib/libc++abi.dylib/usr/lib/libc++.1.dylib/usr/lib/system/libcache.dylib/usr/lib/system/libcommonCrypto.dylib/usr/lib/system/libcompiler_rt.dylib/usr/lib/system/libcopyfile.dylib/usr/lib/system/libcorecrypto.dylib";

const char *blackListLibStrs =
"/usr/lib/libsubstitute.dylib/usr/lib/substitute-loader.dylib/usr/lib/libsubstrate.dylib/Library/MobileSubstrate/DynamicLibraries/RHRevealLoader";

void imageListCheck() {
    //進程依賴的庫數量
    int count = _dyld_image_count();
    //第一個為自己毫别。過濾掉娃弓,因為每次執(zhí)行的沙盒路徑不一樣。
    for (int i = 1; i < count; i++) {
        const char *image_name =  _dyld_get_image_name(i);
//        printf("%s",image_name);
        //黑名單檢測
        if (strstr(blackListLibStrs, image_name)) {//不在白名單
            printf("image_name in black list: %s\n",image_name);
            break;
        }
        //白名單檢測
        if (!strstr(whiteListLibStrs, image_name)) {
            printf("image_name not in white list: %s\n",image_name);
        }
    }
}

調用:

+ (void)load {
    imageListCheck();
}
  • 白名單可以直接通過_dyld_get_image_name獲取岛宦,這里和系統(tǒng)版本有關台丛。需要跑支持的系統(tǒng)版本獲取得到并集。維護起來比較麻煩砾肺。
  • 黑名單中可以將一些越獄庫和檢測到的異常庫放入其中挽霉。
  • 一般檢測到問題直接上報服務端。不要直接表現出異常变汪。

黑白名單一般都通過服務端下發(fā)侠坎,黑名單直接檢測出問題上報服務端處理,白名單維護用來檢測上報未知的庫供分析更新黑白名單裙盾。

這種防護方式可以通過fishhook Hook _dyld_image_count_dyld_get_image_name來做排查是哪塊做的檢測從而去繞過实胸。

  • 對于檢測代碼最好混淆函數名稱。
  • 返回值不要返回一個布爾值番官,函數被hook之后或者被修改成返回YES 之后很多判斷代碼都沒用了庐完。最好返回特定字符串加密這種。
  • 檢測到被注入時不要exit(0)完事徘熔,太明顯了门躯,這種很容易被繞過。攻防的核心不在于防護技術酷师,而在于會不會被對方發(fā)現讶凉。微信的做法就是上報服務端封號處理。
  • 在檢測到時可以悄悄對業(yè)務邏輯做一些處理窒升,比如網絡請求正常返回但是頁面顯示異匙罕椋或者功能不全等。

沒有絕對安全的代碼饱须,只不過在與會不會被對方發(fā)現以及破解的代價。如果破解代價大于收益很少有人去破解的台谊。

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末蓉媳,一起剝皮案震驚了整個濱河市譬挚,隨后出現的幾起案子,更是在濱河造成了極大的恐慌酪呻,老刑警劉巖减宣,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異玩荠,居然都是意外死亡漆腌,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門阶冈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來闷尿,“玉大人,你說我怎么就攤上這事女坑√罹撸” “怎么了?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵匆骗,是天一觀的道長劳景。 經常有香客問我,道長碉就,這世上最難降的妖魔是什么盟广? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮瓮钥,結果婚禮上衡蚂,老公的妹妹穿的比我還像新娘。我一直安慰自己骏庸,他們只是感情好毛甲,可當我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著具被,像睡著了一般玻募。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上一姿,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天七咧,我揣著相機與錄音,去河邊找鬼叮叹。 笑死艾栋,一個胖子當著我的面吹牛,可吹牛的內容都是我干的蛉顽。 我是一名探鬼主播蝗砾,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了悼粮?” 一聲冷哼從身側響起闲勺,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎扣猫,沒想到半個月后菜循,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡申尤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年癌幕,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昧穿。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡勺远,死狀恐怖,靈堂內的尸體忽然破棺而出粤咪,到底是詐尸還是另有隱情谚中,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布寥枝,位于F島的核電站宪塔,受9級特大地震影響,放射性物質發(fā)生泄漏囊拜。R本人自食惡果不足惜某筐,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望冠跷。 院中可真熱鬧南誊,春花似錦、人聲如沸蜜托。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽橄务。三九已至幔托,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蜂挪,已是汗流浹背重挑。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留棠涮,地道東北人谬哀。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像严肪,于是被迫代替她去往敵國和親史煎。 傳聞我的和親對象是個殘疾皇子谦屑,可洞房花燭夜當晚...
    茶點故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內容