Yet Some Dyld Things To Play With

本来是想研究如何“简单”阻止 insert_dylib 注入的,但是好像并没有简单的方法,不过发现了一些 dyld 的东西,虽然目前来看似乎没有太大的意义,可是能拿到的东西肯定不是 dyld 想让应用拿到的。这篇 post 中的方法在 macOS 10.12.4 和 iOS 10.3.2 (14F5089a) 中均测试可行。

关于 iOS / macOS 中 dyld 如何初始化一个 MachO 的运行这里就不详细说了,在网上能找到这方面的一些资料。概要流程引用如下:

  1. 从 kernel 留下的原始调用栈引导和启动自己
  2. 将程序依赖的动态链接库递归加载进内存,当然这里有缓存机制
  3. non-lazy 符号立即 link 到可执行文件,lazy 的存表里
  4. Runs static initializers for the executable
  5. 找到可执行文件的 main 函数,准备参数并调用
  6. 程序执行中负责绑定 lazy 符号、提供 runtime dynamic loading services、提供调试器接口
  7. 程序main函数 return 后执行 static terminator
  8. 某些场景下 main 函数结束后调 libSystem 的 _exit 函数

——iOS 程序 main 函数之前发生了什么

那么在这篇 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)。

LC_ROUTINES_64
LC_ROUTINES_64

而第二种 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 的目的。

Hook dyld`gLinkContext->notifySingle
Hook dyld`gLinkContext->notifySingle

至于说这个 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);
}

One thought on “Yet Some Dyld Things To Play With”

Leave a Reply

Your email address will not be published. Required fields are marked *

7 + 10 =