1、可以借助/sys/kernel/debug/tracing目录下的文件,linux提供了kprobes功能,抓取内核函数中的入参和返回值。
kprobes,强大的调试工具_sydyh43的博客-CSDN博客
当某些场景不适合用命令行的方式,就需要考虑使用代码的方式实现相应的功能。借助内核kprobe的功能,编译一个内核驱动ko文件。给目标函数执行前后完成打桩,在打桩函数中获取函数的入参值和返回值,从而实现对某个功能的跟踪,如是否打开了某一个文件。
2、其中kprobe打桩功能实现如下。
当系统执行到目标函数时,触发int3异常,完成执行目标函数前pre_handler的调用;然后执行目标函数;当目标函数执行结束最后一刻,触发异常,完成执行目标函数后post_handler的调用。最后恢复正常上下文。
3、具体的代码实现,以open系统函数为例,对应内核态的函数是do_sys_open
#include
#include
#include
#include
#define FN_LEN 128
static int entry_handler(struct kretprobe_instance *ri, struct pt_regs *regs)
{
int len = 0;
char kname[FN_LEN] = {0x00};
char *filename = NULL;
memset(kname, 0x00, FN_LEN);
#ifdef CONFIG_X86
filename = (char *)(regs->si);
#endif
len = strncpy_from_user(kname, filename, FN_LEN);
if (unlikely(len < 0)) {
return -1;
}
if (strstr(kname, "abcx.log")) {
printk("pro_name=%s, kname=%s\n", current->comm, kname);
}
return 0;
}
static int ret_handler(struct kretprobe_instance *ri, struct pt_regs *regs)
{
int fd = regs_return_value(regs);
if (!strcmp(current->comm, "t_open")) {
printk("fd=%d\n", fd);
}
return 0;
}
static struct kretprobe krp = {
.entry_handler = entry_handler,
.handler = ret_handler,
.maxactive = 20,
.kp.symbol_name = "do_sys_open",
};
static int kretprobe_init(void)
{
int ret;
ret = register_kretprobe(&krp);
if (ret < 0) {
printk("register_kretprobe failed, returned %d\n", ret);
return -1;
}
printk("Planted return probe at %s: %p\n", krp.kp.symbol_name, krp.kp.addr);
return 0;
}
static void kretprobe_exit(void)
{
unregister_kretprobe(&krp);
printk("kretprobe at %p unregistered\n", krp.kp.addr);
return;
}
module_init(kretprobe_init);
module_exit(kretprobe_exit);
MODULE_LICENSE("GPL");
3.1、do_sys_open函数的定义如下
long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
3.2、regs->si就是函数的第二个入参,即filename。不同CPU架构,用于函数入参和返回值的寄存器不一样。此处是x86的架构。
3.3、通过do_sys_open函数定义可知,filename的指针地址是一个用户态的地址,此处是内核态空间,因此不能直接访问,需要借助函数strncpy_from_user实现数据的拷贝。
4、把上面的代码编译成驱动ko文件,insmod到内核,就可以实现用户态open函数调用的跟踪。当用户态打开abcx.log,就可以查看到底是哪个进程在对abcx.log文件进行操作。