转载_基于探针的动态插桩

由于基于探针的动态插桩,通常只能在函数边界插入代码,难以对程序的指令流进行很好的分析,所以平时用的比较少。以前使用微软研究院的detour的API觉得它很神奇,最近看了下它的原理还是很简单:基于简单动态重写函数的开始几个字节,然后跳转到特定函数。呵呵,但是要做好还是不容易的。闲来无事写了一个很粗糙的实现。

 

基本原理就是:(1)保存函数的入口的几个字节,并插入一天跳回函数的jmp指令(这一块代码称为trampaline)。这里的前几个字节不是个定数是有原因的,实际上我们只需要前5字节来保存一条JMP指令,但入口的5个字节可能并不是几条完整的指令,因此若只保存5个字节就会截断指令。如下面的代码所示,test函数入口的第5个字节包含于sub,保存前6个字节就可以避免截断sub指令。

[cpp]  view plain copy
  1. 080483e4 <test>:  
  2.  80483e4:   55                      push   %ebp  
  3.  80483e5:   89 e5                   mov    %esp,%ebp  
  4.  80483e7:   83 ec 18                sub    $0x18,%esp  
  5.  80483ea:   c7 04 24 e0 84 04 08    movl   $0x80484e0,(%esp)  

(2)修改函数入口的5个字节为jmp xxx指令,其中的xxx就是探针函数到当前函数的偏移量。跳往探针函数并执行它。

(3)执行trampaline代码,执行原函数。

(3)恢复原函数的入口。

 

基本数据结构:

[c-sharp]  view plain copy
  1. /*参数类型,包含的字节数,例如INT8表示数据大小为一个字节*/  
  2. typedef enum arg_type {  
  3.         NULL_TYPE,  
  4.         INT8,  
  5.         INT16,  
  6.         INT32,  
  7.         INT64  
  8. } arg_type_t;  
  9.   
  10. /*探针函数描述符*/  
  11. typedef struct probe {  
  12.         int     id; /*探针id*/  
  13.         int     ref/*引用计数*/  
  14.         void    *probe_fuc; /*函数地址*/  
  15.         arg_type_t    arg[MAX_ARG]; /*参数类型*/  
  16.         char    name[MAX_FUC_NAME_LEN]; /*函数名*/  
  17.         struct probe *next; /*下一探针*/  
  18. }probe_t;  
  19.   
  20. /*trampline描述符*/  
  21. typedef struct trampline {  
  22.         char code[MAX_CODE_CACHE]; /*保存函数入口代码*/  
  23.         struct trampline *next;  
  24. }trampline_t;  
  25.   
  26. /*函数描述符*/  
  27. typedef struct fuc_info {  
  28.         int     id; /*id*/  
  29.         char    fuc_name[MAX_FUC_NAME_LEN]; /*函数名*/  
  30.         void    *fuc_addr; /*函数地址*/  
  31.         arg_type_t     arg[MAX_ARG]; /*参数类型*/  
  32.         trampline_t *tp;  
  33.         probe_t *probe_list;  
  34. }fuc_info_t;  

 

初始化

利用Linux LD_PRELOAD的特性,初始化整个库。其实在linux下利用LD_PRELAOD可以直接拦截库函数的执行,这里使用这个简化实现。

[c-sharp]  view plain copy
  1. void __attribute__((constructor)) lib_init() {  
  2.     probe_t *probe;  
  3.     fuc_info_t *fuc;  
  4.   
  5.     printf("init lib/n");  
  6.   
  7.     //分配trampaline,将其权限设为可执行等  
  8.     tp_table = mmap(NULL, MAX_TABLE_SIZE * sizeof(trampline_t), PROT_EXEC | PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);  
  9.       
  10.     if (tp_table == -1) {  
  11.             printf("lib_init fail to map/n");  
  12.             exit(1);  
  13.     }  
  14.   
  15.          //可以提供一个接口,让用户来指导探针和原函数信息  
  16.            //这里简化这一步  
  17.     probe = &probe_table[0];  
  18.     probe->probe_fuc = (void *)hello;  
  19.     strcpy(probe->name,"hello");  
  20.     probe->next = NULL;  
  21.   
  22.     fuc = &info_table[0];  
  23.     strcpy(fuc->fuc_name,"test");  
  24.     fuc->fuc_addr = 0x080483e4;  
  25.     fuc->probe_list = NULL;  
  26.     fuc->tp = &tp_table[0];  
  27.       
  28.          //插入探针  
  29.     insert_probe(fuc, probe);  
  30. }  

 

插入探针

偏移量计算一般是目标地址 - jmp的下一条指令的地址。在计算跳回员函数偏移量时,多加6个字节。因为6个字节的代码已经被执行了,同时这样做也避免了在探针和原函数间跳来跳去的死循环。这里作为了简单保存的6字节,因为测试的是本文开始的test函数。

 

 

[c-sharp]  view plain copy
  1. static void insert_probe(fuc_info_t *fuc, probe_t *probe) {  
  2.     void *p;  
  3.     char *code;  
  4.     trampline_t *tp;  
  5.     fuc_info_t *info;  
  6.   
  7.     info = fuc;  
  8.     tp = info->tp;  
  9.   
  10.     p = PAGE_ALIGN(info->fuc_addr);  
  11.   
  12.     //修改代码段权限为可写  
  13.     if (mprotect(p, PAGESIZE, PROT_EXEC | PROT_READ | PROT_WRITE) == -1)  
  14.             printf("error change prot/n");  
  15.       
  16.     //复制入口代码  
  17.     memcpy(tp->code, info->fuc_addr, 6);  
  18.     //jmp info->fuc_addr + 6,跳往保存代码的下一条指令  
  19.     tp->code[6] = 0xE9; //jmp的机器码  
  20.     //偏移量,(info->fuc_addr + 6) - (&tp->code[6] + 5)(jmp的下一条指令)  
  21.     *((int *)(tp->code + 7)) = (char *)info->fuc_addr - tp->code - 5;  
  22.   
  23.     code = (char*)info->fuc_addr;  
  24.     //jmp dispatch,跳到我们的分派函数  
  25.     code[0] = 0xE9;  
  26.     *((int *)(code + 1)) = (int)dispatch - (int)code - 5;  
  27.   
  28.     //重置权限  
  29.     if (mprotect(p, PAGESIZE, PROT_EXEC | PROT_READ) == -1)  
  30.         printf("error rechange prot/n");  
  31.       
  32.     probe->next = info->probe_list;  
  33.     info->probe_list = probe;  
  34. }  

 

删除探针

为了简单,只回复函数入口代码

[c-sharp]  view plain copy
  1. static void remove_probe(fuc_info_t *fuc) {  
  2.     trampline_t *tp;  
  3.     fuc_info_t *info;  
  4.     void *p;  
  5.   
  6.     info = fuc;  
  7.     tp = info->tp;  
  8.       
  9.     p = PAGE_ALIGN(info->fuc_addr);  
  10.   
  11.     if (mprotect(p, PAGESIZE, PROT_EXEC | PROT_READ | PROT_WRITE) == -1)  
  12.             printf("error change prot/n");  
  13.       
  14.     //恢复函数入口  
  15.     memcpy(info->fuc_addr, tp->code, 6);  
  16.   
  17.     if (mprotect(p, PAGESIZE, PROT_EXEC | PROT_READ) == -1)  
  18.         printf("error rechange prot/n");  
  19.   
  20. }  

dispatch函数

用来管理整个跳转和探针函数的执行。原函数有参数的话,需要进一步处理,这里简化了这一步。

[c-sharp]  view plain copy
  1. static void dispatch() {  
  2.     probe_t *probe;  
  3.     trampline_t *tp;  
  4.     //根据地址进行查找,这里简化了这一步  
  5.     fuc_info_t *info = &info_table[0];  
  6.       
  7.     tp = info->tp;   
  8.     probe = info->probe_list;  
  9.   
  10.     //处理参数,未实现  
  11.       
  12.     //执行探针列表的探针函数  
  13.     while (probe) {  
  14.         ((enter)probe->probe_fuc)();  
  15.         probe = probe->next;  
  16.     }  
  17.   
  18.     //执行原函数  
  19.     ((enter)tp->code)();  
  20.   
  21.     //作为测试删除函数的插桩信息  
  22.     remove_probe(info);  
  23. }  

 

探针的代码

[c-sharp]  view plain copy
  1. static void hello() {  
  2.     printf("Hello world, I am a probe fuc!/n");  
  3. }  
  

 

fini函数:释放相应资源

[c-sharp]  view plain copy
  1. void __attribute__ ((destructor)) lib_fini() {  
  2.     int retval;  
  3.       
  4.     printf("fini lib/n");  
  5.     retval = -1;  
  6.   
  7.     if (tp_table) {  
  8.         retval = munmap(tp_table, MAX_TABLE_SIZE * sizeof(trampline_t));  
  9.         if (retval == -1) {  
  10.                 printf("lib_fini fail to free map/n");  
  11.         }  
  12.     }  
  13. }  

 

测试程序:test.c

[c-sharp]  view plain copy
  1. #include <stdio.h>  
  2.   
  3. void test() {  
  4.     printf("test /n");  
  5. }  
  6.   
  7. int main()  
  8. {  
  9.     int a,b;  
  10.       
  11.     test();  
  12.     printf("after remove probe/n");  
  13.     test();  
  14.     return 0;  
  15. }  

 

执行结果:

LD_PRELOAD=./libprobe.so ./test。可以看到hello函数在test之前执行了,删除探针后函数恢复正常执行。

[c-sharp]  view plain copy
  1. init lib  
  2. Hello world, I am a probe fuc!  
  3. test  
  4. after remove probe  
  5. test  
  6. fini lib  

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值