LD_PRELOAD, DYLD_INSERT_LIBRARIES 和 Cydia Substrate

0x00 背景

正好看到了Substrate的源代码:,就顺便想着分析一下Cydia Substrate的原理。
Android的代码在这里(逆向的代码):https://github.com/rootkiter/Reverse-Engineering/tree/master/CydiaSubstrate

0x01 iOS substrate原理

简单来说就是设置环境变量,用DYLD_INSERT_LIBRARIES来加载subsrate的注入代码:
https://github.com/jevinskie/substrate/blob/97fa4bae349b867ae789bb756f6c45c311d16e7d/Environment.hpp#L25-L26
上面这个MobileSubstrate.dylib其实就是SubstrateInjection.dylib的软连接:
https://github.com/jevinskie/substrate/blob/97fa4bae349b867ae789bb756f6c45c311d16e7d/package.sh#L78

而SubstrateInjection.dylib其实又是SubstrateBootstrap.dylib的软连接:
https://github.com/jevinskie/substrate/blob/97fa4bae349b867ae789bb756f6c45c311d16e7d/package.sh#L80

SubstrateBootstrap.dylib的代码在Bootstrap.cpp里,我们可以看到这个文件加载了SubstrateLoader.dylib: https://github.com/jevinskie/substrate/blob/master/Bootstrap.cpp#L42

而SubstrateLoader.dylib的代码又通过读取plist文件加载了我们自己的hooking dylib https://github.com/jevinskie/substrate/blob/master/DarwinLoader.cpp#L377

在我们自己的dylib中,我们就可以通过MSHookFunction来进行hook了。

0x02 使用DYLD_INSERT_LIBRARIES的注意事项

使用DYLD_INSERT_LIBRARIES和LD_PRELOAD(android, linux etc.)相同,如果你想hook的目标库是在preload的库加载后加载的,显然你无法hook目标(当然,你可以用dlopen加载目标库,然后再进行memory patch)。而在substrate中并没有这样的操作,这是因为目标库几乎总是优先于preload的库被加载。我们可以在linux上做一个实验:

//1.c
#include <stdlib.h>
#include <stdio.h>
__attribute__((constructor)) void bar()
{
    printf("lib1.so\n");
}

void test()
{
    printf("lib1 test\n");
}
//2.c
#include <stdlib.h>
#include <stdio.h>
__attribute__((constructor)) void foo()
{
    printf("lib2.so\n");
}

void callee()
{
    printf("lib2 callee\n");
}
//inject.c
#include <stdlib.h>
#include <stdio.h>
__attribute__((constructor)) void var()
{
    printf("libinject.so\n");
}
//hello.c
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <unistd.h>
void callee(void);

typedef void t(void);
int main(void) 
{ 
    callee();
    void *h = dlopen("./lib1.so", RTLD_NOW);
    void *test = dlsym(h, "test");
    ((t*)test)();
    if (test == NULL)
        exit(1);
    while (1){
        sleep(1);
        printf("Hello main!\n"); 
    }
    return 0;
}
//Makefile
all:
    gcc -shared -o libinject.so -fPIC inject.c
    gcc -shared -o lib1.so -fPIC 1.c
    gcc -shared -o lib2.so -fPIC 2.c
    gcc hello.c -L. -l2 -ldl
    # export LD_LIBRARY_PATH=$$LD_LIBRARY_PATH:.
    # LD_PRELOAD=./libinject.so ./a.out

有这样几个文件,我们make一下,然后修改一下环境变量export LD_LIBRARY_PATH=$$LD_LIBRARY_PATH:.并执行LD_PRELOAD=./libinject.so ./a.out,输出结果:

lib2.so
libinject.so
lib2 callee
lib1.so
lib1 test
Hello main!
Hello main!
Hello main!
^C

可以看到libinject是在lib2之后才被加载的,并且可执行文件总是被最先加载的。

iOS也类似(并没试验过,此处不确定,但是应该是正确的),iOS APP只有一个巨大的可执行文件,大部分逻辑都在它里面,它总是被最先加载的,如果我们用debugger连接上app,并执行image list命令,我们会发现这个可执行文件总是在第一个,说明它是第一个被加载的,这样的话就不用担心DYLD_INSERT_LIBRARIES的库加载时我们的目标库或可执行文件没被加载这个问题了。

0x03 Android Substrate原理

安卓上用的不是LD_PRELOAD,从这个逆向代码中可以发现,安卓上是伪造了一个liblog.so,这样所有app启动的时候都会加载这个伪造的liblog.so,这个liblog.so其实就是libAndroidBootstrap0.so,这样就跟iOS同样,注入了substrate自己的动态链接库了。