MachO文件詳解--逆向開發

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

1、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文件路徑查看文件類型架構

咱們經過部分實例代碼來簡單研究一下。app

2.1目標文件.o

經過test.c 文件,可使用clang命令將其編譯成目標文件.o框架

咱們再經過file命令(以下)查看文件類型ide

是個Mach-O文件。函數

2.2 dylib

經過cd /usr/lib命令查看dylibpost

經過file命令查看文件類型ui

 

 2.3 .dsym

下面是一個截圖來講明.dsym是也是Mach-O文件格式

 

以上只是Mach-O常見格式的某一種,你們能夠經過命令來嘗試。

3. 通用二進制文件

但願你們在瞭解App二進制架構的時候,能夠先讀一下本人的另外一篇博客關於armv7,armv7s以及arm64等的介紹。http://www.javashuo.com/article/p-rliycvhj-hu.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命令以下:

 

2、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查看,地址映射內容

 

3、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函數,步驟總結以下

對待dyld的講述,是很是不易的,由於自己過程是比較複雜的,上面僅僅是自身的抽出來的。下面再畫一張流程圖,幫助你們理解。

 

4、總結

MachO文件對於逆向開發是很是重要的,經過本次講解,但願對你們理解逆向開發有所幫助,也但願你們真正能夠提升技術,應對iOS市場的大環境,下一篇咱們將講述Hook原理--逆向開發。謝謝!!!

 

 

 

 

 

 

 

原文出處:https://www.cnblogs.com/guohai-stronger/p/11915571.html

相關文章
相關標籤/搜索