我想拦截应用程序对dlsym的调用。我已经尝试在.so中声明我正在预装dlsym,并使用dlsym本身获取它的真实地址,但是由于非常明显的原因,这是行不通的。
有什么方法比获取进程的内存映射和使用libelf在加载的libdl.so中查找dlsym的真实位置更容易呢?
发布于 2013-09-16 09:52:01
警告
我必须向所有试图这么做的人发出明确的警告。共享库连接dlsym的一般前提有几个明显的缺点。最大的问题是,最初的dlsym实现(如果glibc内部使用堆栈展开技术)来查找函数是从哪个加载模块调用的。如果拦截共享库然后代表原始应用程序调用原始dlsym,这将使中断使用RTLD_NEXT之类的东西进行查找,因为现在当前模块不是最初调用的模块,而是您的钩子库。
也许可以用正确的方式来实现这一点,但是它需要做更多的工作。在没有尝试的情况下,我认为使用dlinfo来获取链接映射的链表,您可以单独遍历所有模块,并为每个模块执行一个单独的dlsym,以使RTLD_NEXT行为正确。您仍然需要获得调用者的地址,这可能是通过旧的backtrace(3)系列函数获得的。
我2013年的旧答案,
我无意中发现了与评论相同的问题:直接调用__libc_dlsym()时会出现分段故障。在阅读了一些glibc源代码之后,我想出了以下解决方案:
extern void *_dl_sym(void *, const char *, void *);
extern void *dlsym(void *handle, const char *name)
{
/* my target binary is even asking for dlsym() via dlsym()... */
if (!strcmp(name,"dlsym"))
return (void*)dlsym;
return _dl_sym(handle, name, dlsym);
}请注意这个“解决方案”的两件事:
(__libc_)dlsym()在内部完成的锁定,因此,要使这个线程安全,您应该添加一些锁定。_dl_sym()的参数是调用者的地址,glibc似乎通过堆栈展开来重构这个值,但我只使用函数本身的地址。调用方地址在内部用于查找调用方所处的链接映射,以获得RTLD_NEXT正确的内容(使用NULL作为fail参数将使调用在使用RTLD_NEXT时出现错误而失败)。但是,我还没有看过glibc的unwindind功能,所以我不能100%肯定上面的代码会做正确的事情,而且它可能碰巧只是偶然的.到目前为止,提出的解决方案有一些明显的缺点:在某些情况下,_dl_sym()的行为与预期的dlsym()非常不同。例如,尝试解析不存在的符号会退出程序,而不只是返回NULL。要解决这个问题,您可以使用_dl_sym()来获取指向原始dlsym()的指针,并将其用于其他任何东西(就像“标准”LD_PRELOAD钩子方法中完全不挂钩dlsym ):
extern void *_dl_sym(void *, const char *, void *);
extern void *dlsym(void *handle, const char *name)
{
static void * (*real_dlsym)(void *, const char *)=NULL;
if (real_dlsym == NULL)
real_dlsym=_dl_sym(RTLD_NEXT, "dlsym", dlsym);
/* my target binary is even asking for dlsym() via dlsym()... */
if (!strcmp(name,"dlsym"))
return (void*)dlsym;
return real_dlsym(handle,name);
}更新2021年/ glibc-2.34
从glibc 2.34开始,函数_dl_sym()不再公开导出。我可以建议的另一种方法是使用dlvsym(),这是glibc和ABI的非正式部分。唯一的缺点是,现在需要确切的版本来请求dlsym符号。幸运的是,这也是glibc的一部分,不幸的是,它的架构各不相同。但是,glibc源的根文件夹中的grep 'GLIBC_.*\bdlsym\b' -r sysdeps将告诉您需要什么:
[...]
sysdeps/unix/sysv/linux/i386/libc.abilist:GLIBC_2.0 dlsym F
sysdeps/unix/sysv/linux/i386/libc.abilist:GLIBC_2.34 dlsym F
[...]
sysdeps/unix/sysv/linux/x86_64/64/libc.abilist:GLIBC_2.2.5 dlsym F
sysdeps/unix/sysv/linux/x86_64/64/libc.abilist:GLIBC_2.34 dlsym FGlibc-2.34实际上引入了这个函数的新版本,但是旧版本仍然保持在向后兼容的状态。
对于x86_64,您可以使用:
real_dlsym=dlvsym(RTLD_NEXT, "dlsym", "GLIBC_2.2.5");而且,如果您都希望获得最新版本,以及可能在同一进程中获得另一个拦截器,您可以再次使用该版本执行不版本查询:
real_dlsym=real_dlsym(RTLD_NEXT, "dlsym");如果您实际上需要将dlsym和dlvsym连接到您的共享对象中,那么这种方法当然也不能工作。
dlsym()更新:同时连接和 dlvsym()
出于好奇,我考虑了一些方法来连接两个glibc符号查询方法,我想出了一个使用链接到libdl的额外包装库的解决方案。其思想是,拦截器库可以在运行时使用带有dlopen()标志的RTLD_LOCAL | RTLD_DEEPBIND动态加载该库,这将为该对象创建一个单独的链接器作用域,其中也包含libdl,以便将dlsym和dlvsym解析为原始方法,而不是拦截器库中的方法。现在的问题是我们的拦截器库不能直接调用包装库中的任何函数,因为我们不能使用dlsym,这是我们最初的问题。
但是,共享库可以具有一个初始化函数,链接器将在dlopen()返回之前调用该函数。我们只需要将一些信息从包装器库的初始化函数传递到拦截器库。因为两者都在同一个过程中,所以我们可以使用环境块来实现这一点。
这是我想出的代码:
dlsym_wrapper.h
#ifndef DLSYM_WRAPPER_H
#define DLSYM_WRAPPER_H
#define DLSYM_WRAPPER_ENVNAME "DLSYM_WRAPPER_ORIG_FPTR"
#define DLSYM_WRAPPER_NAME "dlsym_wrapper.so"
typedef void* (*DLSYM_PROC_T)(void*, const char*);
#endifdlsym_wrapper.c,编译为dlsym_wrapper.so
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include "dlsym_wrapper.h"
__attribute__((constructor))
static void dlsym_wrapper_init()
{
if (getenv(DLSYM_WRAPPER_ENVNAME) == NULL) {
/* big enough to hold our pointer as hex string, plus a NUL-terminator */
char buf[sizeof(DLSYM_PROC_T)*2 + 3];
DLSYM_PROC_T dlsym_ptr=dlsym;
if (snprintf(buf, sizeof(buf), "%p", dlsym_ptr) < (int)sizeof(buf)) {
buf[sizeof(buf)-1] = 0;
if (setenv(DLSYM_WRAPPER_ENVNAME, buf, 1)) {
// error, setenv failed ...
}
} else {
// error, writing pointer hex string failed ...
}
} else {
// error: environment variable already set ...
}
}并且在拦截器库中有一个函数来获取指向原始dlsym()的指针(应该只调用一次,由一个互斥):
static void *dlsym_wrapper_get_dlsym
{
char dlsym_wrapper_name = DLSYM_WRAPPER_NAME;
void *wrapper;
const char * ptr_str;
void *res = NULL;
void *ptr = NULL;
if (getenv(DLSYM_WRAPPER_ENVNAME)) {
// error: already defined, shoudn't be...
}
wrapper = dlopen(dlsym_wrapper_name, RTLD_LAZY | RTLD_LOCAL | RTLD_DEEPBIND | RTLD_NOLOAD);
if (wrapper) {
// error: dlsym_wrapper.so already loaded ...
// it is important that we load it by ourselves to a sepearte linker scope
}
wrapper = dlopen(dlsym_wrapper_name, RTLD_LAZY | RTLD_LOCAL | RTLD_DEEPBIND);
if (!wrapper) {
// error: dlsym_wrapper.so can't be loaded
}
ptr_str = getenv(DLSYM_WRAPPER_ENVNAME);
if (!ptr_str) {
// error: dlsym_wrapper.so failed...
}
if (sscanf(ptr_str, "%p", &ptr) == 1) {
if (ptr) {
// success!
res = ptr;
} else {
// error: got invalid pointer ...
}
} else {
// error: failed to parse pointer...
}
// this is a bit evil: close the wrapper. we can be sure
// that libdl still is used, as this mosule uses it (dlopen)
dlclose(wrapper);
return res;
}当然,这假定dlsym_wrapper.so位于库搜索路径中。但是,您可能更喜欢使用完全路径通过LD_PRELOAD注入拦截器库,而完全不修改LD_LIBRARY_PATH。为此,您可以添加dladdr(dlsym_wrapper_get_dlsym,...)来查找注入器库本身的路径,并将其用于搜索包装库。
发布于 2013-03-24 13:38:58
http://www.linuxforu.com/2011/08/lets-hook-a-library-function/
从案文中:
当您需要在钩子中调用__libc_dlsym (句柄,符号)时,请注意它们本身调用dlsym()的函数。
extern void *__libc_dlsym (void *, const char *);
void *dlsym(void *handle, const char *symbol)
{
printf("Ha Ha...dlsym() Hooked\n");
void* result = __libc_dlsym(handle, symbol); /* now, this will call dlsym() library function */
return result;
}https://stackoverflow.com/questions/15599026
复制相似问题