// 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(unsignedint i=0; i < sInsertedDylibCount; ++i) { ImageLoader* image = sAllImages[i+1]; link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL)); image->setNeverUnloadRecursive(); } // only INSERTED libraries can interpose // register interposing info after all inserted libraries are bound so chaining works for(unsignedint 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 (int 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;
sMainExecutable->weakBind(gLinkContext);
initializeMainExecutable();
// 寻找主程序的入口 // find entry point for main executable result = (uintptr_t)sMainExecutable->getThreadPC(); if ( result != 0 ) { // 主可执行文件使用lc_main,需要返回libdyld.dylib中的glue //调用main() //当执行完dyld::_main方法之后,返回了main()函数地址,这个时候所有初始化工作都已经完成了,正式进入Objc声明周期 // 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(constchar* message) { syncAllImages(); halt(message); } catch(...) { dyld::log("dyld: launch failed\n"); } return result; }
在main函数中会执行如下操作:
将主程序初始化为imageLoader,用于后续的链接等操作
加载共享库到内存
加载插入的动态库
链接主程序,链接插入库
初始化主程序
寻找主程序入口点
接下来我们将按照上面的顺序进行展开:
将主程序初始化为imageLoader
在dyld main方法中会通过instantiateFromLoadedImage创建ImageLoader,其中第一个参数传入的是mainExecutableMH为主程序的Mach O Header,有了mainExecutableMH,dyld就可以从头开始遍历整个Mach O文件信息了。在开始创建ImageLoader之前会先调用isCompatibleMachO来查看mainExecutableMH中的cputype与cpusubtype是否与当前设备兼容,只有兼容的情况下才会继续创建ImageLoader,创建后的ImageLoader会通过addImage添加到sAllImages,然后调用addMappedRange()申请内存,更新主程序镜像映射的内存区。
static ImageLoader* instantiateFromLoadedImage(constmacho_header*mh, uintptr_tslide, constchar*path) { // 检测mach-o header的cputype与cpusubtype是否与当前系统兼容 if ( isCompatibleMachO((constuint8_t*)mh, path) ) { //初始化镜像加载器 ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext); addImage(image); return image; } throw "main executable not a known format"; }
ImageLoader* load(const char* path, const LoadContext& context) { //..... // 尝试所有路径排列并检查现有加载的镜像 // try all path permutations and check against existing loaded images ImageLoader* image = loadPhase0(path, orgPath, context, NULL); if ( image != NULL ) { return image; }
//...... image = loadPhase0(path, orgPath, context, &exceptions); if ( (image == NULL) && cacheablePath(path) && !context.dontLoad ) { //...... if ( (myerr == ENOENT) || (myerr == 0) ) { // see if this image is in shared cache //...... if ( findInSharedCacheImage(resolvedPath, false, NULL, &mhInCache, &pathInCache, &slideInCache) ) { //...... image = ImageLoaderMachO::instantiateFromCache(mhInCache, pathInCache, slideInCache, stat_buf, gLinkContext); image = checkandAddImage(image, context); } } } }
static ImageLoader* loadPhase6(intfd, conststructstat& stat_buf, constchar*path, const LoadContext& context) { //....... // try mach-o loader if ( shortPage ) throw "file too short"; if ( isCompatibleMachO(firstPage, path) ) { // 只有MH_BUNDLE,MH_DYLIB,以及一些MH_EXECUTE 才能被动态加载 switch ( ((mach_header*)firstPage)->filetype ) { case MH_EXECUTE: case MH_DYLIB: case MH_BUNDLE: break; default: throw "mach-o, but wrong filetype"; } //实例化镜像 ImageLoader* image = ImageLoaderMachO::instantiateFromFile(path, fd, firstPage, fileOffset, fileLength, stat_buf, gLinkContext); // validate return checkandAddImage(image, context); } //..... }
链接主程序
link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL)); //...... // 4.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(unsignedint i=0; i < sInsertedDylibCount; ++i) { ImageLoader* image = sAllImages[i+1]; link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL)); image->setNeverUnloadRecursive(); } //...... } //...... gLinkContext.linkingMainExecutable = false; sMainExecutable->weakBind(gLinkContext);
void ImageLoader::recursiveRebase(const LinkContext& context) { if ( fState < dyld_image_state_rebased ) { // break cycles fState = dyld_image_state_rebased; try { // rebase lower level libraries first for(unsigned int i=0; i < libraryCount(); ++i) { ImageLoader* dependentImage = libImage(i); if ( dependentImage != NULL ) dependentImage->recursiveRebase(context); } // rebase this image doRebase(context); // notify context.notifySingle(dyld_image_state_rebased, this); } //...... } }
在之前的步骤中我们都在完成库的加载,但是这些dylibs之间是没有关联的,需要rebase,binding对地址修正,不知道大家看过高达之类的动画片没有,在变身过程中装备会从四面八方飞过来,然后会一个个安装到身上,嗯,link就是这个过程,动态库中的地址是相对的,因为它需要保证它内部逻辑的独立性,同时为了降低缓冲区溢出攻击的成功率主流的操作系统都会采用ASLR(Address space layout randomization)技术,它通过对堆、栈、共享库映射等线性区布局的随机化来增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的。
// if prebound and loaded at prebound address, and all libraries are same as when this was prebound, then no need to bind // note: flat-namespace binaries need to have imports rebound (even if correctly prebound) if ( this->usablePrebinding(context) ) { // don't need to bind } else { #if TEXT_RELOC_SUPPORT // if there are __TEXT fixups, temporarily make __TEXT writable if ( fTextSegmentBinds ) this->makeTextSegmentWritable(context, true); #endif
// run through all binding opcodes eachBind(context, &ImageLoaderMachOCompressed::bindAt); #if TEXT_RELOC_SUPPORT // if there were __TEXT fixups, restore write protection if ( fTextSegmentBinds ) this->makeTextSegmentWritable(context, false); #endif // if this image is in the shared cache, but depends on something no longer in the shared cache, // there is no way to reset the lazy pointers, so force bind them now if ( forceLazysBound || fInSharedCache ) this->doBindJustLazies(context);
// this image is in cache, but something below it is not. If // this image has lazy pointer to a resolver function, then // the stub may have been altered to point to a shared lazy pointer. if ( fInSharedCache ) this->updateOptimizedLazyPointers(context);
// tell kernel we are done with chunks of LINKEDIT if ( !context.preFetchDisabled ) this->markFreeLINKEDIT(context); } // set up dyld entry points in image // do last so flat main executables will have __dyld or __program_vars set up this->setupLazyPointerHandler(context); CRSetCrashLogMessage2(NULL); }
// 初始化任意插入的dylibs // run initialzers for any inserted dylibs if ( rootCount > 1 ) { //这里需要注意的是sImageRoots 中的第一个变量是主程序镜像, 因此这里初始化的时候需要跳过第一个数据, 对其他后面插入的dylib进行调用ImageLoader::runInitializers进行初始化 for(size_t i=1; i < rootCount; ++i) { sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]); } } // 最后运行主程序的初始化器 // run initializers for main executable and everything it brings up sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
result = (uintptr_t)sMainExecutable->getThreadPC(); if ( result != 0 ) { // 主可执行文件使用lc_main,需要返回libdyld.dylib中的glue //调用main() //当执行完dyld::_main方法之后,返回了main()函数地址,这个时候所有初始化工作都已经完成了,正式进入Objc声明周期 // 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; }
main 地址有两种方式获取,一种是存放在LC_MAIN命令中,这时候需要调用getThreadPC
void* ImageLoaderMachO::getThreadPC()const { constuint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; conststructload_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; conststructload_command* cmd = cmds; for (uint32_t i = 0; i < cmd_count; ++i) { // 遍历loadCommand,加载loadCommand中的'LC_MAIN'所指向的偏移地址 if ( cmd->cmd == LC_MAIN ) { entry_point_command* mainCmd = (entry_point_command*)cmd; // 偏移量 + header所占的字节数,就是main的入口 void* entry = (void*)(mainCmd->entryoff + (char*)fMachOData); if ( this->containsAddress(entry) ) return entry; else throw"LC_MAIN entryoff is out of range"; } cmd = (conststruct load_command*)(((char*)cmd)+cmd->cmdsize); } returnNULL; }