您的位置:首页 > 运维架构 > Linux

获取kprobes嗅探的函数参数

2015-12-16 11:00 615 查看
获取kprobes嗅探的函数参数

疑问:

我已经用kprobes对一个函数进行嗅探,我需要在pre_handler处理函数里获取被嗅探函数的参数值。

被嗅探函数如下:

void foobar(int arg, int arg2, int arg3,int arg4, int arg5, int arg6, int arg7, int arg8)
{
    printk("foobarcalled\n");
}
设置嗅探:

...

kp.addr = (kprobe_opcode_t *) foobar;

register_kprobe(&kp);

 

foobar(0xdead1, 0xdead2, 0xdead3, 0xdead4, 0xdead5, 0xdead6, 0xdead7, 0xdead8);

pre_handler处理函数如下:

static int inst_generic_make_request(struct kprobe *p, struct pt_regs *regs)

{

  printk(KERN_INFO "eax: %08lx   ebx: %08lx   ecx: %08lx   edx: %08lx\n",

    regs->ax, regs->bx, regs->cx, regs->dx);

    printk(KERN_INFO "esi: %08lx   edi: %08lx   ebp: %08lx   esp: %08lx\n",

      regs->si, regs->di, regs->bp, regs->sp);

    regs++;

    //...

}

运行后,pre_handler处理函数输出如下:

22:58:07 kernel: [  402.640994] eax: 000dead1   ebx: f7d80086   ecx: 000dead3   edx: 000dead2

May 10 22:58:07 kernel: [  402.640996] esi: 00000000   edi: b77c8040   ebp: 00000000   esp: f7d8006c

 

May 10 22:58:07 kernel: [  402.641006] eax: f7d8032c   ebx: 000dead5   ecx: 000dead6   edx: 000dead7

May 10 22:58:07 kernel: [  402.641007] esi: 000dead8   edi: f7d800e0   ebp: f7d80330   esp: 08049674

 

May 10 22:58:07 kernel: [  402.641014] eax: 00000080   ebx: 0992b018   ecx: 0000108e   edx: 0992b008

May 10 22:58:07 kernel: [  402.641015] esi: 08049674   edi: b77c8040   ebp: bfe23fb8   esp: bfe23f50

现在我可以在各个寄存器看到foobar函数的参数(但0xdead4在哪里?),它们不是应该在栈里吗?我怎样才能在pre_handler处理函数里访问这些栈?又或者说我怎样才能获取任何不知道参数类型和个数的函数的参数?我知道这不是一件容易的事(甚至不可能获取全部参数),即使获取约值也行,我已经计算了两个函数间的参数,所以不需要太精确。如果我有参数在栈里的被调用函数的汇编代码,这是否有帮助?

 

回答

至少有两种方法。

1、方法一:Jprobes

估计是最简单的一种方法:如果你的任务可以用Jprobes,你可以试一下。Jprobes是基于kprobes实现的(详细描述和使用例子可以看:http://www.mjmwired.net/kernel/Documentation/kprobes.txt)。

Jprobes允许在被嗅探函数入口处调用与被嗅探函数原型一致的处理函数,你自然就可以通过这种方式获取它的所有参数了。

2、方法二:寄存器和栈

另一种方法就如你已经做得,稍微复杂一点。从你的输出日志看,我猜你是在32位X86系统上做的。

2.1、 32位X86

正如我们所见,在X86的linux内核(详见http://www.agner.org/optimize/calling_conventions.pdf)传递参数时有两个最常见的规律。需要注意的是系统调用可能遵循其他规律(详见man手册http://www.agner.org/optimize/calling_conventions.pdf),但我想你感兴趣的是分析“普通”函数而非系统调用。

规律1:

对于
asmlinkage
 标志和有变量参数列表的函数,全部参数压入栈内。函数的返回地址在入口函数的栈顶,第一个参数位于它的后面,第二个参数跟在第一个的后面,以此类推。

比如,在这种规律下如果你保存了esp的值,你会发现:*(esp+4)就是第一个参数,*(esp+8)就是第二个参数,以此类推。

规律2:

包括你所列举的例子在内的大多数函数都遵循这种规律。

内核编译时是带参数-mregparm=3的,所以前3个参数保存在eax、ebx和ecx,按照这种顺序,其他参数压入栈内,*(esp+4) 就是第4个参数,*(esp+8) 就是第5个参数。

2.2、64位X86

在X86-64上会简单一些。大部分内核函数(包括有变量参数列表的函数),可以通过寄存器
rdi
,、
rsi
,、
rdx
,、
rcx
,、r8,、
r9
获取前6个参数,按照这种顺序,其他参数压入栈内,
*(esp+8)

就是第7个参数, 
*(esp+16)
 就是第8个参数。

2.3、注意

值得注意的是:在X86-32系统上,并没有为内核模式陷阱(包括kprobes依赖的断点)把esp的值保存到pt_regs上。 <asm/ptrace.h> 提供kernel_stack_pointer()函数来获取当前esp的值,它在X86-32和X86-64上均有效。详见其头文件。

另外,regs_get_kernel_stack_nth()(也是定义在 <asm/ptrace.h>)能方便地获取处理器堆栈的内容。

 

英语原文:http://stackoverflow.com/questions/10563635/getting-function-arguments-using-kprobes

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息