如何在 dlopen() 内拦截文件系统访问?

发布于 2024-12-09 00:04:34 字数 861 浏览 1 评论 0原文

我想拦截 dlopen() 内部发生的所有文件系统访问。起初,LD_PRELOAD-Wl,-wrap, 似乎是可行的解决方案,但由于一些技术原因,我在使它们工作时遇到了困难:

  • ld.so 在处理 LD_PRELOAD 时已经映射了自己的符号。对我来说,拦截初始加载并不重要,但 _dl_* 工作函数此时已解析,因此将来的调用将通过它们。我认为 LD_PRELOAD 已经太晚了。

  • 不知何故,malloc 规避了上述问题,因为 ld.so 内部的 malloc() 没有函数 free(),它只需调用 memset()

  • ld.so 中包含的文件系统工作函数(例如 __libc_read())是静态的,因此我无法使用 -Wl,- 拦截它们wrap,__libc_read

这可能意味着我需要直接从源代码构建自己的 ld.so ,而不是将其链接到包装器中。面临的挑战是 libcrtld-libc 都是从同一源构建的。我知道宏IS_IN_rtld是在构建rtld-libc时定义的,但是如何保证静态数据结构只有一份副本,同时仍然导出公共接口函数? (这是一个 glibc 构建系统问题,但我还没有找到这些细节的文档。)

是否有更好的方法来进入 dlopen() ?

注意:我无法使用像 FUSE 这样的特定于 Linux 的解决方案,因为这是针对不支持此类功能的最小“计算节点”内核。

I want to intercept all file system access that occurs inside of dlopen(). At first, it would seem like LD_PRELOAD or -Wl,-wrap, would be viable solutions, but I have had trouble making them work due to some technical reasons:

  • ld.so has already mapped its own symbols by the time LD_PRELOAD is processed. It's not critical for me to intercept the initial loading, but the _dl_* worker functions are resolved at this time, so future calls go through them. I think LD_PRELOAD is too late.

  • Somehow malloc circumvents the issue above because the malloc() inside of ld.so does not have a functional free(), it just calls memset().

  • The file system worker functions, e.g. __libc_read(), contained in ld.so are static so I can't intercept them with -Wl,-wrap,__libc_read.

This might all mean that I need to build my own ld.so directly from source instead of linking it into a wrapper. The challenge there is that both libc and rtld-libc are built from the same source. I know that the macro IS_IN_rtld is defined when building rtld-libc, but how can I guarantee that there is only one copy of static data structures while still exporting a public interface function? (This is a glibc build system question, but I haven't found documentation of these details.)

Are there any better ways to get inside dlopen()?

Note: I can't use a Linux-specific solution like FUSE because this is for minimal "compute-node" kernels that do not support such things.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

倦话 2024-12-16 00:04:34

LD_PRELOAD 或 -Wl,-wrap 似乎是可行的解决方案

--wrap 解决方案不可能可行:它仅在(静态)链接时工作,并且您的 ld .solibc.so.6libdl.so.2 都已经链接了,所以现在使用 已经太晚了 - -换行

LD_PRELOAD 本来可以工作,除了... ld.so 认为 dlopen() 调用 open() 是一个内部实现细节。因此,它只是调用内部 __open 函数,绕过 PLT 以及您使用它插入 open 的能力。

malloc 以某种方式规避了这个问题

这是因为 libc 支持实现自己的 malloc 的用户(例如用于调试目的)。因此,从 dlopen 调用 calloc 确实会经过 PLT,并且可以通过 LD_PRELOAD 进行插入。

这可能意味着我需要直接从源代码构建自己的 ld.so,而不是将其链接到包装器中。

重建后的ld.so会做什么?我认为你希望它调用 __libc_open (在 libc.so.6 中),但这不可能工作,原因很明显:它是 ld.so 首先(在进程启动时)打开s libc.so.6

您可以重建 ld.so,将对 __open 的调用替换为对 open 的调用。这将导致 ld.so 通过 PLT,并将其暴露给 LD_PRELOAD 插入。

如果您走这条路,我建议您不要用新副本覆盖系统 ld.so (犯错误并导致系统无法启动的可能性太大)。相反,请将其安装到例如 /usr/local/my-ld.so,然后使用 -Wl,--dynamic-linker=/usr/local/my-ld 链接二进制文件.so.

另一种选择:运行时修补。这有点像黑客,但您可以(一旦您在 main 中获得控制权)只需扫描 ld.so.text,然后查找 CALL __open 指令。如果ld.so没有被剥离,那么你可以找到内部的__open,以及你想要修补的函数(例如open_verify在<代码>dl-load.c)。一旦找到有趣的CALLmprotect包含它的页面可写,并修补您自己的插入器的地址(它可以依次调用__libc_open 如果需要),然后mprotect 将其恢复。任何未来的 dlopen() 现在都将通过您的插入器。

it would seem like LD_PRELOAD or -Wl,-wrap, would be viable solutions

The --wrap solution could not possibly be viable: it works only at (static) link time, and your ld.so and libc.so.6 and libdl.so.2 have all already been linked, so now it is too late to use --wrap.

The LD_PRELOAD could have worked, except ... ld.so considers the fact that dlopen() calls open() an internal implementation detail. As such, it just calls the internal __open function, bypassing PLT, and your ability to interpose open with it.

Somehow malloc circumvents the issue

That's because libc supports users who implement their own malloc (e.g. for debugging purposes). So the call to e.g. calloc from dlopen does go through PLT, and is interposable via LD_PRELOAD.

This might all mean that I need to build my own ld.so directly from source instead of linking it into a wrapper.

What will the rebuilt ld.so do? I think you want it to call __libc_open (in libc.so.6), but that can't possibly work for obvious reason: it is ld.so that opens libc.so.6 in the first place (at process startup).

You could rebuild ld.so with the call to __open replaced with a call to open. That will cause ld.so to go through PLT, and expose it to LD_PRELOAD interposition.

If you go that route, I suggest that you don't overwrite the system ld.so with your new copy (the chance of making a mistake and rendering the system unbootable is just too great). Instead, install it to e.g. /usr/local/my-ld.so, and then link your binaries with -Wl,--dynamic-linker=/usr/local/my-ld.so.

Another alternative: runtime patching. This is a bit of a hack, but you can (once you gain control in main) simply scan the .text of ld.so, and look for CALL __open instructions. If ld.so is not stripped, then you can find both the internal __open, and the functions you want to patch (e.g. open_verify in dl-load.c). Once you find the interesting CALL, mprotect the page that contains it to be writable, and patch in the address of your own interposer (which can in turn call __libc_open if it needs to), then mprotect it back. Any future dlopen() will now go through your interposer.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文