让我们hook一个库函数

来源:互联网 发布:阿里云空间购买流程 编辑:程序博客网 时间:2024/05/22 12:26

让我们hook一个库函数

原文地址:http://opensourceforu.com/2011/08/lets-hook-a-library-function/

说明:

    Hook中文翻译为钩子,可以用来截获调用函数,并改变函数的行为。WindowsLinux都提供了相应的实现机制。这篇文章是针对Linux平台的。也是在学习协程库libco过程中接触到的。

 

正文:

 如果你是一个开发者,并期望去改变一个库函数的行为,那么这篇文章将带你入门——只是用库函数做实验。所有的代码是用C写的,在Linux上面使用GCC编译测试。

    根据维基百科“在计算机程序中,hook包含一系列的技术,通过在软件中截获系统调用、消息或者事件,改变或者增强操作系统、应用程序或者其他软件模块的行为。那些处理这些拦截系统调用,事件或者消息的代码就称为hook。”

    截获一个系统调用,或者调用你自己的外壳代码,也称为函数介入。

    Hook有两个好处:

      •你不需要到像libc(glibc是GNU的C库,libc差不多是glibc的一半大小)一样的库里查找函数的定义,并改变它。严肃地说,这是非常下流的技术(至少对我而言!)

      •你不需要重新编译库的源码。

库函数和系统调用

  图1和图2使用图表的形式展现了一个库函数hook之后的样子。


1:库函数



2:使用hook之后的库函数


 现在让我们来hook一个库函数。下面的prog.c程序只是简单地分配了10字节的堆空间,然后释放:

#include<stdio.h>#include<malloc.h>#include<stdlib.h>int main(void){    int *p;    printf("calling from main...\n");    p=(int *)malloc(10);    if(!p)    {        printf("Got allocation error...\n");        exit(1);    }    printf("returning to main...\n");    free(p);                           /* freeing memory from heap */    printf("freeing memory...\n");    return 0;}

我们编译运行上面的程序,结果如下:

[root@workbenchsvr malloc_hook]# gcc -o prog1 prog1.c[root@workbenchsvr malloc_hook]# ./prog1calling from main...returning to main...freeing memory...[root@workbenchsvr malloc_hook]#

  下面一个程序,prog2.c,是一个简单的malloc()函数的hook:

#define _GNU_SOURCE#include <stdio.h>#include <stdint.h>#include <dlfcn.h>                               /* header required for dlsym() */ /* lcheck() is for memory leak check; its code is not shown here */void lcheck(void);void* malloc(size_t size){    static void* (*my_malloc)(size_t) = NULL;    printf("inside shared object...\n");    if (!my_malloc)    my_malloc = dlsym(RTLD_NEXT, "malloc");  /* returns the object reference for malloc */    void *p = my_malloc(size);               /* call malloc() using function pointer my_malloc */     printf("malloc(%d) = %p\n", size, p);    lcheck();                                /* calling do_your_stuff function */    printf("returning from shared object...\n");    return p;}void lcheck(void){    printf("displaying memory leaks...\n");    /* do required stuff here */}

编译运行,结果如下:

[root@workbenchsvr malloc_hook]# gcc -shared -ldl -fPIC prog2.c -o libprog2.so[root@workbenchsvr malloc_hook]# LD_PRELOAD=/home/dibyendu/malloc_hook/libprog2.so ./prog1calling from main...inside shared object...malloc(10) = 0x8191008displaying memory leaks...returning from shared object...returning to main...freeing memory...[root@workbenchsvr malloc_hook]#

  现在让我们近距离看看第一个hook程序。dlsym()函数接受两个参数:第一个是调用dlopen()返回的句柄。这里我们必须使用RTLD_NEXT。

  这告诉动态链接器查找指定函数的下一个版本,而不是调用dlsym()的那个[注:这里指的是查找真正的malloc函数]。第二个参数是一个字符串类型的标识名(本例中就是malloc)。dlsym()返回第二个参数执行标识的地址。编译的时候,使用fPIC产生一个位置无关的对象。

    LD_PRELOAD环境变量提供给加载器一系列需要加载的函数,在加载其他东西之前。我们只用它加载libprog2.so,并在prog1程序中动态链接。不要忘记在LD_PRELOAD中为.so提供绝对路径。当然,如果你想使用GNU C库中的某些扩展功能,需要包含_GNU_SOURCE[注:这里如果不包含,则找不到RTLD_NEXT的定义],因为这些扩展在其他非GNU的系统上可能不能用,加入这个#define有利于移植。

我们能使用dlsym()去hook所有函数吗?

   如果我们想为dlsym()本身加壳,或者为那些内部调用了dlsym()函数的库函数加壳,上面的hook方法将不可用。所以,有没有干预dlsym()的函数?有的,但你不能使用相同的hook过程———如果你想试试,请看一下输出结果。首先,使用file1.c和file2.c产生一个共享对象libfile.so,然后使用编译命令gcc -rdynamic -o dl_prog1 dl_prog1.c -ldl是的,结果显而易见:

/* file1.c */void file1(int *i){    *i=100;}/* file2.c  */void file2(int *i){    *i=200;}

  接下来的dl_prog1.c程序简单展示了dlopen()dlsym()的功能。file1()file2()函数定义在file1.cfile2.c文件中。

#include<stdio.h>#include<dlfcn.h>#include<stdlib.h> void file1(int *i);void file2(int *i);int main(void){    void *handler;    int (*fn) (int *);    int x;    char *error;    handler = dlopen("/home/dibyendu/dlsym_hook/libfile.so", RTLD_LAZY);    if (!handler)    {        fprintf(stderr,"%s\n", dlerror());        exit(1);    }    fn = dlsym(handler,"file1");     /* getting the handle of file1 through dlsym() */    if ((error = dlerror()) != NULL) /* checking error through dlerror() */    {        fprintf(stderr,"%s\n", error);        exit(1);    }    (*fn)(&x);                            /* Calling file1() to resolve x */    printf("The value of x is %d\n", x);    dlclose(handler);                 /* closing the file handle */    return 0;}

运行结果:

[root@workbenchsvr dlsym_hook]# gcc -shared -ldl -fPIC file1.c file2.c -o libfile.so[root@workbenchsvr dlsym_hook]# gcc -rdynamic -o dl_prog1 dl_prog1.c -ldl[root@workbenchsvr dlsym_hook]# ./dl_prog1The value of x is 100[root@workbenchsvr dlsym_hook]#

现在试图hook函数dlsmn(),你将因为循环调用而得到一个段错误(dlsym()将调用自己)。下面的dl_prog2.c将导致dlsym()循环调用自己,结果是内存泄露和段错误:


#define _GNU_SOURCE#include <stdio.h>#include <stdint.h>#include <dlfcn.h> void *dlsym(void *handle, const char *name){    void *(*dlsym_fn)(void *, const char *)=NULL;    printf("inside shared object::before dlsym()...\n");    dlsym_fn=dlsym(RTLD_NEXT, "dlsym");                     /* this will call itself again and again */    printf("inside shared object::after dlsym()...\n");    return (*dlsym_fn)(handle, name);}

输出:

[root@workbenchsvr dlsym_hook]# gcc -shared -ldl -fPIC dl_prog2.c -o libdl_prog2.so[root@workbenchsvr dlsym_hook]# LD_PRELOAD=/home/dibyendu/dlsym_hook/libdl_prog2.so ./dl_prog1inside shared object::before dlsym()...…...............................................................[注:这里一直在循环。。。]inside shared object::before dlsym()...Segmentation fault[root@workbenchsvr dlsym_hook]#

下面的dl_prog3.c程序成功地干预了dlsym()

#define __USE_GNU#include <stdio.h>#include <stdlib.h>#include <dlfcn.h> 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;}

输出:

[root@workbenchsvr dlsym_hook]# gcc -shared -ldl -fPIC dl_prog3.c -o libdl_prog3.so[root@workbenchsvr dlsym_hook]# LD_PRELOAD=/home/dibyendu/dlsym_hook/libdl_prog3.so ./dl_prog1Ha Ha...dlsym() HookedThe value of x is 100[root@workbenchsvr dlsym_hook]#

我们还能做什么?

 我已经hook了像getaddrinfo(),open()等函数,所以你能够干预任何函数。但这里有一些限制:

    •当心那些自己调用了dlsym()的函数,这时候你需要__libc_dlsym(handle,symbol)技术

    •确保SUID位没有设置,否则你不能使用LD_PRELOAD。

    •而且,内部库函数调用时再运行时之前确定的—也是就说,如果在libc中的一些函数调用了getaddrinfo()或者malloc(),它将不会在不同的库中调用hook。

 

参考

•Linux中的函数介入[http://jayconrod.com/posts/23/tutorial-function-interposition-in-linux]

•Linux的man pages:dlopen(), dlsym()dlerror()dlclose()






原创粉丝点击