前言
dylib 这种格式的表示是动态链接的,编译的时候不会被编译到执行文件中,在程序执行的时候才 link,这样就不用算到包的大小里,而且也能够不更新执行程序就能够更新库
通过 otool 可以找到所需库在哪 ➜ ~ otool -l tmp.arm64
程序加载:在程序执行 Main 函数之前,都做了哪些事。
- 当加载 Mach-O 文件时动态链接器会先检查共享内存是否有。每个进程都会在自己地址空间映射这些共享缓存,这样可以优化启动速度
- 生成可执行文件后就是在启动时进行动态链接了,进行符号和地址的绑定。
首先会加载所依赖的 dylibs,修正地址偏移,因为 iOS 会用 ASLR 来做地址偏移避免攻击,确定 Non-Lazy Pointer 地址进行符号地址绑定,加载所有类;
最后执行 load 方法和 clang attribute 的 constructor 修饰函数。
/System/Library/Caches/com.apple.dyld: ios的共享缓存机制
iPhone:/System/Library/Caches/com.apple.dyld root# ls -lrt total 394928 -rw-r--r-- 1 root wheel 404405507 Oct 14 2014 dyld_shared_cache_armv7s -rwxr-xr-x 1 root admin 0 Apr 23 2017 enable-dylibs-to-override-cache*
ls -lrt /var/db/dyld/ : osx
因为 Fundation 还会依赖一些其它的动态库,其它的库还会再依赖更多的库,这样相互依赖的符号会很多,需要处理的时间也会比较长,这里系统上的动态链接器会使用共享缓存,共享缓存在 /var/db/dyld/ ➜ ~ ls -rlt /var/db/dyld/ total 3255808 -rw-r--r-- 1 root wheel 518811648 Nov 27 2017 dyld_shared_cache_i386 -rw-r--r-- 1 root wheel 137234 Nov 27 2017 dyld_shared_cache_i386.map -rw-r--r-- 1 root wheel 1147727872 Nov 27 2017 dyld_shared_cache_x86_64h -rw-r--r-- 1 root wheel 291583 Nov 27 2017 dyld_shared_cache_x86_64h.map
分析dyld_shared_cache_armv7s 中的系统二进制文件
jtool 进行提取
导出对应的模块
jtool -extract UIKit dyld_shared_cache_armv7s
xcode在有新设备连接的时候,会自动提取系统库到
~//Library/Developer/Xcode/iOS\ DeviceSupport
- 定时清理DeviceSupport
➜ bin git:(master) ✗ cat knclean #!/bin/sh # The ~/Library/Developer/Xcode/iOS DeviceSupport folder is basically only needed to symbolicate crash logs. # You could completely purge the entire folder. Of course the next time you connect one of your devices, Xcode would redownload the symbol data from the device. # I clean out that folder once a year or so by deleting folders for versions of iOS I no longer support or expect to ever have to symbolicate a crash log for. rm -rf /Users/devzkn/Library/Developer/Xcode/iOS\ DeviceSupport/* exit 0%
➜ bin git:(master) ✗ cd /Users/devzkn/Library/Developer/Xcode/iOS\ DeviceSupport ➜ iOS DeviceSupport ls -lrt total 0 drwxr-xr-x 5 devzkn staff 160 May 4 08:58 9.3.3 (13G34) drwxr-xr-x 5 devzkn staff 160 May 6 14:46 11.3 (15E216) drwxr-xr-x 5 devzkn staff 160 Jul 3 11:49 8.1.1 (12B435) drwxr-xr-x 5 devzkn staff 160 Jul 3 15:00 11.1.1 (15B150) drwxr-xr-x 5 devzkn staff 160 Jul 3 15:10 11.2.5 (15D60) drwxr-xr-x 5 devzkn staff 160 Jul 5 11:06 8.1.2 (12B440) drwxr-xr-x 5 devzkn staff 160 Jul 5 11:26 8.4 (12H143) drwxr-xr-x 5 devzkn staff 160 Jul 6 10:48 8.3 (12F70) drwxr-xr-x 5 devzkn staff 160 Jul 6 19:32 8.2 (12D508) drwxr-xr-x 5 devzkn staff 160 Jul 6 19:34 8.1.3 (12B466) drwxr-xr-x 5 devzkn staff 160 Jul 7 10:42 10.2 (14C92) drwxr-xr-x 5 devzkn staff 160 Jul 8 11:36 8.1 (12B411)
dyld 做了些什么事
程序在运行时会依赖很多系统动态库。系统动态库通过动态库器(/usr/lib/dyld )加载起加载到内存。
- 1)kernel 做启动程序初始准备,开始由dyld负责。
- 2)基于非常简单的原始栈为 kernel 设置进程来启动自身。
3)使用共享缓存来处理递归依赖带来的性能问题,ImageLoader 会读取二进制文件,其中包含了我们的类,方法等各种符号。 (共享缓存技术)
共享缓存在系统启动后被加载到内存中,当有新的程序加载时会先到共享缓存里面寻找;如果找到,就直接将共享缓存中的地址映射到目标进程的内存地址空间,极大提高加载效率。
- 4)立即绑定 non-lazy 的符号并设置用于 lazy bind 的必要表,将这些库 link 到执行文件里。
- 5)为可执行文件运行静态初始化。
- 6)设置参数到可执行文件的 main 函数并调用它。
7)在执行期间,通过绑定符号处理对 lazily-bound 符号存根的调用提供 runtime 动态加载服务(通过 dl*() 这个 API ),并为gdb和其它调试器提供钩子以获得关键信息。runtime 会调用 map_images 做解析和处理,load_images 来调用 call_load_methods 方法遍历所有加载了的 Class,按照继承层级依次调用 +load 方法。
load_images(const char *path __unused, const struct mach_header *mh)
获取所有类的列表然后收集其中的 +load 方法;Class 的 +load 是先执行的,然后执行 Category 的
void prepare_load_methods(const headerType *mhdr)
遍历 Class 的 +load 方法时会执行 schedule_class_load 这个方法,这个方法会递归到根节点来满足 Class 收集完整关系树的需求。
- void call_load_methods(void)
创建一个 autoreleasePool 使用函数指针来动态调用类和 Category 的 +load 方法
- 8)在 mian 函数返回后运行 static terminator。 在某些情况下,一旦 main 函数返回,就需要调用 libSystem 的 _exit。
dylb 的加载流程
在程序执行 Main 函数之前,都做了哪些事。
从dyldStartup.s 汇编文件开始执行
__dyld_start: popl %edx # edx = mh of app pushl $0 # push a zero for debugger end of frames marker movl %esp,%ebp # pointer to base of kernel frame andl $-16,%esp # force SSE alignment subl $32,%esp # room for locals and outgoing parameters call L__dyld_start_picbase L__dyld_start_picbase: popl %ebx # set %ebx to runtime value of picbase movl Lmh-L__dyld_start_picbase(%ebx), %ecx # ecx = prefered load address movl __dyld_start_static_picbase-L__dyld_start_picbase(%ebx), %eax subl %eax, %ebx # ebx = slide = L__dyld_start_picbase - [__dyld_start_static_picbase] addl %ebx, %ecx # ecx = actual load address # call dyldbootstrap::start(app_mh, argc, argv, slide, dyld_mh, &startGlue) call __ZN13dyldbootstrap5startEPK12macho_headeriPPKclS2_Pm
1、 //设置上下文信息、 //配置进程是否受限 2、 //获取当前运行架构信息 3、 //加载可执行文件并生成一个ImageLoader实例对象 4、 //检查共享缓存是否映射到共享区域 5、 //加载所有DYLD_INSERT_LIBRARIES指定的库 // load any inserted libraries if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) { for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) loadInsertedDylib(*lib); } 6、 //链接主程序 7、 //链接插入的动态库,执行符号替换 8、 //执行初始化方法,+load、constructor方法就是在这里执行的。 9、寻找主程序入口,来到我们熟悉的main 函数
dyld.cpp一些重点的code
ImageLoaderMachO
// The kernel maps in main executable before dyld gets control. We need to // make an ImageLoader* for the already mapped in main executable. //在dyld获取控制权之前内核已经将可执行文件映射到内存了,这里需要从已经映射的文件生成一个ImageLoader实例 static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path) { // try mach-o loader //比较文件架构和当前架构是否兼容 if ( isCompatibleMachO((const uint8_t*)mh, path) ) { //加载文件生成ImageLoader实例 ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext); //将image加到全局sAllImages,并更新mapped range //如果设置了DYLD_PRINT_LIBRARIES环境变量,将会打印当前加载的image addImage(image); return (ImageLoaderMachO*)image; } throw "main executable not a known format"; }
EnvironmentVariables
环境变量
// // state of all environment variables dyld uses // struct EnvironmentVariables { const char* const * DYLD_FRAMEWORK_PATH; const char* const * DYLD_FALLBACK_FRAMEWORK_PATH; const char* const * DYLD_LIBRARY_PATH; const char* const * DYLD_FALLBACK_LIBRARY_PATH; const char* const * DYLD_INSERT_LIBRARIES; const char* const * LD_LIBRARY_PATH; // for unix conformance const char* const * DYLD_VERSIONED_LIBRARY_PATH; const char* const * DYLD_VERSIONED_FRAMEWORK_PATH; bool DYLD_PRINT_LIBRARIES_POST_LAUNCH; bool DYLD_BIND_AT_LAUNCH; bool DYLD_PRINT_STATISTICS; bool DYLD_PRINT_STATISTICS_DETAILS; bool DYLD_PRINT_OPTS; bool DYLD_PRINT_ENV; bool DYLD_DISABLE_DOFS; bool DYLD_PRINT_CS_NOTIFICATIONS; // DYLD_SHARED_CACHE_DONT_VALIDATE ==> sSharedCacheIgnoreInodeAndTimeStamp // DYLD_SHARED_CACHE_DIR ==> sSharedCacheDir // DYLD_ROOT_PATH ==> gLinkContext.rootPaths // DYLD_IMAGE_SUFFIX ==> gLinkContext.imageSuffix // DYLD_PRINT_OPTS ==> gLinkContext.verboseOpts // DYLD_PRINT_ENV ==> gLinkContext.verboseEnv // DYLD_FORCE_FLAT_NAMESPACE ==> gLinkContext.bindFlat // DYLD_PRINT_INITIALIZERS ==> gLinkContext.verboseInit // DYLD_PRINT_SEGMENTS ==> gLinkContext.verboseMapping // DYLD_PRINT_BINDINGS ==> gLinkContext.verboseBind // DYLD_PRINT_WEAK_BINDINGS ==> gLinkContext.verboseWeakBind // DYLD_PRINT_REBASINGS ==> gLinkContext.verboseRebase // DYLD_PRINT_DOFS ==> gLinkContext.verboseDOF // DYLD_PRINT_APIS ==> gLogAPIs // DYLD_IGNORE_PREBINDING ==> gLinkContext.prebindUsage // DYLD_PREBIND_DEBUG ==> gLinkContext.verbosePrebinding // DYLD_NEW_LOCAL_SHARED_REGIONS ==> gLinkContext.sharedRegionMode // DYLD_SHARED_REGION ==> gLinkContext.sharedRegionMode // DYLD_PRINT_WARNINGS ==> gLinkContext.verboseWarnings // DYLD_PRINT_RPATHS ==> gLinkContext.verboseRPaths // DYLD_PRINT_INTERPOSING ==> gLinkContext.verboseInterposing // DYLD_PRINT_LIBRARIES ==> gLinkContext.verboseLoading };
一些重要的环境变量
//如果设置了DYLD_PRINT_OPTS环境变量打印参数 //如果设置了DYLD_PRINT_ENV环境变量打印环境变量
在xocode 中设置环境变量:
DYLD_PRINT_STATISTICS、DYLD_PRINT_STATISTICS_DETAILS
,打印各阶段消耗的时间// dump info if requested //DYLD_PRINT_STATISTICS if ( sEnv.DYLD_PRINT_STATISTICS ) ImageLoader::printStatistics((unsigned int)allImagesCount(), initializerTimes[0]); //DYLD_PRINT_STATISTICS_DETAILS if ( sEnv.DYLD_PRINT_STATISTICS_DETAILS ) ImageLoaderMachO::printStatisticsDetails((unsigned int)allImagesCount(), initializerTimes[0]);
例子
通过 otool 可以找到所需库在哪
* ➜ ~ otool -l tmp.arm64
Load command 13 cmd LC_LOAD_DYLIB cmdsize 80 name /System/Library/Frameworks/Security.framework/Security (offset 24)
name /usr/lib/libSystem.B.dylib (offset 24) 1)libdispatch:GCD 2)libsystem_c:C语言库 3)libsystem_blocks:Block 4)libcommonCrypto:加密,比如md5
DYLD_PRINT_LIBRARIES
- 打印什么库被加载了
➜ ~ export DYLD_PRINT_LIBRARIES=;/Users/devzkn/Downloads/kevinsoftware/ios-Reverse_Engineering/llvm-3.9.0.src/build/CMakeFiles/3.10.0-rc4/CompilerIdCXX/a.out dyld: loaded: /Users/devzkn/Downloads/kevinsoftware/ios-Reverse_Engineering/llvm-3.9.0.src/build/CMakeFiles/3.10.0-rc4/CompilerIdCXX/a.out dyld: loaded: /usr/lib/libc++.1.dylib dyld: loaded: /usr/lib/libSystem.B.dylib dyld: loaded: /usr/lib/libc++abi.dylib dyld: loaded: /usr/lib/system/libcache.dylib dyld: loaded: /usr/lib/system/libcommonCrypto.dylib dyld: loaded: /usr/lib/system/libcompiler_rt.dylib dyld: loaded: /usr/lib/system/libcopyfile.dylib dyld: loaded: /usr/lib/system/libcorecrypto.dylib dyld: loaded: /usr/lib/system/libdispatch.dylib dyld: loaded: /usr/lib/system/libdyld.dylib dyld: loaded: /usr/lib/system/libkeymgr.dylib dyld: loaded: /usr/lib/system/liblaunch.dylib dyld: loaded: /usr/lib/system/libmacho.dylib dyld: loaded: /usr/lib/system/libquarantine.dylib dyld: loaded: /usr/lib/system/libremovefile.dylib dyld: loaded: /usr/lib/system/libsystem_asl.dylib dyld: loaded: /usr/lib/system/libsystem_blocks.dylib dyld: loaded: /usr/lib/system/libsystem_c.dylib dyld: loaded: /usr/lib/system/libsystem_configuration.dylib dyld: loaded: /usr/lib/system/libsystem_coreservices.dylib dyld: loaded: /usr/lib/system/libsystem_darwin.dylib dyld: loaded: /usr/lib/system/libsystem_dnssd.dylib dyld: loaded: /usr/lib/system/libsystem_info.dylib dyld: loaded: /usr/lib/system/libsystem_m.dylib dyld: loaded: /usr/lib/system/libsystem_malloc.dylib dyld: loaded: /usr/lib/system/libsystem_network.dylib dyld: loaded: /usr/lib/system/libsystem_networkextension.dylib dyld: loaded: /usr/lib/system/libsystem_notify.dylib dyld: loaded: /usr/lib/system/libsystem_sandbox.dylib dyld: loaded: /usr/lib/system/libsystem_secinit.dylib dyld: loaded: /usr/lib/system/libsystem_kernel.dylib dyld: loaded: /usr/lib/system/libsystem_platform.dylib dyld: loaded: /usr/lib/system/libsystem_pthread.dylib dyld: loaded: /usr/lib/system/libsystem_symptoms.dylib dyld: loaded: /usr/lib/system/libsystem_trace.dylib dyld: loaded: /usr/lib/system/libunwind.dylib dyld: loaded: /usr/lib/system/libxpc.dylib dyld: loaded: /usr/lib/closure/libclosured.dylib dyld: loaded: /usr/lib/libobjc.A.dylib
越狱检测
<mach-o/dyld.h>
#import <mach-o/dyld.h> int count = _dyld_image_count();//获得加载的动态库的数量 for (int i=0; i<count; i++) { printf("%s", _dyld_get_image_name(i));//获得名字,然后遍历他们的名字,看看有没有 “MobileSubstrate” 关键字,有的话就是越狱的 // 如果 _dyld_get_image_name() 里面包含 MobileSubstrate 就是越狱了 }
Section
动态库加载器
/usr/lib/dyld
__DATA segment 的__dyld 是section占位符,用于动态链接器。
符号表
每个函数,全局变量和类都是通过符号的形式来定义和使用的,当把目标文件(.o)链接成一个执行文件(.out)时, 链接器在目标文件和动态库之间对符号做解析处理.
-
符号表
-
链接器通过动态库解析成符号会记录是通过哪个动态库解析的,路径也会一起记录
➜ ~ nm -nm tmp.arm64 0000000000006e80 (__TEXT,__text) non-external -[ASSwitchIPOperation initWithTimeOut:Operation:tryTimes:] (undefined) external _CFDataCreate (from CoreFoundation) undefined 符号表示该文件类未实现的,所以在目标文件和 Fundation framework 动态库做链接处理时,链接器会尝试解析所有的 undefined 符号
See Also
/Users/devzkn/bin//knpost dyld dyld 简介 -t dyld #原来""的参数,需要自己加上""