在 macOS 上,如何拦截对 dlsym 的调用?

问题描述 投票:0回答:3

我正在尝试检查调用

dlopen
dlsym
的程序的行为:最终目标是插入一个小函数,记录对通过
dlsym
获得的函数的调用。

通常,对于您想要用

foo
包装的某些函数
DYLD_INSERT_LIBRARIES
,您可以编写如下内容:

void (*real_foo)(void);

void init() __attribute__((constructor)) {
    real_foo = dlsym(RTLD_NEXT, "foo");
}

void foo(void) {
    puts("foo was called");
    real_foo();
}

问题是,当你试图包装

dlsym
时,你最终会得到这样的结果:

void* (*real_dlsym)(void* handle, const char* symbol);

void init() __attribute__((constructor)) {
    real_dlsym = dlsym(RTLD_NEXT, "dlsym");
}

void* dlsym(void* handle, const char* symbol) {
    return real_dlsym(handle, symbol);
}

init
最终会调用您自己的
dlsym
实现,这没有帮助。

当我导出另一个 dlsym 时,如何获得“真正的”dlsym?或者有没有更好的方法来拦截对 dlsym 的调用?

macos dlopen
3个回答
0
投票

至少在 10.12 系统上,

dlsym
实际上是在
/usr/lib/system/libdyld.dylib
中实现的。它应该适用于
dlopen()
该文件,然后使用返回的句柄调用
dlsym()
以获得真正的
dlsym()
实现。您可能需要额外的
mode
标记到
dlopen()
,例如
RTLD_FIRST
和/或
RTLD_LOCAL
。有点像:

    void *libdyld_handle = dlopen("/usr/lib/system/libdyld.dylib", RTLD_LAZY);
    void *dlsym_funcptr = dlsym(libdyld_handle, "dlsym");

0
投票

迟来的答案说明了 Dyld4 实现的一个有趣的怪癖(即 MacOS 12.x Monterey 和 iOS15)。本质上,全局 dyld 对象在

main
启动时通过硬件寄存器泄漏其详细信息(无论平台如何)。

显然这是一种黑客行为,永远不要考虑在严肃的生产代码中这样做。

int main(int argc, char * argv[]) {
#ifdef __x86_64__
    register void* rbx asm("rbx");  //dyld4::gDyld
    register void* (*typed_dlopen)( void*, char const*, int) asm("rax");
    register void* (*typed_dlsym)(void*, char const*, void*) asm("rcx");

    __asm volatile(".intel_syntax noprefix;"
#if TARGET_OS_SIMULATOR
                   "add rbx,0x100000;"
#endif
                   "mov rax,[rbx];"
                   "mov rcx,[rax + 0x88];"
                   "mov rax,[rax + 0x70];"
                   : "=r"(typed_dlopen), "=r"(rbx), "=r"(typed_dlsym)); //dyld4::gDyld

    void* handle = typed_dlopen(rbx, "libc.dylib", RTLD_NOW);
    int (*myPrintf)(const char * __restrict, ...) = typed_dlsym(rbx, handle, "printf");

    myPrintf("Hello world");
#endif
#if TARGET_CPU_ARM64
    register void* x19 asm("x19");  //dyld4::gDyld
    register void* (*typed_dlopen)( void*, char const*, int) asm("x0");
    register void* (*typed_dlsym)(void*, char const*, void*) asm("x1");

    __asm volatile("ldr x0,[x19] \t\n"
                   "ldr x1,[x0, 0x88]\t\n"
                   "ldr x0,[x0, 0x70]\t\n"
                   : "=r"(typed_dlopen), "=r"(x19), "=r"(typed_dlsym)); //dyld4::gDyld

    void* handle = typed_dlopen(x19, "libc.dylib", RTLD_NOW);
    int (*myPrintf)(const char * __restrict, ...) = typed_dlsym(x19, handle, "printf");

    myPrintf("Hello world");
#endif
    //Rest of your startup code e.g. UIApplicationMain(...) / NSApplicationMain(...)
}

这些偏移量

0x88
0x70
来自原始
libdyld.dylib
,并在与 dyld4 内部接口时用于真正的
dlopen
dlsym
。简而言之,我们直接利用内部实现(因此额外的第一个参数是 c++
this
指针)。时间会证明它们是否会在 Apple 操作系统的未来版本中保持稳定。

这也可以改用

void __attribute__ ((constructor)) premain(void) {
代替(分别使用
r15
x8
x20
)。

void __attribute__ ((constructor)) premain(void) {
#ifdef __x86_64__
    register void* r15 asm("r15");  //dyld4::gDyld
    register void* (*typed_dlopen)( void*, char const*, int) asm("rax");
    register void* (*typed_dlsym)(void*, char const*, void*) asm("rcx");
    __asm volatile(".intel_syntax noprefix;"
                   "mov rax,[r15];"
                   "mov rcx,[rax + 0x88];"
                   "mov rax,[rax + 0x70];"
                   : "=r"(typed_dlopen), "=r"(r15), "=r"(typed_dlsym)); //dyld4::gDyld
    void* handle = typed_dlopen(r15, "libc.dylib", RTLD_NOW);
    int (*myPrintf)(const char * __restrict, ...) = typed_dlsym(r15, handle, "printf");

    myPrintf("Hello world");
#endif
#if TARGET_CPU_ARM64
    register void* x8 asm("x8");  //dyld4::gDyld
    register void* (*typed_dlopen)( void*, char const*, int) asm("x0");
    register void* (*typed_dlsym)(void*, char const*, void*) asm("x1");

    __asm volatile("ldr x0,[x8] \t\n"
                   "ldr x1,[x0, 0x88]\t\n"
                   "ldr x0,[x0, 0x70]\t\n"
                   : "=r"(typed_dlopen), "=r"(x8), "=r"(typed_dlsym)); //dyld4::gDyld

    void* handle = typed_dlopen(x8, "libc.dylib", RTLD_NOW);
    int (*myPrintf)(const char * __restrict, ...) = typed_dlsym(x8, handle, "printf");

    myPrintf("Hello world");
#endif
} 

模拟器更新 有趣的是,在主要变体中,我发现有 2 种不同的

dlopen
实现:

0x000000010e78272a (dyld`dyld4::APIs::dlopen(char const*, int))
0x000000010424b278 (dyld_sim`dyld4::APIs::dlopen(char const*, int))

前者在使用模拟器时会崩溃。但是,由于另一个粗略的巧合,premain 似乎使用了另一个(?)

//dyld4::gDyld
移动了偏移量
0x1000000
。如果我们将
rbx
移到
main
中,它就会起作用。

经黑客验证可用于:

  • MacOS 蒙特利 12.3.1,x86-64
  • MacOS 蒙特利 12.4,x86-64
  • MacOS 蒙特利 12.6,x86-64
  • iOS15.0 iPhone13 Pro
  • iOS15.1 iPad
  • iOS 模拟器 x86-64

iOS14.1使用Dyld3会崩溃。 未在 M1 及其同级产品上进行测试,但原则上它们应该像 iOS 手臂一样工作。


0
投票

从 macOS 15+/iOS 18+ 开始,您可以使用

_dyld_register_dlsym_notifier
SPI 来实现此目的: https://github.com/apple-oss-distributions/dyld/blob/65bbeed63cec73f313b1d636e63f243964725a9d/include/mach-o/dyld_priv.h#L962

© www.soinside.com 2019 - 2024. All rights reserved.