如何在使用dlopen加载的库中覆盖全局符号?

3

这里涉及到三个组件:

  • 主程序:加载 loader.so 的主程序
  • loader.so:使用 -Bsymbolic 编译,重写 puts 并加载 other.so
  • other.so:调用 puts,且不可修改

如何让 other.so 使用被 loader.so 重写的 puts

请注意,我希望仅从 loader.so 开始(包括 other.so)重写 puts,而不影响主程序。

以下是示例代码:

main.c

#include <stdio.h>
#include <dlfcn.h>

int main(int argc, char *argv[]){
    dlopen("./loader.so", RTLD_NOW | RTLD_GLOBAL | RTLD_DEEPBIND);
    puts("Normal");
    return 0;
}

loader.c

#include <stdio.h>
#include <dlfcn.h>

extern int puts(const char *s){
    fputs("Hooked: ", stdout);
    fputs(s, stdout);
    fputc('\n', stdout);
    return 0;
}

__attribute__((constructor))
void ctor(void) {
    puts("Something");
    void *other = dlopen("./other.so", RTLD_NOW);
}

other.c

#include <stdio.h>

__attribute__((constructor))
void ctor(void) {
    puts("Hello!");
}

make.sh

#!/bin/bash
gcc main.c -o main -ldl
gcc loader.c -fPIC -shared -Wl,-Bsymbolic -o loader.so
gcc other.c -fPIC -shared -o other.so

Desired output

Hooked: Something
Hooked: Hello!
Normal

实际输出

Hooked: Something
Hello!
Normal
2个回答

1
在进一步研究该问题后,我找到了一个解决方案,需要使用外部工具patchelf的帮助,因此我将等待接受此解决方案,以防有不同的解决方法。
该解决方案是通过创建一个新的共享对象shared.so并修改其中的puts来实现的,如下所示。
int puts(const char *s){
    fputs("Hooked: ", stdout);
    fputs(s, stdout);
    fputc('\n', stdout);
    return 0;
}

我们需要强制other.so依赖于这个新的共享对象,可以使用patchelf --add-needed shared.so other.so来实现。
这确实涉及对other.so的修改,但不需要重新编译源代码(这使得这种方法更加可行)。
现在,当我们加载other.so时,我们需要在loader.c中指定RTLD_DEEPBIND,像这样:
void *other = dlopen("./libother.so", RTLD_NOW | RTLD_GLOBAL | RTLD_DEEPBIND);

为了使搜索顺序不从全局上下文开始,而是从库本身开始。 由于other.so没有定义puts,因此将查找直接依赖项,并在shared.so中找到putsRTLD_DEEPBIND的属性确保即使出现LD_PRELOAD对象,也会被覆盖。
因此,如果在预加载的共享对象内禁用了puts,我们可以绕过该限制并从glibc调用真正未修改的puts(仅对源自other.so的调用)。
如果我们只想恢复原始行为,则不需要任何patchelfshared.so

0

尝试添加标志-Wl,--no-as-needed

gcc loader.c -fPIC -shared -Wl,-Bsymbolic -Wl,--no-as-needed -o loader.so

我已经成功地从C库中挂钩了与时间相关的函数time-machine


感谢提案,但似乎没有任何区别。我尝试在运行时修改other.so的符号搜索顺序,以便它可以解析loader.so中的puts - Smx

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接