前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[linux][kprobe]谁动了我的文件---使用kprobe找到目标进程

[linux][kprobe]谁动了我的文件---使用kprobe找到目标进程

作者头像
皮振伟
发布2018-04-09 10:10:53
2.2K0
发布2018-04-09 10:10:53
举报
文章被收录于专栏:皮振伟的专栏皮振伟的专栏

问题场景:

云计算IaaS平台上,经常使用libvirt+qemu-kvm做基础平台。libvirt会在/etc/libvirt/qemu/目录下,保存很多份qemu的配置文件,如ubuntu.xml。

作者发现其中的配置文件会在特定的场景下被修改,却不知道哪个进程是凶手。为了找到凶手,作者写下了这个debug工具。

代码分析:

代码路径:https://github.com/pacepi/whotouchmyfile

代码语言:javascript
复制
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <asm/uaccess.h>

#define FILE_NAME 64//被监控的文件名最大字符数,按需求,自己改

static struct kprobe kp = {
    .symbol_name    = "vfs_write",//使用kprobe,监控vfs_write,如果修改文件,几乎逃不开这个入口。作者想到的其他情况就是使用了mmap映射文件,然后msync回写。
};

static char file_name[FILE_NAME] = {0};
static struct ctl_table_header *cth = NULL;
static struct ctl_path path = {
    .procname = "kernel",//监控点放到了/proc/sys/kernel目录下
};
static struct ctl_table table[] = {
    {
        .procname   = "who_touch_my_file",//命名是作者的一时想法。用法就是echo "file_name" > /proc/sys/kernel/who_touch_my_file
        .data       = file_name,
        .maxlen     = FILE_NAME,
        .mode       = 0644,
        .proc_handler   = proc_dostring,
    },
    {
    }
};

static int handler_pre(struct kprobe *p, struct pt_regs *regs)
{//这里是重头戏,主要实现在这里。如果调用到了这里,说明就有进程调用了write,pwrite,writev,总之,陷入到了vfs_write这里。至于原因,留个悬念吧。后面开一篇来分析kprobe的实现
    struct file *file = (struct file *)regs->di;//因为x86的参数传递规则是di,si,dx,cx,r8,r9,所以di就是vfs_write的第一个参数。arm默认是r0,r1,r2,
r3,相应的取r0
    char *buf = NULL;
    size_t size = 0;

#if 0
    printk(KERN_INFO "pre_handler: p->addr = 0x%p, ip = %lx," " flags = 0x%lx\n", p->addr, regs->ip, regs->flags);
    if (file->f_path.dentry && file->f_path.dentry->d_name.name)
        printk(KERN_INFO "name = %s" , file->f_dentry->d_name.name);
#endif
    //简单的字符串比较。这里需要注意一下,作者实验过的3.10和4.0.4,这里的数据结构发生了变化。对于不同版本,需要自己修改适配一下。
    if (unlikely(strlen(file_name) && file && file->f_path.dentry && file->f_path.dentry->d_name.name && (strncmp(file->f_path.dentry->d_name.name, file_name, FILE_NAME) == 0)))
        printk(KERN_INFO "bingo : %s\n" , file->f_path.dentry->d_name.name);
    else
        return 0;

    size = regs->dx + 1;//vfs_write的第三个参数在dx中
    printk(KERN_INFO "process = %s , pid = %ld, file = %s, size = %ld\n" , current->comm, current->pid, file->f_path.dentry->d_name.name, size);//进程从用户态call下来,到这里都是同步的,所以current就是调用vfs_write的caller,拿到进程名称和pid就很容易了
    /*
     * should be careful ! if size is too big, maybe over flow.//文件不大的时候,可以尝试打印一下,不过注意,内核栈和thread结构体一共8k。不要overflow。
     buf = (char*)kmalloc(size, 0);
     if (buf == NULL)
     return 0;

     memset(buf, 0x00, size);
     if(copy_from_user(buf, regs->si, size))
     goto out;

     printk(KERN_INFO "%s\n" , buf);
     */

out :
    if (buf)
        kfree(buf);

    return 0;
}

static void handler_post(struct kprobe *p, struct pt_regs *regs,
        unsigned long flags)
{
    //printk(KERN_INFO "post_handler: p->addr = 0x%p, flags = 0x%lx\n", p->addr, regs->flags);
}

static int handler_fault(struct kprobe *p, struct pt_regs *regs, int trapnr)
{
    printk(KERN_INFO "fault_handler: p->addr = 0x%p, trap #%dn",
            p->addr, trapnr);
    return 0;
}

static int __init kprobe_init(void)
{//这里是八股文,init+exit
    int ret;
    kp.pre_handler = handler_pre;
    kp.post_handler = handler_post;
    kp.fault_handler = handler_fault;

    ret = register_kprobe(&kp);//其实这里才是入口,ko向kernel注册kprobe
    if (ret < 0) {
        printk(KERN_INFO "register_kprobe failed, returned %d\n", ret);
        goto out;
    }
    printk(KERN_INFO "register kprobe at %p\n", kp.addr);

    cth = register_sysctl_paths(&path, &table);///proc/sys/kernel/who_touch_my_file在这里注册的
    if (cth == NULL) {
        printk(KERN_INFO "register_sysctl_paths failed\n");
        ret = -EFAULT;
        goto error;
    }

    return 0;
error:
    unregister_kprobe(&kp);

out:
    return ret;
}

static void __exit kprobe_exit(void)
{
    if (cth)
        unregister_sysctl_table(cth);
    unregister_kprobe(&kp);
    printk(KERN_INFO "kprobe at %p unregistered\n", kp.addr);
}

module_init(kprobe_init);
module_exit(kprobe_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("PiZhenwei p_ace@126.com");

其他的话

这里是否可以用systemtap?答案是可以的。本质来说,systemtap也是用kprobe实现的(不过它需要debug symbol,也就是vmlinux,不过也可以捕获更精确的代码,原因在后面的kprobe实现一起分析)。

所谓内核热补丁,也可以用kprobe实现。在不重启内核的情况下,动态加载ko,修改内核行为。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-01-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 AlwaysGeek 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
腾讯云代码分析
腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档