本来是想研究如何“简单”阻止 insert_dylib 注入的,但是好像并没有简单的方法,不过发现了一些 dyld 的东西,虽然目前来看似乎没有太大的意义,可是能拿到的东西肯定不是 dyld 想让应用拿到的。这篇 post 中的方法在 macOS 10.12.4 和 iOS 10.3.2 (14F5089a) 中均测试可行。
关于 iOS / macOS 中 dyld 如何初始化一个 MachO 的运行这里就不详细说了,在网上能找到这方面的一些资料。概要流程引用如下:
- 从 kernel 留下的原始调用栈引导和启动自己
- 将程序依赖的动态链接库递归加载进内存,当然这里有缓存机制
- non-lazy 符号立即 link 到可执行文件,lazy 的存表里
- Runs static initializers for the executable
- 找到可执行文件的 main 函数,准备参数并调用
- 程序执行中负责绑定 lazy 符号、提供 runtime dynamic loading services、提供调试器接口
- 程序main函数 return 后执行 static terminator
- 某些场景下 main 函数结束后调 libSystem 的 _exit 函数
那么在这篇 post 中,我们关注点是 2 和 4,并且主要在 4 上。
通过阅读 dyld 的源代码,我们可以建立如下主要的调用顺序
1. __dyld_start 最开始的工作,为下一个调用设置好参数 2. dyldbootstrap::start(app_mh, argc, argv, slide, dyld_mh, &startGlue) dyld 初始化 3. dyld::_main(appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue) 4. setContext(mainExecutableMH, argc, argv, envp, apple) 设置全局的 gLinkContext,这也是我们这篇 post 的主角 5. instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath) 6. initializeMainExecutable() 7. runInitializers(gLinkContext, initializerTimes[0]) 执行所有的 initializers (如果需要的话) 8. recursiveInitialization(context, thisThread, images.images[i]->getPath(), timingInfo, ups) 根据动态库之间的依赖关系(有向无环图,DAG)递归初始化 9. doInitialization(context) 具体某个动态库的初始化
终于到了这里了,doInitialization 的源代码是这样的
bool ImageLoaderMachO::doInitialization(const LinkContext& context) { CRSetCrashLogMessage2(this->getPath()); // mach-o has -init and static initializers doImageInit(context); doModInitFunctions(context); CRSetCrashLogMessage2(NULL); return (fHasDashInit || fHasInitializers); }
可以看到这里说 MachO 有两种 initializer,一种是通过 -init 指定的,另一种是 static initializer。
对于第一种,也就是 -init 指定的 initializer,是通过设置 linker flag 指定的,它会在 MachO 的 load_commands 中生成一个 LC_ROUTINES_64(对应 32 位的 MachO 则是 LC_ROUTINES)。
而第二种 initializer 则是常见的 __attribute__((constructor)) 函数。
在 dyld 处理的时候,先是调用了 doImageInit(context);,跳转到 doImageInit 可以发现这个函数就是在该 MachO 中查找 LC_ROUTINES_64(或 LC_ROUTINES)的 load_command,并且得到 initializer 的地址,然后执行它(当然在执行前有检查是否合法)。需要注意的是,一个 MachO 只能有一个这样的 load_command(当然你可以构造多个出来,但是 dyld 的 doImageInit 代码在遇到第一个之后就会跳出对 load_command 的遍历)。对这个 initializer 的调用如下。
func(context.argc, context.argv, context.envp, context.apple, &context.programVars)
第二种 initializer 的查找本质上和第一种的差别不大,只不过第二种 initializer 可以有很多个。dyld 对它们的调用和上面一样。
现在将焦点放在这个 context 变量上,根据我们一路的追踪(以及前面的剧透233),我们知道它是 dyld 中全局的 gLinkContext,它在 setContext 中被设置,现在传给了我们的 initializer。于是我们的 initializer 定义如下
extern "C" void dashinit(int argc, const char * argv[], char ** envp, char ** apple, struct ProgramVars * pvars)
这里前面 2 个参数就不说了,第 3 个环境参数知道的人也不少,第 4 个是 Apple 平台特有的,应该也有不少人知道,第 5 个 ProgramVars * pvars 知道的可能就没那么多了。它的定义如下
struct ProgramVars { const void* mh; int* NXArgcPtr; char*** NXArgvPtr; char*** environPtr; char** __prognamePtr; };
从上到下依次是 MachO 首部的地址,全局的 argc 指针,全局的 argv 的指针,环境变量指针,程序名的指针。当然,要是能拿到的只有这些,那也就不必费时间写这篇 post 了。下面我们继续来看 struct LinkContext 的定义。(programVars 所在的那一行已高亮标记)
struct LinkContext { ImageLoader* (*loadLibrary)(const char* libraryName, bool search, const char* origin, const RPathChain* rpaths, unsigned& cacheIndex); void (*terminationRecorder)(ImageLoader* image); bool (*flatExportFinder)(const char* name, const Symbol** sym, const ImageLoader** image); bool (*coalescedExportFinder)(const char* name, const Symbol** sym, const ImageLoader** image); unsigned int (*getCoalescedImages)(ImageLoader* images[], unsigned imageIndex[]); void (*undefinedHandler)(const char* name); MappedRegion* (*getAllMappedRegions)(MappedRegion*); void * (*bindingHandler)(const char *, const char *, void *); void (*notifySingle)(dyld_image_states, const ImageLoader* image, InitializerTimingList*); void (*notifyBatch)(dyld_image_states state, bool preflightOnly); void (*removeImage)(ImageLoader* image); void (*registerDOFs)(const std::vector<DOFInfo>& dofs); void (*clearAllDepths)(); void (*printAllDepths)(); unsigned int (*imageCount)(); void (*setNewProgramVars)(const ProgramVars&); bool (*inSharedCache)(const char* path); void (*setErrorStrings)(unsigned errorCode, const char* errorClientOfDylibPath, const char* errorTargetDylibPath, const char* errorSymbol); ImageLoader* (*findImageContainingAddress)(const void* addr); void (*addDynamicReference)(ImageLoader* from, ImageLoader* to); #if SUPPORT_ACCELERATE_TABLES void (*notifySingleFromCache)(dyld_image_states, const mach_header* mh, const char* path); dyld_image_state_change_handler (*getPreInitNotifyHandler)(unsigned index); dyld_image_state_change_handler (*getBoundBatchHandler)(unsigned index); #endif #if SUPPORT_OLD_CRT_INITIALIZATION void (*setRunInitialzersOldWay)(); #endif BindingOptions bindingOptions; int argc; const char** argv; const char** envp; const char** apple; const char* progname; ProgramVars programVars; ImageLoader* mainExecutable; const char* imageSuffix; #if SUPPORT_ROOT_PATH const char** rootPaths; #endif const dyld_interpose_tuple* dynamicInterposeArray; size_t dynamicInterposeCount; PrebindMode prebindUsage; SharedRegionMode sharedRegionMode; bool dyldLoadedAtSameAddressNeededBySharedCache; bool strictMachORequired; bool requireCodeSignature; bool mainExecutableCodeSigned; bool preFetchDisabled; bool prebinding; bool bindFlat; bool linkingMainExecutable; bool startedInitializingMainExecutable; #if __MAC_OS_X_VERSION_MIN_REQUIRED bool processIsRestricted; bool processUsingLibraryValidation; #endif bool verboseOpts; bool verboseEnv; bool verboseLoading; bool verboseMapping; bool verboseRebase; bool verboseBind; bool verboseWeakBind; bool verboseInit; bool verboseDOF; bool verbosePrebinding; bool verboseCoreSymbolication; bool verboseWarnings; bool verboseRPaths; bool verboseInterposing; bool verboseCodeSignatures; };
可以看到,在 struct LinkContext 的定义中,programVars 是一个 ProgramVars 的实体,而这个实体又是在 LinkContext 的实体中,并且我们知道这个 LinkContext 的实体就是 dyld 中的全局变量 gLinkContext。因此,在 dyld 给我们的 initializer 传递参数时,
func(context.argc, context.argv, context.envp, context.apple, &context.programVars)
我们得到了 context.programVars 的地址,而这个地址属于 gLinkContext 实体的地址空间。如下图所示
那么拿到 gLinkContext 的地址之后,我们可以做什么呢?别忘了 struct LinkContext 中那一大堆函数指针w
gLinkContext 是一个结构体类型的全局变量,在它的地址空间中保存的是那些函数的地址,我们拿到了 gLinkContext 的地址的话,就可以改写那些函数的指向了,从而达到 hook 的目的。
至于说这个 gLinkContext 还有没有别的“使用方法”,这个还真不知道,目前而言,的确是没有太多的权限,也给不了什么信息,不过的确是可以让使用者获得本不应该出现的一些数据,甚至进行 hook 。
代码如下
// // dyld.mm // DYLD // // Created by Ryza 2017/5/7. // Copyright © 2017[data deleted]. All rights reserved. // #import <Foundation/Foundation.h> #import "dyld_defs.h" static void (*notifySingle_orig)(dyld_image_states, const ImageLoader* image, InitializerTimingList*); void notifiySingle_hook(dyld_image_states states, const ImageLoader* image, InitializerTimingList* list) { NSLog(@"[Hook] %s", __func__); (*notifySingle_orig)(states, image, list); } static struct LinkContext * linkContext = NULL; #define HookLinkContext(func, orig, hook)\ orig = *linkContext->func;\ linkContext->func = &hook; extern "C" void dashinit(int argc, const char * argv[], char ** envp, char ** apple, struct ProgramVars * pvars) { // 我们可以通过 pvars 得到 dyld`gLinkContext // 因为 dyld`ImageLoaderMachO::doImageInit 向我们传入的是 &context.programVars linkContext = (structLinkContext *)((long)pvars - offsetof(structLinkContext, programVars) + sizeof(void *)); // dyld`gLinkContext 中有一组函数指针,我们可以在这里进行 Hook HookLinkContext(notifySingle, notifySingle_orig, notifiySingle_hook); }
大佬,dyld_defs.h 内容 能不能发一下