1. 程式人生 > >MachO檔案詳解--逆向開發

MachO檔案詳解--逆向開發

今天是逆向開發的第5天內容--MachO檔案(Mac 和 iOS 平臺可執行的檔案),在逆向開發中是比較重要的,下面我們著重講解一下MachO檔案的基本內容和使用。

一、MachO概述

1. 概述

Mach-O是Mach Object檔案格式的縮寫,iOS以及Mac上可執行的檔案格式,類似Window的exe格式,Linux上的elf格式。Mach-O是一個可執行檔案、動態庫以及目的碼的檔案格式,是a.out格式的替代,提供了更高更強的擴充套件性。

2.常見格式

Mach-O常見格式如下:

  • 目標檔案 .o
  • 庫檔案
  1. .a
  2. .dylib
  3. .framework
  • 可執行檔案
  • dyld
  • .dsym

  通過file檔案路徑檢視檔案型別

我們通過部分例項程式碼來簡單研究一下。

2.1目標檔案.o

通過test.c 檔案,可以使用clang命令將其編譯成目標檔案.o

我們再通過file命令(如下)檢視檔案型別

是個Mach-O檔案。

2.2 dylib

通過cd /usr/lib命令檢視dylib

通過file命令檢視檔案型別

 

 2.3 .dsym

下面是一個截圖來說明.dsym是也是Mach-O檔案格式

 

以上只是Mach-O常見格式的某一種,大家可以通過命令來嘗試。

3. 通用二進位制檔案

希望大家在瞭解App二進位制架構的時候,可以先讀一下本人寫的另一篇部落格關於armv7,armv7s以及arm64等的介紹。https://www.cnblogs.com/guohai-stronger/p/9447364.html

通用二進位制檔案是蘋果自身發明的,基本內容如下

下面通過指令檢視Macho檔案來看下通用二進位制檔案

 

然後通過file指令檢視檔案型別

 

上面該MachO檔案包含了3個架構分別是arm v7,arm v7s 以及arm 64 。

針對該MachO檔案我們做幾個操作,利用lipo命令拆分合並架構

3.1 利用lipo-info檢視MachO檔案架構

3.2 瘦身MachO檔案,拆分

利用lipo-thin瘦身架構

 

 檢視一下結果如下,多出來一個新建的MachO_armv7

 

3.3 增加架構,合併

利用lipo -create 合併多種架構

發現多出一種框架,合併成功多出Demo可執行檔案。結果如下:

 

整理出lipo命令如下:

 

二、MachO檔案

2.1 檔案結構

下面是蘋果官方圖解釋MachO檔案結構圖

MachO檔案的組成結構如上,看包括了三個部分

  • Header包含了該二進位制檔案的一般資訊,資訊如下:
  1. 位元組順序、載入指令的數量以及架構型別
  2. 快速的確定一些資訊,比如當前檔案是32位或者64位,對應的檔案型別和處理器是什麼
  • Load commands 包含很多內容的表
  1. 包括區域的位置、動態符號表以及符號表等
  • Data一般是物件檔案的最大部分
  1. 一般包含Segement具體資料

2.2 Header的資料結構

在專案程式碼中,按下Command+ 空格,然後輸入loader.h  

然後檢視loader.h檔案,找到mach_header

上面是mach_header,對應結構體的意義如下:

通過MachOView檢視Mach64 Header頭部資訊

2.3 LoadCommands

LoadCommand包含了很多內容的表,通過MachOView檢視LoadCommand的資訊,圖如下:

 

但是大家看的可能並不瞭解內容,下面有圖進行註解,可以看下主要的意思

2.4 Data

Data包含Segement,儲存具體資料,通過MachOView檢視,地址對映內容

 

三、DYLD

3.1 dyld概述

dyld(the dynamic link editor)是蘋果動態連結器,是蘋果系統一個重要的組成部分,系統核心做好準備工作之後,剩下的就會交給了dyld。

3.2 dyld載入過程

程式的入口一般都是在main函式中,但是比較少的人關心main()函式之前發生了什麼?這次我們先探索dyld的載入過程。(但是比在main函式之前,load方法就在main函式之前)

3.2.1 新建專案,在main函式下斷

 

main()之前有個libdyld.dylib start入口,但是不是我們想要的,根據dyld原始碼找到__dyld_start函式

3.2.2 dyld main()函式

dyld main()函式是關鍵函式,下面是函式實現內容。(此時的main實現函式和程式App的main 函式是不一樣的,因為dyld也是一個可執行檔案,也是具有main函式的)

//
// Entry point for dyld.  The kernel loads dyld and jumps to __dyld_start which
// sets up some registers and call this function.
//
// Returns address of main() in target program which __dyld_start jumps to
//
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
        int argc, const char* argv[], const char* envp[], const char* apple[], 
        uintptr_t* startGlue)
{
    // Grab the cdHash of the main executable from the environment
    // 第一步,設定執行環境
    uint8_t mainExecutableCDHashBuffer[20];
    const uint8_t* mainExecutableCDHash = nullptr;
    if ( hexToBytes(_simple_getenv(apple, "executable_cdhash"), 40, mainExecutableCDHashBuffer) )
        // 獲取主程式的hash
        mainExecutableCDHash = mainExecutableCDHashBuffer;

    // Trace dyld's load
    notifyKernelAboutImage((macho_header*)&__dso_handle, _simple_getenv(apple, "dyld_file"));
#if !TARGET_IPHONE_SIMULATOR
    // Trace the main executable's load
    notifyKernelAboutImage(mainExecutableMH, _simple_getenv(apple, "executable_file"));
#endif

    uintptr_t result = 0;
    // 獲取主程式的macho_header結構
    sMainExecutableMachHeader = mainExecutableMH;
    // 獲取主程式的slide值
    sMainExecutableSlide = mainExecutableSlide;

    CRSetCrashLogMessage("dyld: launch started");
    // 設定上下文資訊
    setContext(mainExecutableMH, argc, argv, envp, apple);

    // Pickup the pointer to the exec path.
    // 獲取主程式路徑
    sExecPath = _simple_getenv(apple, "executable_path");

    // <rdar://problem/13868260> Remove interim apple[0] transition code from dyld
    if (!sExecPath) sExecPath = apple[0];

    if ( sExecPath[0] != '/' ) {
        // have relative path, use cwd to make absolute
        char cwdbuff[MAXPATHLEN];
        if ( getcwd(cwdbuff, MAXPATHLEN) != NULL ) {
            // maybe use static buffer to avoid calling malloc so early...
            char* s = new char[strlen(cwdbuff) + strlen(sExecPath) + 2];
            strcpy(s, cwdbuff);
            strcat(s, "/");
            strcat(s, sExecPath);
            sExecPath = s;
        }
    }

    // Remember short name of process for later logging
    // 獲取程序名稱
    sExecShortName = ::strrchr(sExecPath, '/');
    if ( sExecShortName != NULL )
        ++sExecShortName;
    else
        sExecShortName = sExecPath;
    
    // 配置程序受限模式
    configureProcessRestrictions(mainExecutableMH);


    // 檢測環境變數
    checkEnvironmentVariables(envp);
    defaultUninitializedFallbackPaths(envp);

    // 如果設定了DYLD_PRINT_OPTS則呼叫printOptions()列印引數
    if ( sEnv.DYLD_PRINT_OPTS )
        printOptions(argv);
    // 如果設定了DYLD_PRINT_ENV則呼叫printEnvironmentVariables()列印環境變數
    if ( sEnv.DYLD_PRINT_ENV ) 
        printEnvironmentVariables(envp);
    // 獲取當前程式架構
    getHostInfo(mainExecutableMH, mainExecutableSlide);
    //-------------第一步結束-------------
    
    // load shared cache
    // 第二步,載入共享快取
    // 檢查共享快取是否開啟,iOS必須開啟
    checkSharedRegionDisable((mach_header*)mainExecutableMH);
    if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
        mapSharedCache();
    }
    ...

    try {
        // add dyld itself to UUID list
        addDyldImageToUUIDList();

        // instantiate ImageLoader for main executable
        // 第三步 例項化主程式
        sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
        gLinkContext.mainExecutable = sMainExecutable;
        gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);

        // Now that shared cache is loaded, setup an versioned dylib overrides
    #if SUPPORT_VERSIONED_PATHS
        checkVersionedPaths();
    #endif


        // dyld_all_image_infos image list does not contain dyld
        // add it as dyldPath field in dyld_all_image_infos
        // for simulator, dyld_sim is in image list, need host dyld added
#if TARGET_IPHONE_SIMULATOR
        // get path of host dyld from table of syscall vectors in host dyld
        void* addressInDyld = gSyscallHelpers;
#else
        // get path of dyld itself
        void*  addressInDyld = (void*)&__dso_handle;
#endif
        char dyldPathBuffer[MAXPATHLEN+1];
        int len = proc_regionfilename(getpid(), (uint64_t)(long)addressInDyld, dyldPathBuffer, MAXPATHLEN);
        if ( len > 0 ) {
            dyldPathBuffer[len] = '\0'; // proc_regionfilename() does not zero terminate returned string
            if ( strcmp(dyldPathBuffer, gProcessInfo->dyldPath) != 0 )
                gProcessInfo->dyldPath = strdup(dyldPathBuffer);
        }

        // load any inserted libraries
        // 第四步 載入插入的動態庫
        if  ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
            for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)
                loadInsertedDylib(*lib);
        }
        // record count of inserted libraries so that a flat search will look at 
        // inserted libraries, then main, then others.
        // 記錄插入的動態庫數量
        sInsertedDylibCount = sAllImages.size()-1;

        // link main executable
        // 第五步 連結主程式
        gLinkContext.linkingMainExecutable = true;
#if SUPPORT_ACCELERATE_TABLES
        if ( mainExcutableAlreadyRebased ) {
            // previous link() on main executable has already adjusted its internal pointers for ASLR
            // work around that by rebasing by inverse amount
            sMainExecutable->rebase(gLinkContext, -mainExecutableSlide);
        }
#endif
        link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
        sMainExecutable->setNeverUnloadRecursive();
        if ( sMainExecutable->forceFlat() ) {
            gLinkContext.bindFlat = true;
            gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding;
        }

        // link any inserted libraries
        // do this after linking main executable so that any dylibs pulled in by inserted 
        // dylibs (e.g. libSystem) will not be in front of dylibs the program uses
        // 第六步 連結插入的動態庫
        if ( sInsertedDylibCount > 0 ) {
            for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
                ImageLoader* image = sAllImages[i+1];
                link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
                image->setNeverUnloadRecursive();
            }
            // only INSERTED libraries can interpose
            // register interposing info after all inserted libraries are bound so chaining works
            for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
                ImageLoader* image = sAllImages[i+1];
                image->registerInterposing();
            }
        }

        // <rdar://problem/19315404> dyld should support interposition even without DYLD_INSERT_LIBRARIES
        for (long i=sInsertedDylibCount+1; i < sAllImages.size(); ++i) {
            ImageLoader* image = sAllImages[i];
            if ( image->inSharedCache() )
                continue;
            image->registerInterposing();
        }
        ...

        // apply interposing to initial set of images
        for(int i=0; i < sImageRoots.size(); ++i) {
            sImageRoots[i]->applyInterposing(gLinkContext);
        }
        gLinkContext.linkingMainExecutable = false;
        
        // <rdar://problem/12186933> do weak binding only after all inserted images linked
        // 第七步 執行弱符號繫結
        sMainExecutable->weakBind(gLinkContext);

        // If cache has branch island dylibs, tell debugger about them
        if ( (sSharedCacheLoadInfo.loadAddress != NULL) && (sSharedCacheLoadInfo.loadAddress->header.mappingOffset >= 0x78) && (sSharedCacheLoadInfo.loadAddress->header.branchPoolsOffset != 0) ) {
            uint32_t count = sSharedCacheLoadInfo.loadAddress->header.branchPoolsCount;
            dyld_image_info info[count];
            const uint64_t* poolAddress = (uint64_t*)((char*)sSharedCacheLoadInfo.loadAddress + sSharedCacheLoadInfo.loadAddress->header.branchPoolsOffset);
            // <rdar://problem/20799203> empty branch pools can be in development cache
            if ( ((mach_header*)poolAddress)->magic == sMainExecutableMachHeader->magic ) {
                for (int poolIndex=0; poolIndex < count; ++poolIndex) {
                    uint64_t poolAddr = poolAddress[poolIndex] + sSharedCacheLoadInfo.slide;
                    info[poolIndex].imageLoadAddress = (mach_header*)(long)poolAddr;
                    info[poolIndex].imageFilePath = "dyld_shared_cache_branch_islands";
                    info[poolIndex].imageFileModDate = 0;
                }
                // add to all_images list
                addImagesToAllImages(count, info);
                // tell gdb about new branch island images
                gProcessInfo->notification(dyld_image_adding, count, info);
            }
        }

        CRSetCrashLogMessage("dyld: launch, running initializers");
        ...
        // run all initializers
        // 第八步 執行初始化方法
        initializeMainExecutable(); 

        // notify any montoring proccesses that this process is about to enter main()
        dyld3::kdebug_trace_dyld_signpost(DBG_DYLD_SIGNPOST_START_MAIN_DYLD2, 0, 0);
        notifyMonitoringDyldMain();

        // find entry point for main executable
        // 第九步 查詢入口點並返回
        result = (uintptr_t)sMainExecutable->getThreadPC();
        if ( result != 0 ) {
            // main executable uses LC_MAIN, needs to return to glue in libdyld.dylib
            if ( (gLibSystemHelpers != NULL) && (gLibSystemHelpers->version >= 9) )
                *startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;
            else
                halt("libdyld.dylib support not present for LC_MAIN");
        }
        else {
            // main executable uses LC_UNIXTHREAD, dyld needs to let "start" in program set up for main()
            result = (uintptr_t)sMainExecutable->getMain();
            *startGlue = 0;
        }
    }
    catch(const char* message) {
        syncAllImages();
        halt(message);
    }
    catch(...) {
        dyld::log("dyld: launch failed\n");
    }
    ...
    
    return result;
}
View Code

摺疊開dyld main函式,步驟總結如下

  1. 配置執行環境,獲取當前執行架構
  2. 載入共享快取,對映到當前執行架構
  3. 進行例項化主程式
  4. 開始載入插入的動態庫
  5. 然後連結主程式
  6. 開始連結插入的動態庫
  7. 弱符號繫結
  8. 初始化方法
  9. 尋找主程式的入口

對待dyld的講述,是非常不易的,因為本身過程是比較複雜的,上面僅僅是自身的抽出來的。下面再畫一張流程圖,幫助大家理解。

 

四、總結

MachO檔案對於逆向開發是非常重要的,通過本次講解,希望對大家理解逆向開發有所幫助,也希望大家真正可以提高技術,應對iOS市場的大環境,下一篇我們將講述Hook原理--逆向開發。謝謝!!!

 

 

 

 

 

 

&n