• 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] = ''; // 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
    ");
        }
        ...
        
        return result;
    }
    View Code

    折叠开dyld main函数,步骤总结如下

    对待dyld的讲述,是非常不易的,因为本身过程是比较复杂的,上面仅仅是自身的抽出来的。下面再画一张流程图,帮助大家理解。

    四、总结

    MachO文件对于逆向开发是非常重要的,通过本次讲解,希望对大家理解逆向开发有所帮助,也希望大家真正可以提高技术,应对iOS市场的大环境,下一篇我们将讲述Hook原理--逆向开发。谢谢!!!

  • 相关阅读:
    发布Web端
    回溯算法套路详解(转)
    埋点自动化测试框架设计
    Mitmproxy + Python 做拦截代理
    面向对象三大特性-多态的思考
    java集合类-Set接口
    java集合类-List接口
    java集合类-集合框架体系
    测试的Python、 Java语言之争
    Python迭代器、生成器
  • 原文地址:https://www.cnblogs.com/guohai-stronger/p/11915571.html
Copyright © 2020-2023  润新知