「这是我参与2022首次更文挑战的第11天,活动详情查看:2022首次更文挑战」。
前言
dyld 这种格式的表示是动态链接的,编译的时候不会被编译到执行文件中,在程序执行的时候才 link,这样就不用算到包的大小里,而且也能够不更新执行程序就能够更新库。
动态库加载器/usr/lib/dyld,
__DATA segment的__dyld 是section占位符,用于动态链接器。
I 程序加载:在程序执行 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
1.1 分析dyld_shared_cache_armv7s 中的系统二进制文件
- dsc_extractor
- jtool 进行提取: 导出对应的模块
jtool -extract UIKit dyld_shared_cache_armv7s
- xcode在有新设备连接的时候,会自动提取系统库到~//Library/Developer/Xcode/iOS\ DeviceSupport
定时清理DeviceSupport
rm -rf ~/Library/Developer/Xcode/iOS\ DeviceSupport/*
➜ Housekeeper git:(develop) cat ~/bin/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.
killall -9 Xcode
killall -9 com.apple.CoreSimulator.CoreSimulatorService
rm -rf ~/Library/Developer/Xcode/iOS\ DeviceSupport/*
rm -rf ~/Library/Developer/Xcode/DerivedData/*
rm -rf ~/Library/Developer/Xcode/Archives/*
rm -rf ~/Library/Developer/Xcode/Products/*
rm -rf ~/Library/Developer/CoreSimulator/Devices/*
killall -9 com.apple.CoreSimulator.CoreSimulatorService
killall -9 Xcode
rm -rf ~/.Trash/
exit 0% ➜ Housekeeper git:(develop)
1.2 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。
1.3 dylb 的加载流程
- 从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的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";
}
1.4 环境变量 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]);
- getenv
char * getenv(const char *name) {
static void *handle; // 1
static char * (*real_getenv)(const char *); // 2
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ // 3
handle = dlopen("/usr/lib/system/libsystem_c.dylib", RTLD_NOW);
assert(handle);
real_getenv = dlsym(handle, "getenv");
});
if (strcmp(name, "HOME") == 0) { // 4
return "/"; }
return real_getenv(name); // 5
}
II 例子
由于篇幅原因,更多内容请关注 #小程序:iOS逆向,只为你呈现有价值的信息,专注于移动端技术研究领域;更多服务和咨询请关注#公众号:iOS逆向。