UNIX 编程技巧——如何 hook 一个共享库

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

假如你有一个二进制版的系统,例如现在流行的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、如果你遇到一个共享库,里面的符号是强符号(不能再定义一个同名函数了),则这里的方法不再有用了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值