前些天在琢磨微信协程库(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_DEFAULT和RTLD_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