Hook Library Function

前些天在琢磨微信协程库(libco),对库里Hook Library Function相关内容比较感兴趣,自己寻找相关的资料手动实践了一遍,确实很有意思。

核心参考文档 – 点击这里

理解Hook

一般来说我们调用系统API过程是这样:

Program -> Library Function -> System Call

举个例子,如果我们遇见这样的需求:把项目中调用malloc和free的地方打印日志,用作系统调优或者其他。

会遇见2个难题:1、从libc中malloc、free实现处做修改,这样需要重新编译整个libc和项目;2、日志应该是程序级的,若实现了1,那么在该机器上其实使用libc的项目不是也被“修改”了。

所幸Hook技术一直存在,形如:

Program -> Hook -> Library Function -> System Call

这样我们可以在不修改libc源码的情况下满足需求,并且做到程序级的修改。或者比如像Libco一样,hook住socket相关的系统调用添加自己超时相关逻辑。

How To Hook Library Function

首先我们写一个简单的例子:

//main.c
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>

int main()
{
    printf("enter main...\n");
    int *p = (int *)malloc(10);
    if (!p) {
        printf("allocation error...\n");
        exit(1);
    }
    printf("returning to main...\n");
    free(p);

//  my_hook_test();

    return 0;
}
[viclan@ ~]$gcc main.c -o test
[viclan@ ~]$./test
enter main...
returning to main...

我们现在对malloc和free添加自己的打印:

//my_hook.c
#define _GNU_SOURCE
#include <stdio.h>
#include <stdint.h>
#include <dlfcn.h>

#define unlikely(x) __builtin_expect(!!(x), 0)
#define TRY_LOAD_HOOK_FUNC(name) if (unlikely(!g_sys_##name)) {g_sys_##name = (sys_##name##_t)dlsym(RTLD_NEXT,#name);}


typedef void* (*sys_malloc_t)(size_t size);
static sys_malloc_t g_sys_malloc;
void* malloc(size_t size)
{
    printf("inside shared malloc object...\n");
    TRY_LOAD_HOOK_FUNC(malloc);
    void *p = g_sys_malloc(size);
    printf("malloc(%d) = %p\n", size, p);
    return p;
}


typedef void (*sys_free_t)(void *ptr);
static sys_free_t g_sys_free;
void free(void *ptr)
{
    printf("inside shared free object...\n");
    TRY_LOAD_HOOK_FUNC(free);
    printf("freeing memory = %p\n", ptr);
    g_sys_free(ptr);
}
[viclan@ ~]$gcc -shared -ldl -fPIC my_hook.c -o libmyhook.so
[viclan@ ~]$LD_PRELOAD=./libmyhook.so ./test
enter main...
inside shared malloc object...
malloc(10) = 0xd31010
returning to main...
inside shared free object...
freeing memory = 0xd31010

发现我们添加的打印输出,证明成功hook到了malloc与free。

原理

LD_PRELOAD

它允许你定义在程序运行前优先加载的动态链接库,上文中我们指定加载了编译好的libmyhook.so,它的设计初衷是为了有选择性的载入不同动态链接库中的相同函数。

dlsym(RTLD_NEXT,#name)

要从void *dlsym(void *handle, const char *symbol)这个接口说起:

dlsym

作用:根据动态链接库操作句柄(handle)与符号(symbol),返回符号对应的地址。

参数handle:由dlopen打开动态链接库后返回的句柄;

参数symbol:函数或者变量名;

注意:有两个伪句柄,RTLD_DEFAULTRTLD_NEXT,可以用作dlsym的参数。

RTLD_DEFAULT 表示当前进程会按照library search order搜索symbol,返回找到的第一个;

RTLD_NEXT 表示按照library search order搜索到symbol,返回找到的第二个(不同的library中)。

我们使用RTLD_NEXT,使用第二个symbol,因为第一个在LD_PRELOAD的库里。

 

深入一点,How To Hook SharedObject

比如我们有一个第三方动态库.so:

//my_func.c
#include <stdio.h>

void my_hook_test()
{
    printf("enter my main func!\n");
}

[/sourcecode ]


[viclan@ ~]$gcc -shared -ldl -fPIC my_func.c -o libmyfunc.so

我们将my_hook_test()这一行的注释打开,重新编译test链接我们自己的动态库libmyfunc.so并执行

[viclan@ ~]$gcc main.c -lmyfunc -I./ -L./  -o test
[viclan@ ~]$export LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH
[viclan@ ~]$./test
enter main...
returning to main...
enter my main func!

看输出,我们已经将自己的so链接成功,现在在my_hook.c中添加:

typedef void (*my_test_t)(void);
static my_test_t g_my_test;
void my_hook_test()
{
    printf("inside my_hook_test...\n");
    g_my_test = (my_test_t)dlsym(RTLD_NEXT, "my_hook_test");
    g_my_test();
}

重新编译libmyhook.so并执行

[viclan@ ~]$gcc -shared -ldl -fPIC my_hook.c -o libmyhook.so
[viclan@ ~]$LD_PRELOAD=./libmyhook.so ./test
enter main...
inside shared malloc object...
malloc(10) = 0x1c61010
returning to main...
inside shared free object...
freeing memory = 0x1c61010
inside my_hook_test...
enter my main func!

hook成功!

关于hook一些杂谈

我们希望将hook技术尽量用于正途,而不是类似hook auth api直接返回true这种行为,或者是hook time api改变系统时间行为。

简而言之需要防范LD_PRELOAD的使用限制,下面有几篇博文详细论述了这个问题:

http://blog.csdn.net/haoel/article/details/1602108

http://www.ibm.com/developerworks/cn/linux/l-sppriv/

(全文结束)


转载文章请注明出处:漫漫路 - lanindex.com

Leave a Comment

Your email address will not be published.