gpt4 book ai didi

c - 避免LD_PRELOAD:包装库并提供libc请求的功能

转载 作者:太空宇宙 更新时间:2023-11-03 23:19:55 25 4
gpt4 key购买 nike

我有一个共享库,比如somelib.so,它使用libc中的ioctl(根据objdump)。
我的目标是编写一个新的库,该库包装somelib.so并提供自定义ioctl我希望避免预加载库,以确保只有somelib.so中的调用使用自定义ioctl
以下是我当前的片段:

typedef int (*entryfunctionFromSomelib_t) (int par, int opt);
typedef int (*ioctl_t) (int fd, int request, void *data);
ioctl_t real_ioctl = NULL;

int ioctl(int fd, int request, void *data )
{
fprintf( stderr, "trying to wrap ioctl\n" );
void *handle = dlopen( "libc.so.6", RTLD_NOW );
if (!handle)
fprintf( stderr, "Error loading libc.so.6: %s\n", strerror(errno) );

real_ioctl = (ioctl_t) dlsym( handle, "ioctl" );
return real_ioctl( fd, request, data);
}

int entryfunctionFromSomelib( int par, int opt ) {
void *handle = dlopen( "/.../somelib.so", RTLD_NOW );
if (!handle)
fprintf( stderr, "Error loading somelib.so: %s\n", strerror(errno) );

real_entryfunctionFromSomelib = entryfunctionFromSomelib_t dlsym( handle, "entryfunctionFromSomelib" );
return real_entryfunctionFromSomelib( par, opt );
}

但是,从某种意义上说,对 ioctl表单 somelib.so的调用没有重定向到我的自定义 ioctl实现,这是行不通的我如何强制包装的 somelib.so执行此操作?
======================
在@Nominal Animal post后面添加的附加信息:
下面是通过 readelf -s | grep functionname从mylib.so(编辑后的somelib.so)获得的一些信息:
   246: 0000000000000000   121 FUNC    GLOBAL DEFAULT  UND dlsym@GLIBC_2.2.5 (11)
42427: 0000000000000000 121 FUNC GLOBAL DEFAULT UND dlsym@@GLIBC_2.2.5


184: 0000000000000000 37 FUNC GLOBAL DEFAULT UND ioctl@GLIBC_2.2.5 (6)
42364: 0000000000000000 37 FUNC GLOBAL DEFAULT UND ioctl@@GLIBC_2.2.5

在“patching”mylib.so之后,它还将新功能显示为:
   184: 0000000000000000    37 FUNC    GLOBAL DEFAULT  UND iqct1@GLIBC_2.2.5 (6)

我对wrap_mylib库中的符号进行了“版本化”并将其导出,现在 readelf显示了:
25: 0000000000000d15   344 FUNC    GLOBAL DEFAULT   12 iqct1@GLIBC_2.2.5
63: 0000000000000d15 344 FUNC GLOBAL DEFAULT 12 iqct1@GLIBC_2.2.5

但是,当我尝试 dlopen包装mylib时,会出现以下错误:
symbol iqct1, version GLIBC_2.2.5 not defined in file libc.so.6 with link time reference

可能是因为mylib.so试图从libc.so.6中 dlsym iqct1

最佳答案

如果binutils'objcopy可以修改动态符号,mylib.so是一个ELF动态库,我们可以使用

mv  mylib.so  old.mylib.so
objcopy --redefine-sym ioctl=mylib_ioctl old.mylib.so mylib.so

将库中的符号名从 ioctl重命名为 mylib_ioctl,以便我们可以实现
int mylib_ioctl(int fd, int request, void *data);

在另一个链接到最终二进制文件的库或对象中。
不幸的是, this feature is not implemented(至少截至2017年初)。
如果替换符号名的长度与原始名称的长度完全相同,我们可以使用丑陋的黑客来解决这个问题符号名是ELF文件中的一个字符串(前面和后面都有一个nul字节),因此我们可以使用GNU sed替换它:
LANG=C LC_ALL=C sed -e 's|\x00ioctl\x00|\x00iqct1\x00|g' old.mylib.so > mylib.so

这将替换从 ioctl()iqct1()的名称这显然不是最优的,但这似乎是最简单的选择。
如果您发现需要向实现的 iqct1()函数添加版本信息,那么使用GCC您只需添加一行类似于
__asm__(".symver iqct1,iqct1@GLIBC_2.2.5");

其中版本跟随 @字符。
下面是一个实际的例子,展示了我是如何在实践中测试的。
首先,让我们创建mylib.c,表示mylib.c的源代码(OP没有这个代码——否则只需修改源代码并重新编译库就可以解决这个问题):
#include <unistd.h>
#include <errno.h>

int myfunc(const char *message)
{
int retval = 0;

if (message) {
const char *end = message;
int saved_errno;
ssize_t n;

while (*end)
end++;

saved_errno = errno;

while (message < end) {
n = write(STDERR_FILENO, message, (size_t)(end - message));
if (n > 0)
message += n;
else {
if (n == -1)
retval = errno;
else
retval = EIO;
break;
}
}

errno = saved_errno;
}

return retval;
}

导出的唯一函数是 myfunc(message),如mylib.h中声明的:
#ifndef   MYLIB_H
#define MYLIB_H

int myfunc(const char *message);

#endif /* MYLIB_H */

让我们将mylib.c编译成一个动态共享库, mylib.so
gcc -Wall -O2 -fPIC -shared mylib.c -Wl,-soname,libmylib.so -o mylib.so

我们希望在自己的程序中使用自己设计的 write(),而不是来自C库的 ioctl()(这是一个类似于 mywrt()的POSIX函数,不是标准的C函数)上面的命令将原始库保存为 mylib.so(同时在内部将其命名为 libmylib.so),因此我们可以使用
sed -e 's|\x00write\x00|\x00mywrt\x00|g' mylib.so > libmylib.so

要更改符号名,请将修改后的库另存为 libmylib.so
接下来,我们需要一个测试可执行文件,它提供 ssize_t mywrt(int fd, const void *buf, size_t count);函数(原型与它替换的 write(2)函数相同)测试c:
#include <stdlib.h>
#include <stdio.h>
#include "mylib.h"

ssize_t mywrt(int fd, const void *buffer, size_t bytes)
{
printf("write(%d, %p, %zu);\n", fd, buffer, bytes);
return bytes;
}
__asm__(".symver mywrt,mywrt@GLIBC_2.2.5");

int main(void)
{
myfunc("Hello, world!\n");

return EXIT_SUCCESS;
}

.symver行指定 GLIBC_2.2.5的版本。
版本取决于使用的C库在这种情况下,我运行 mywrt,这给了我
00000000000f66d0  w   DF .text  000000000000005a  GLIBC_2.2.5 write

其中倒数第二个字段是所需的版本。
因为需要导出 objdump -T $(locate libc.so) 2>/dev/null | grep -e ' write$'符号以便动态库使用,所以我创建了test.syms:
{
mywrt;
};

为了编译测试可执行文件,我使用
gcc -Wall -O2 test.c -Wl,-dynamic-list,test.syms -L. -lmylib  -o test

因为 mywrt在当前工作目录中,我们需要将当前目录添加到动态库搜索路径:
export LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH

然后,我们可以运行测试二进制文件:
./test

它将输出类似于
write(2, 0xADDRESS, 14);

因为 libmylib.so函数就是这么做的如果我们想检查未修改的输出,我们可以运行 mywrt()并重新运行 mv -f mylib.so libmylib.so,然后只输出
Hello, world!

这表明,这种方法虽然依赖于对共享库文件的非常粗略的二进制修改(使用 ./test——但仅仅是因为 sed还不支持动态符号上的 objcopy),但在实践中应该工作得很好。
这也是开源优于专有库的一个完美例子:试图解决这个小问题的工作量至少比将库源中的 --redefine-sym调用重命名为例如 ioctl并重新编译它要高出一个数量级。
在OP的情况下,在最终二进制文件中插入 mylib_ioctl()(根据POSIX.1-2001标准化的 dlsym())似乎是必要的。
假设原始动态库是使用
sed -e 's|\x00ioctl\x00|\x00iqct1\x00|g;
s|\x00dlsym\x00|\x00d15ym\x00|g;' mylib.so > libmylib.so

我们将这两个自定义函数实现为
int iqct1(int fd, unsigned long request, void *data)
{
/* For OP to implement! */
}
__asm__(".symver iqct1,iqct1@GLIBC_2.2.5");

void *d15ym(void *handle, const char *symbol)
{
if (!strcmp(symbol, "ioctl"))
return iqct1;
else
if (!strcmp(symbol, "dlsym"))
return d15ym;
else
return dlsym(handle, symbol);
}
__asm__(".symver d15ym,d15ym@GLIBC_2.2.5");

请检查与您使用的C库相对应的版本上面对应的.syms文件将只包含
{
i1ct1型;
d15ym;
};
否则,该实现应与本答案前面所示的实际示例相同。
因为 <dlfcn.h>的实际原型是 ioctl(),所以没有保证它适用于 int ioctl(int, unsigned long, ...);的所有一般用途在Linux中,第二个参数的类型是 ioctl(),第三个参数要么是指针,要么是长的,要么是无符号的长的——在所有Linux体系结构中,指针和长/无符号的长都有相同的大小——所以它应该可以工作,除非实现 unsigned long的驱动程序也关闭了,在这种情况下,您只是被hose并且仅限于希望它能正常工作,或者切换到具有适当Linux支持和开源驱动程序的其他硬件。
上述特殊情况既有原始符号,也有硬线连接到替换的函数(我称之为替换符号,而不是插入符号,因为我们确实用这些符号替换了 ioctl()调用,而不是插入 mylib.soioctl()调用)
这是一种相当野蛮的方法,但是除了使用 dlsym()之外,由于在 sed中缺乏动态符号重新定义支持,它对于所做的事情和实际发生的事情非常健壮和清晰。

关于c - 避免LD_PRELOAD:包装库并提供libc请求的功能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42987641/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com