逆向开发--7.dyld

    dyld(the dynamic link editor)是苹果的动态连接器,是苹果操作系统一个重要组成部分,在系统内核做好程序准备工作之后,交由dyld负责余下的工作

    1.+ (void)load{} 方法断点,起始 _dyld_start,跳入

dyldbootstrap::start(macho_header const*, int, char const**, long, macho_header const*, unsigned long*)

    2.dyldInitialization.cpp文件

uintptr_t start(const struct macho_header* appsMachHeader, int argc, const char* argv[], intptr_t slider, const struct macho_header* dyldsMachHeader, uintptr_t* startGlue)

    ASLR(Address space layout randomization)是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的。据研究表明ASLR可以有效的降低缓冲区溢出攻击的成功率,如今Linux、FreeBSD、Windows等主流操作系统都已采用了该技术。

    3. dyldInitialization::start方法

        slide = slideOfMainExecutable(dyldsMachHeader); 计算偏移地址

        if (shouldRebase) { rebaseDyld(dyldMachHeader, slide) }; 重定向

        mach_init(); 初始化

        __guard_setup(apple); 保护栈溢出

        return dyld::_main(appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue); 

    4.跳转 dyld.cpp文件

_main(const macho_header* mainExecutableH, uintptr_t mainExecutableSlide, int argc, const char * argv[], const char *envp[], const char *apple[], unitptr_t *startGlue)

    第一步,设置运行环境,处理环境变量

    代码在开始时候,将传入的变量mainExecutableMH赋值给了sMainExecutableMachHeader,这是一个macho_header类型的变量,其结构体内容就是本章前面介绍的mach_header结构体,表示的是当前主程序的Mach-O头部信息,有了头部信息,加载器就可以从头开始,遍历整个Mach-O文件的信息。
接着执行了setContext(),此方法设置了全局一个链接上下文,包括一些回调函数、参数与标志设置信息

    第二步,初始化主程序

    这一步主要执行了instantiateFromLoadedImage()

static ImageLoader* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path)

    执行了instantiateFromLoadedImage(),isCompatibleMachO()主要检查Mach-O的头部的cputypecpusubtype来判断程序与当前的系统是否兼容。如果兼容接下来就调用instantiateMainExecutable()实例化主程序

    第三步,加载共享缓存

    这一步主要执行mapSharedCache()来映射共享缓存。该函数先通过_shared_region_check_np()来检查缓存是否已经映射到了共享区域了,如果已经映射了,就更新缓存的slideUUID,然后返回。反之,判断系统是否处于安全启动模式(safe-boot mode)下,如果是就删除缓存文件并返回,正常启动的情况下,接下来调用openSharedCacheFile()打开缓存文件,该函数在sSharedCacheDir路径下,打开与系统当前cpu架构匹配的缓存文件,也就是/var/db/dyld/dyld_shared_cache_x86_64h,接着读取缓存文件的前8192字节,解析缓存头dyld_cache_header的信息,将解析好的缓存信息存入mappings变量,最后调用_shared_region_map_and_slide_np()完成真正的映射工作。

    共享缓存加载完毕后,接着进行动态库的版本化重载,这主要通过函数checkVersionedPaths()完成。该函数读取DYLD_VERSIONED_LIBRARY_PATHDYLD_VERSIONED_FRAMEWORK_PATH环境变量,将指定版本的库比当前加载的库的版本做比较,如果当前的库版本更高的话,就使用新版本的库来替换掉旧版本的。

    第四步,加载插入的动态库

    这一步循环遍历DYLD_INSERT_LIBRARIES环境变量中指定的动态库列表,并调用loadInsertedDylib()将其加载。该函数调用load()完成加载工作。load()会调用loadPhase0()尝试从文件加载,loadPhase0()会向下调用下一层phase来查找动态库的路径,直到loadPhase6(),查找的顺序为DYLD_ROOT_PATH->LD_LIBRARY_PATH->DYLD_FRAMEWORK_PATH->原始路径->DYLD_FALLBACK_LIBRARY_PATH,找到后调用ImageLoaderMachO::instantiateFromFile()来实例化一个ImageLoader,之后调用checkandAddImage()验证映像并将其加入到全局映像列表中。如果loadPhase0()返回为空,表示在路径中没有找到动态库,就尝试从共享缓存中查找,找到就调用ImageLoaderMachO::instantiateFromCache()从缓存中加载,否则就抛出没找到映像的异常。

    第五步,链接主程序

    这一步执行link()完成主程序的链接操作。该函数调用了ImageLoader自身的link()函数,主要的目的是将实例化的主程序的动态数据进行修正,达到让进程可用的目的,典型的就是主程序中的符号表修正操作。

    第六步,链接插入的动态库

    链接插入的动态库与链接主程序一样,都是使用的link(),插入的动态库列表是前面调用addImage()保存到sAllImages中的,之后,循环获取每一个动态库的ImageLoader,调用link()对其进行链接,注意:sAllImages中保存的第一项是主程序的映像。接下来调用每个映像的registerInterposing()方法来注册动态库插入与调用applyInterposing()应用插入操作。registerInterposing()查找__DATA段的__interpose节区,找到需要应用插入操作(也可以叫作符号地址替换)的数据,然后做一些检查后,将要替换的符号与被替换的符号信息存入fgInterposingTuples列表中,供以后具体符号替换时查询。applyInterposing()调用了虚方法doInterpose()来做符号替换操作,在ImageLoaderMachOCompressed中实际是调用了eachBind()eachLazyBind()分别对常规的符号与延迟加载的符号进行应用插入操作,具体使用的是interposeAt(),该方法调用interposedAddress()fgInterposingTuples中查找要替换的符号地址,找到后然后进行最终的符号地址替换。

    第七步,执行弱符号绑定

    weakBind()函数执行弱符号绑定。首先通过调用contextgetCoalescedImages()sAllImages中所有含有弱符号的映像合并成一个列表,合并完后调用initializeCoalIterator()对映像进行排序,排序完成后调用incrementCoalIterator()收集需要进行绑定的弱符号,后者是一个虚函数,在ImageLoaderMachOCompressed中,该函数读取映像动态链接信息的weak_bind_offweak_bind_size来确定弱符号的数据偏移与大小,然后挨个计算它们的地址信息。之后调用getAddressCoalIterator(),按照映像的加载顺序在导出表中查找符号的地址,找到后调用updateUsesCoalIterator()执行最终的绑定操作,执行绑定的是bindLocation(),前面有讲过,此处不再赘述。

    第八步,执行初始化方法

    执行初始化的方法是initializeMainExecutable()。该函数主要执行runInitializers(),后者调用了ImageLoaderrunInitializers()方法,最终迭代执行了ImageLoaderMachOdoInitialization()方法,后者主要调用doImageInit()doModInitFunctions()执行映像与模块中设置为init的函数与静态初始化方法

    第九步,查找入口点并返回

    这一步调用主程序映像的getThreadPC()函数来查找主程序的LC_MAIN加载命令获取程序的入口点,没找到就调用getMain()LC_UNIXTHREAD加载命令中去找,找到后就跳到入口点指定的地址并返回了。

到这里,dyld整个加载动态库的过程就算完成了。

另外再讨论下延迟符号加载的技术细节。在所有拥有延迟加载符号的Mach-O文件里,它的符号表中一定有一个dyld_stub_helper符号,它是延迟符号加载的关键!延迟绑定符号的修正工作就是由它完成的。绑定符号信息可以使用XCode提供的命令行工具dyldinfo来查看

猜你喜欢

转载自blog.csdn.net/liqun3yue25/article/details/86649272