十三、iOS逆向之《越獄防護(hù)》

概敘

越獄防護(hù)是指防止別人修改自己的APP作出的防護(hù)手段鸟悴。

1.了解代碼注入方式

了解防護(hù)之前需要了解代碼注入的方式,針對(duì)性防護(hù)奖年。
代碼注入一般有兩種方式:

  1. 第一種是通過(guò)注入動(dòng)態(tài)庫(kù)改變machO文件頭的Load_Command字段细诸,注入dyld或者framework,讓dyld加載我們注入的動(dòng)態(tài)庫(kù)陋守。
  2. 第二種是通過(guò)配置DYLD_INSERT_LIBRARIES的環(huán)境變量揍堰,在dyld執(zhí)行過(guò)程中插入動(dòng)態(tài)庫(kù)。

Theos是一個(gè)逆向插件嗅义,它可以生成的dylib動(dòng)態(tài)庫(kù)后通過(guò)DYLD_INSERT_LIBRARIES的方式插入到app中屏歹。采用了第二種注入方式。
了解Theos的工作方式后之碗,我們現(xiàn)在對(duì)Theos進(jìn)行防護(hù)蝙眶!

2 防護(hù)

2.1 了解dyld的插入過(guò)程

在做防護(hù)前我們先了解dyld,dyld是app程序調(diào)用前的執(zhí)行代碼,他會(huì)幫我們啟動(dòng)庫(kù)幽纷、執(zhí)行machO文件式塌、并執(zhí)行程序代碼入口的程序,是蘋(píng)果開(kāi)源的代碼友浸》宄ⅲ可以百度搜索下載得到!

打開(kāi)dyld收恢,全局搜索DYLD_INSERT_LIBRARIES找到插入動(dòng)態(tài)庫(kù)的函數(shù)調(diào)用位置武学。
在第5909行有一個(gè)loadInsertedDyldb(*lib)加載動(dòng)態(tài)庫(kù)的函數(shù),此函數(shù)是調(diào)用動(dòng)態(tài)庫(kù)的函數(shù)伦意。

dyld在調(diào)用這個(gè)函數(shù)的前面做了很多判斷火窒!
但在5693行有一個(gè)重要的判斷條件是關(guān)鍵:gLinkContext.processIsRestricted

gLinkContext.processIsRestricted

這個(gè)函數(shù)開(kāi)關(guān)是限制動(dòng)態(tài)庫(kù)的開(kāi)關(guān),只要符合這個(gè)條件就無(wú)法插入動(dòng)態(tài)庫(kù)驮肉。
那在什么時(shí)候這個(gè)gLinkContext.processIsRestricted才會(huì)等于true呢熏矿?
全局搜索一下 processIsRestricted = true
在第一個(gè)結(jié)果中可以看到這個(gè)判斷條件等于true
processIsRestricted = true

而他前面還有兩個(gè)判斷條件( issetugid() || hasRestrictedSegment(mainExecutableMH) )
其中issetugid()是系統(tǒng)調(diào)用公用動(dòng)態(tài)庫(kù)的函數(shù),是私有的离钝,無(wú)法知其工作原理票编。
hasRestrictedSegment(mainExecutableMH)是可以看得到源碼的。
跳轉(zhuǎn)到hasRestrictedSegment(mainExecutableMH)函數(shù)中卵渴。
hasRestrictedSegment(mainExecutableMH)

可以看到這里面判斷了兩個(gè)東西 :__RESTRICT__restrict栏妖。
如果這兩個(gè)東西存在就返回true。就符合我們的預(yù)期奖恰。表示是限制的。

在看看此函數(shù)宛裕,在函數(shù)的入?yún)⒅杏幸粋€(gè)macho_header結(jié)構(gòu)體類型瑟啃,這個(gè)地方傳進(jìn)來(lái)的其實(shí)就是machO文件的頭。那么只需要在machO文件的頭添加這兩個(gè)字段就可以起到防護(hù)的效果揩尸!

2.2 開(kāi)始第一次防護(hù)

我們隨意新建一個(gè)demo蛹屿。
然后在Build Settings中搜索other linker flags。
并輸入值-Wl,-sectcreate,__RESTRICT,__restrict,/dev/null岩榆。
這樣就能完成那兩個(gè)字段错负,此乃固定寫(xiě)法。

-Wl,-sectcreate,__RESTRICT,__restrict,/dev/null

只需要在此處添加這一句話勇边,編譯后就會(huì)在ipa包的machO文件的頭部出現(xiàn)__RESTRICT__restrict判斷條件犹撒。

通過(guò)MachOView工具就可以看到效果

MachOView

對(duì)此就完成了通過(guò)DYLD_INSERT_LIBRARIES方式注入動(dòng)態(tài)庫(kù)的防護(hù)。

2.3 攻破防護(hù)粒褒!

你以為通過(guò)添加兩個(gè)字段就搞定防護(hù)了嗎识颊?太幼稚了。
我只要把這兩個(gè)字段隨便改掉奕坟,你這個(gè)防護(hù)一點(diǎn)用都沒(méi)有了祥款!
我們可以通過(guò)修改machO的二進(jìn)制進(jìn)行修改__RESTRICT__restrict清笨。
下面介紹一款工具:Synalyze It! Pro Mac。
Synalyze It! Pro是一款Mac上的16進(jìn)制編輯工具刃跛,能夠?qū)崟r(shí)編輯任何二進(jìn)制文件抠艾,簡(jiǎn)單易用,支持多種字符編碼桨昙、Python和Lua腳本检号、導(dǎo)出XML和TXT文件等等绊率,可謂反向工程的利器,對(duì)于經(jīng)常需要編輯二進(jìn)制文件的開(kāi)發(fā)者很有幫助脸狸!

Synalyze It! Pro Mac – 二進(jìn)制編輯器

通過(guò)此工具打開(kāi)ipa內(nèi)的machO文件,通過(guò)搜索直接可以定位到修改處藐俺!


__restrict

我們對(duì)其進(jìn)行隨意修改,如下圖欲芹。


修改二進(jìn)制

修改完成后記得保存!
然后我們?cè)诎研薷倪^(guò)對(duì)machO文件放回原來(lái)ipa內(nèi)菱父,覆蓋掉原來(lái)的machO文件颈娜。

覆蓋完后還是不能直接運(yùn)行,需要對(duì)其進(jìn)行重簽才能運(yùn)行在手機(jī)中官辽!
使用Monkey可以很輕松的完成重簽過(guò)程粟瞬!推薦使用。

這樣我們又再一次攻破machO的防線俗批!

2.4 再一次防護(hù)市怎!

既然被攻破岁忘,那就再防護(hù)一次臭觉!
你修改了__restrict字段,那我監(jiān)測(cè)一下你有沒(méi)有修改那兩個(gè)字段行不行蝠筑?
答案是肯定的什乙!
事實(shí)上mach-o程序是公開(kāi)使用的,導(dǎo)入頭文件臣镣!

#import <mach-o/dyld.h>
#import <mach-o/loader.h>

再把dyld的這段代碼復(fù)制過(guò)來(lái)忆某!

//
// Look for a special segment in the mach header. 
// Its presences means that the binary wants to have DYLD ignore
// DYLD_ environment variables.
//
#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\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

復(fù)制過(guò)來(lái)后出現(xiàn)了很多警告和錯(cuò)誤弃舒。大部分是類型的問(wèn)題癞埠,我們把相關(guān)的類型定義也復(fù)制過(guò)來(lái)!

#if __LP64__
    #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 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

這樣就少了很多警告苗踪!
其中有一個(gè)是入?yún)?code>macho_header的警告
實(shí)際上macho_header是有32位和64位架構(gòu)的讀取方式削锰。

/*
 * The 32-bit mach header appears at the very beginning of the object file for
 * 32-bit architectures.
 */
struct mach_header {
    uint32_t    magic;      /* mach magic number identifier */
    cpu_type_t  cputype;    /* cpu specifier */
    cpu_subtype_t   cpusubtype; /* machine specifier */
    uint32_t    filetype;   /* type of file */
    uint32_t    ncmds;      /* number of load commands */
    uint32_t    sizeofcmds; /* the size of all the load commands */
    uint32_t    flags;      /* flags */
};

/*
 * The 64-bit mach header appears at the very beginning of object files for
 * 64-bit architectures.
 */
struct mach_header_64 {
    uint32_t    magic;      /* mach magic number identifier */
    cpu_type_t  cputype;    /* cpu specifier */
    cpu_subtype_t   cpusubtype; /* machine specifier */
    uint32_t    filetype;   /* type of file */
    uint32_t    ncmds;      /* number of load commands */
    uint32_t    sizeofcmds; /* the size of all the load commands */
    uint32_t    flags;      /* flags */
    uint32_t    reserved;   /* reserved */
};

這是兩種結(jié)構(gòu)體器贩。
我們需要區(qū)分32架構(gòu)和64架構(gòu)使用的結(jié)構(gòu)體蛹稍。在下面的構(gòu)架方式定義中添加兩條宏定義。

#if __LP64__
    #define macho_header              mach_header_64   //64為架構(gòu)中添加此條
    #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 //32為架構(gòu)中添加此條
    #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

添加完成!
重新編譯一下饲宿,編譯通過(guò)!
接下來(lái)調(diào)用這個(gè)函數(shù)仗阅!
我們?cè)趌oad方法中調(diào)用国夜,這個(gè)方法是在main后最先調(diào)用的方法了!
判斷如果是有字段存在筹裕,表明未被修改,否則被修改证逻!

+ (void)load{
    //獲取鏡像image的頭抗斤。
    //image是一個(gè)列表,列表中第一個(gè)就是自己的鏡像
    //還記得通過(guò)lldb的`image list`指令獲取的鏡像列表嗎龙宏?就是它伤疙!
    const struct macho_header* header = _dyld_get_image_header(0);
    if (hasRestrictedSegment(header)) {
        NSLog(@"安全!");
    }else{
        NSLog(@"被修改了__RESTRICT或__restrict");
        exit(0);
    }
}

到這里我們就再一次做了防護(hù)花吟!

這里有一個(gè)問(wèn)題厨姚!
發(fā)現(xiàn)被修改后執(zhí)行exit(0)真的好嗎?
不一定~因?yàn)橐坏﹫?zhí)行退出程序操作,就相當(dāng)于告訴黑客我執(zhí)行了什么代碼今布。黑客就可以通過(guò)這個(gè)提示去找到對(duì)應(yīng)的方法拭抬。降低了防御能力!
微信是如何做的呢傅蹂?微信被注入后是沒(méi)有任何提示算凿!它在你不知不覺(jué)中就上報(bào)了一段序號(hào),比如89757婚夫,我們根本不知道這是什么署鸡!一旦被發(fā)現(xiàn)限嫌,第二天可能就被封號(hào)怒医。這種情況下匣椰,你很難找到它在什么時(shí)候做了防護(hù)!

第二種防護(hù)方式禽笑!《白名單庫(kù)》

第二種方式是把正常使用的鏡像庫(kù)列表作為字符串保存,然后在dyld加載動(dòng)態(tài)庫(kù)時(shí)判斷是否一致實(shí)現(xiàn)的佳镜。
如果dyld在執(zhí)行過(guò)程中插入了任何的動(dòng)態(tài)庫(kù)蟀伸,都會(huì)判斷為被修改了!
怎么做呢蠢络?


@implementation ViewController
const char * libPaths = "/var/containers/Bundle/Application/3C9EDF66-A49D-4CF2-BDC8-03EA2E26E3E6/demo.app/demo
/Developer/usr/lib/libBacktraceRecording.dylib
/Developer/Library/PrivateFrameworks/DTDDISupport.framework/libViewDebuggerSupport.dylib
/System/Library/Frameworks/Foundation.framework/Foundation/usr/lib/libobjc.A.dylib/usr/lib/libSystem.B.dylib
/System/Library/Frameworks/UIKit.framework
/UIKit/usr/lib/libarchive.2.dylib/usr/lib/libicucore.A.dylib
/usr/lib/libxml2.2.dylib/usr/lib/libz.1.dylib
..."

+ (void)load{
    int count = _dyld_image_count();
    for (int i = 0; i<count; i++) {
        // 在打包前先把庫(kù)鏈接打印粗來(lái)迟蜜,復(fù)制保存到libPaths,用于對(duì)比
        const char * libPath = _dyld_get_image_name(i);
        // 通過(guò)這個(gè)方法判斷l(xiāng)ibs是否包含libPath髓霞,如果包含會(huì)返回libPath畦戒,否則返回空。
        // 后面這個(gè)判斷是判斷自己纵潦,因?yàn)樽约核诘氖且粋€(gè)沙盒路徑垃环,路徑不是固定的,所以要排除它
        if (!strstr(libPaths, libPath)&&
            !strstr(libPath, "/var/mobile/Containers/Bundle/Application")){
            printf("被修改了被济!");
        }
        else{
            printf("該庫(kù)在白名單內(nèi)涧团,安全!");
        }
    }
}

這樣钮追,防護(hù)又做了一個(gè)阿迈,安全又提高了一點(diǎn)點(diǎn)。

但是這樣做其實(shí)還是可以被修改刊棕,因?yàn)閘ibPaths是全局的字符串待逞,還是可以拿到修改的识樱。起到防護(hù)效果有限!

結(jié)語(yǔ)

防護(hù)的方式還有很多很多怜庸,這里只是一些啟發(fā)性的知識(shí)點(diǎn)割疾,更多防護(hù)還需要更多學(xué)習(xí)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末驰凛,一起剝皮案震驚了整個(gè)濱河市担扑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌胚宦,老刑警劉巖燕垃,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件卜壕,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡鹤盒,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)驼鞭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)尺碰,“玉大人,你說(shuō)我怎么就攤上這事洛心√馀瘢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵偿枕,是天一觀的道長(zhǎng)渐夸。 經(jīng)常有香客問(wèn)我渔欢,道長(zhǎng),這世上最難降的妖魔是什么苫幢? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任垫挨,我火速辦了婚禮九榔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘哲泊。我一直安慰自己切威,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布缰冤。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪截酷。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天三热,我揣著相機(jī)與錄音三幻,去河邊找鬼。 笑死抑堡,一個(gè)胖子當(dāng)著我的面吹牛朗徊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播有缆,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼温亲,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了袖外?” 一聲冷哼從身側(cè)響起魂务,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤头镊,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后颖杏,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體坛芽,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年阴颖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了丐膝。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡偎肃,死狀恐怖浑此,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情紊馏,我是刑警寧澤蒲犬,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布暖哨,位于F島的核電站,受9級(jí)特大地震影響沛慢,放射性物質(zhì)發(fā)生泄漏达布。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一躺苦、第九天 我趴在偏房一處隱蔽的房頂上張望产还。 院中可真熱鬧,春花似錦愈诚、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至衰琐,卻和暖如春际插,著一層夾襖步出監(jiān)牢的瞬間显设,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工瑟枫, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留慷妙,地道東北人允悦。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像架馋,于是被迫代替她去往敵國(guó)和親全闷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容