Linux下Hook一个共享库函数

来源:互联网 发布:守望先锋英雄伤害数据 编辑:程序博客网 时间:2024/06/07 08:34

有时程序员需要完成这类任务:

假如你有一个二进制版的系统,例如现在流行的android,你需要为这个系统开发一个软件。这个软件牵涉到系统行为,因此需要对系统做修改。然而你并没有这个系统的所有源码( Nexus S的源码不一定与 android 官方版本一模一样),或者是你只有这个系统的头文件。当你需要修改部分系统行为的时候,你不可能用源码重新编译一个共享库来替换系统文件。此时,就需要利用 dlfcn.h 运行时动态链接库来实现我们的目标。利用dlopen(),dlsym(),dlerror(),dlclose()这四个函数,可以实现类似 Windows 平台 Api Hook 机制的效果。


步骤如下:


1、首先得先将原共享库改名。假如系统中有一个共享库libwuxiao.so,那么就需要

把它改成其他名字,比如,改为 libwuxiao_orig.so。

假如这个共享库的源码如下:

#include <stdio.h>
void func_a(int a, int b)
{
printf("func_a called a=%d b=%d\n",a,b);
}
void func_b(char* string)
{
printf("func_b called string=%s\n",string);
}
int func_c()
{
return 123;
}

编译一下, gcc -shared -fPIC -o libwuxiao.so wuxiao.c

可以写一个可执行程序来测试一下这个共享库。之后,你可以看到我们完全不修改这个可执行文件,让这个可执行文件执行我们增加的代码。

#include <stdio.h>

extern void func_a(int a, int b);
extern void func_b(char* string);
extern int func_c();

int main(int argc, char* argv)
{
func_a(55,66);
func_b("hello,wuxiao");
int b = func_c();
printf("after func(), b=%d\n",b);
}

gcc -o wuxiao_test main.c -lwuxiao -L/path/to/libwuxiao.so


2、然后有两类编程方法实现wrapper:

a) 先调用dlopen(),返回libwuxiao_orig.so的句柄,再从句柄中查找想要的函数
#include <stdio.h>
#include <dlfcn.h>

static void* libwuxiao_handle = NULL;

__attribute__((constructor)) _init_wuxiao()
{
libwu_handle = dlopen("libwuxiao_orig.so",RTLD_NOW);  // 注意:已经改名字了
}

__attribute__((destructor)) void _fini_wuxiao()
{
dlclose(libwu_handle);
}

int func_c()
{
int (*func_c_ptr)();
func_c_ptr = dlsym(libwuxiao_handle,"func_c");
if ( func_c_ptr != NULL )
{
printf(" add your hook code here\n");
return func_c_ptr();
}
return -1;

}

链接方法:  gcc -shared -fPIC -o libwuxiao.so -ldl -lwuxiao_orig -L/path/to/libwuxiao_orig.so

(编译完上面的伪装共享库之后,执行一下 main_test,看看增加的代码有没有被执行。)

上面代码中,原共享库的 func_c 就是我们想要修改的函数。

这里写了一个相同的 func_c,它查找到原函数的地址,先执行 hook 代码,然后执行原 func_c 函数, 因而不会影响原函数的执行,只不过多执行了我们的代码。

随后 “伪装”成为libwuxiao.so,系统在执行程序的时候,就会查找伪造的 libwuxiao.so,

而伪造的libwuxiao.so又会去加载真正的 libwuxiao_orig.so。
这样达到了我们的目标:

1、保证原libwuxiao.so中的所有函数不会丢失

2、在调用原libwuxiao.so中的函数的时候能够执行我自己的代码。

b) 这里的方法更简单,原理与上面的相同,都是通过在伪造的libwuxiao.so中链接原libwuxiao_orig.so, 然后再定义同型函数,查找原函数地址,然后调用原函数的方法。这里的方法仅对GNU版本的系统有效。

#include <stdio.h>
#include <sys/types.h>
#include <dlfcn.h>

int func_c()
{
int (*func_c_ptr)();

// find func_c() in another shared object(with the same name)
dlerror();
func_c_ptr = dlsym(RTLD_DEFAULT,"func_c");
while ( func_c_ptr == func_c )
{
if ( func_c_ptr == NULL ) {
printf("Error(wrapper-2): cannot find func_c()\n");
break;
}

dlerror();
func_c_ptr = dlsym(RTLD_NEXT,"func_c");
}

if ( func_c_ptr != NULL )
{
printf("wuxiao is very powerful the second time\n");
return func_c_ptr();
}
return -1;
}

编译: gcc -shared -fPIC -o libwuxiao.so -ldl -lwuxiao_orig -L. -D_GNU_SOURCE

这种方法虽然更简单,但只对GNU有效,如果你的代码要放在非GNU的系统上执行,那还是用a)中的方法吧。


总结:

1、这里实现的wrapper的局限性就在于,你必须知道原来的共享库中的函数原型,但是你不必知道所有函数的原型。

你只需要知道其中你需要的函数原型即可。

2、如果你遇到一个共享库,里面的符号是强符号(不能再定义一个同名函数了),则这里的方法不再有用了。

0 0