如何在 dlopen() 内拦截文件系统访问?
我想拦截 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 ,而不是将其链接到包装器中。面临的挑战是 libc
和 rtld-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 thinkLD_PRELOAD
is too late.Somehow
malloc
circumvents the issue above because themalloc()
inside of ld.so does not have a functionalfree()
, it just callsmemset()
.The file system worker functions, e.g.
__libc_read()
, contained inld.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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
--wrap
解决方案不可能可行:它仅在(静态)链接时工作,并且您的ld .so
和libc.so.6
和libdl.so.2
都已经链接了,所以现在使用已经太晚了 - -换行
。LD_PRELOAD
本来可以工作,除了... ld.so 认为dlopen()
调用open()
是一个内部实现细节。因此,它只是调用内部 __open 函数,绕过PLT
以及您使用它插入open
的能力。这是因为 libc 支持实现自己的 malloc 的用户(例如用于调试目的)。因此,从
dlopen
调用calloc
确实会经过PLT
,并且可以通过LD_PRELOAD
进行插入。重建后的
ld.so
会做什么?我认为你希望它调用__libc_open
(在libc.so.6
中),但这不可能工作,原因很明显:它是ld.so
首先(在进程启动时)打开
slibc.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)。一旦找到有趣的CALL
,mprotect
包含它的页面可写,并修补您自己的插入器的地址(它可以依次调用__libc_open
如果需要),然后mprotect
将其恢复。任何未来的dlopen()
现在都将通过您的插入器。The
--wrap
solution could not possibly be viable: it works only at (static) link time, and yourld.so
andlibc.so.6
andlibdl.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 thatdlopen()
callsopen()
an internal implementation detail. As such, it just calls the internal__open
function, bypassingPLT
, and your ability to interposeopen
with it.That's because
libc
supports users who implement their ownmalloc
(e.g. for debugging purposes). So the call to e.g.calloc
fromdlopen
does go throughPLT
, and is interposable viaLD_PRELOAD
.What will the rebuilt
ld.so
do? I think you want it to call__libc_open
(inlibc.so.6
), but that can't possibly work for obvious reason: it isld.so
thatopen
slibc.so.6
in the first place (at process startup).You could rebuild
ld.so
with the call to__open
replaced with a call toopen
. That will causeld.so
to go throughPLT
, and expose it toLD_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
ofld.so
, and look forCALL __open
instructions. Ifld.so
is not stripped, then you can find both the internal__open
, and the functions you want to patch (e.g.open_verify
indl-load.c
). Once you find the interestingCALL
,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), thenmprotect
it back. Any futuredlopen()
will now go through your interposer.