首页
社区
课程
招聘
[原创]Linux Kprobe原理探究
发表于: 2024-4-27 14:44 24923

[原创]Linux Kprobe原理探究

2024-4-27 14:44
24923

之前在分析其他安全厂商App的防护策略时,想要设计个风控分析沙盒来实现对于App行为的全面监控,包括

其中很棘手的问题在于如何应对App中越来越常见的内联系统调用,对于内联系统调用的监控我不希望通过ptrace这类进程注入的方式来实现,而是想寻求通过定制系统或者相关的方式来实现以达到无侵入App的目的

另一方面来说,通过定制系统的方式完成相关系统函数的修改确实是一种方式,但是定制系统在生产环境使用中会存在两个问题:

综上,最最贴合真实场景的是一种无侵入App且不阻断内核启动的方案,经过一顿搜索,最终定位到了Linux Kprobe这类内核监控方案

第一次了解到kprobe技术是在evilpan的文章Linux 内核监控在 Android 攻防中的应用 中,在现有的内核监控方案中分为数据、采集、前端三个层级

而作为最底层的数据来源,kprobe、uprobe等是我们在做内核监控时需要重点关注的点,相比较于其他几种实现方式,kprobe无论从可扩展性、影响范围上都是最适合做二次开发的,参考作者给出的对比表

因此最终确定了使用Linux Kprobe来作为内核系统函数的监控方案

kprobe可以认为是一种kernel hook手段,它基于内核中断的方式实现,可以想象它是内核层的异常hook(参考SandHook),既然是异常hook,那么它所能hook的范围就没有限制了,可以针对函数、也可以针对单条指令

简单理解就是把指定地址的指令替换成一个可以让cpu进入debug模式的指令(不同架构上指令不同),跳转到probe处理函数上进行数据收集、修改,再跳转回来继续执行

X86中使用的是int3指令,ARM64中使用的是BRK指令进入debug monitor模式

参考HPYU的Kprobe执行流程示意图

kprobe主要有两种使用方法,一是通过模块加载;二是通过debugfs接口。从可扩展性和工程化的角度来看,模块加载是更优的选择,debugfs在某些特殊场景下(快速验证某些函数)可能会适合

首先了解下动态内核模块(Loadable kernel module),LKM可以看出是内核向外提供的一个接口,通常是我们基于已编译好的内核产物+自定义的模块代码编译得到的ko文件,通过insmod的方式来实现动态新增定制功能,这种做法的好处是无需修改内核,需要新增功能时只需要变动相关LKM即可,它的作用域和静态编译的内核其他模块是完全等价的,而缺点是会带来些许性能上的损失,不过相比易用性来说这点可以忽略不计

参考Linux源码下的samples/kprobes,里面包含kprobe、kretprobe等案例

整个案例可以拆分成几个部分来看

这样就完成了对于do_for函数的hook,整体使用流程很清晰简单,初始化kprobes结构体(设置symbol_name、handlder)->注册kprobes->LKM封装

到目前为止,对于kprobe的使用是比较清晰了,下面从其原理角度来探究它是如何实现这套hook机制的

首先我们从kprobe的起始点init_kprobe函数切入,由于各个架构的实现不同,下面以arm64为例

init_kprobes的第一步是初始化哈希表,这里的哈希表指代的就是管理kprobe实例

KPROBE_TABLE_SIZE是64,对于每个槽初始化一个头结点
kprobe table的形式参考下图

以hook的address为key,将kprobe保存到哈希表中,后续在查找时可以通过address来快速定位到kprobe_table槽,再通过对比hlist_node来确定kprobe

注册了kprobe_exceptions_notify函数作为回调函数,暂时不知道什么时候会出发die链的回调函数,先接着往下看

init_kprobes主要做了5件事

计算待hook点地址,这里分为了两种情况

kallsyms_lookup_name是内核的导出函数,可以通过kallsyms_lookup_name定位符号的真实地址

判断kprobe是否已注册过,注册过会在kprobe_table中查找到

这个过程主要对跟踪指令的内存地址进行合法检测,主要检查几个点:

申请新空间来保存当前地址原有指令,以便后续跳转回来时使用

根据地址计算key,插入kprobe的hlist字段,也就是hlist_node

调用链如下arm_kprobe->__arm_kprobe->arch_arm_kprobe,最终arch_arm_kprobe由各个架构决定,如arm64

将该地址的值替换成brk指令

register_kprobe主要做了件事

到这里为止,已经完成了对应地址的指令替换和原始指令的保存,下面看看是怎么触发自定义handler的处理

kprobe的触发和处理是通过brk exception和single step单步exception执行的,每次的处理函数中会修改被异常中断的上下文(struct pt_regs)的指令寄存器,实现执行流的跳转。ARM64对于异常处理的注册在arch/arm64/kernel/debug-monitors.c, 是arm64的通用debug模块

通过hook_debug_fault_code动态定义了异常处理的钩子函数brk_handler,它将在断点异常处理函数中被调用。hook_debug_fault_code替换了debug_fault_info的值,将原有的异常处理函数变成自定义的异常处理函数

arm64的异常处理都在arch/arm64/kernel/entry.S中

会调用到do_debug_exception函数,之所以是在el1这里处理,是因为BRK异常的产生是因为在内核态执行了BRR指令,内核态是执行在EL1的,所以异常等级是EL1

这里根据传入的esr解析得到数组索引,对于BRK,解析出来的索引为6,从而调用到debug_traps_init里注册的BRK exception处理函数brk_handler;对于HWSS exception,解析出来的索引是1,则调用debug_traps_init里注册的BRK exception处理函数single_step_handler

对于brk断点来说,最终会调用brk_handler

在brk异常处理中,首先是调用了自定义的pre_handler完成函数指令调用前的操作,接着调用了setup_singlestep函数,setup_singlestep主要是设置寄存器状态变成单步调试状态并设置pc指令为之前缓存的opcode(缓存的指令就是原始指令),由于之前设置了单步调试,在执行opcode之后会触发HWSS exception从而进入kprobe_single_step_handler

以上就是从源码角度来分析kprobe的实现流程,总结下来就是

可以结合和上文Kprobe执行流程示意图来梳理思路

监控方案 静态 动态 内核 用户
Kprobes
Uprobes
Tracepoints
USDT
// samples/kprobes/kprobe_example.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>
 
/* For each probe you need to allocate a kprobe structure */
static struct kprobe kp = {
    .symbol_name    = "_do_fork",
};
 
/* kprobe pre_handler: called just before the probed instruction is executed */
static int handler_pre(struct kprobe *p, struct pt_regs *regs)
{
#ifdef CONFIG_X86
    printk(KERN_INFO "pre_handler: p->addr = 0x%p, ip = %lx,"
            " flags = 0x%lx\n",
        p->addr, regs->ip, regs->flags);
#endif
#ifdef CONFIG_PPC
    printk(KERN_INFO "pre_handler: p->addr = 0x%p, nip = 0x%lx,"
            " msr = 0x%lx\n",
        p->addr, regs->nip, regs->msr);
#endif
#ifdef CONFIG_MIPS
    printk(KERN_INFO "pre_handler: p->addr = 0x%p, epc = 0x%lx,"
            " status = 0x%lx\n",
        p->addr, regs->cp0_epc, regs->cp0_status);
#endif
#ifdef CONFIG_TILEGX
    printk(KERN_INFO "pre_handler: p->addr = 0x%p, pc = 0x%lx,"
            " ex1 = 0x%lx\n",
        p->addr, regs->pc, regs->ex1);
#endif
 
    /* A dump_stack() here will give a stack backtrace */
    return 0;
}
 
/* kprobe post_handler: called after the probed instruction is executed */
static void handler_post(struct kprobe *p, struct pt_regs *regs,
                unsigned long flags)
{
#ifdef CONFIG_X86
    printk(KERN_INFO "post_handler: p->addr = 0x%p, flags = 0x%lx\n",
        p->addr, regs->flags);
#endif
#ifdef CONFIG_PPC
    printk(KERN_INFO "post_handler: p->addr = 0x%p, msr = 0x%lx\n",
        p->addr, regs->msr);
#endif
#ifdef CONFIG_MIPS
    printk(KERN_INFO "post_handler: p->addr = 0x%p, status = 0x%lx\n",
        p->addr, regs->cp0_status);
#endif
#ifdef CONFIG_TILEGX
    printk(KERN_INFO "post_handler: p->addr = 0x%p, ex1 = 0x%lx\n",
        p->addr, regs->ex1);
#endif
}
 
/*
 * fault_handler: this is called if an exception is generated for any
 * instruction within the pre- or post-handler, or when Kprobes
 * single-steps the probed instruction.
 */
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 because we don't handle the fault. */
    return 0;
}
 
static int __init kprobe_init(void)
{
    int ret;
    kp.pre_handler = handler_pre;
    kp.post_handler = handler_post;
    kp.fault_handler = handler_fault;
 
    ret = register_kprobe(&kp);
    if (ret < 0) {
        printk(KERN_INFO "register_kprobe failed, returned %d\n", ret);
        return ret;
    }
    printk(KERN_INFO "Planted kprobe at %p\n", kp.addr);
    return 0;
}
 
static void __exit kprobe_exit(void)
{
    unregister_kprobe(&kp);
    printk(KERN_INFO "kprobe at %p unregistered\n", kp.addr);
}
 
module_init(kprobe_init)
module_exit(kprobe_exit)
MODULE_LICENSE("GPL");
 
// include/linux/kprobes.h
struct kprobe {
    // 所有注册过的kprobe都会加入到kprobe_table哈希表中,hlist指向哈希表的位置
    struct hlist_node hlist;
 
    /* list of kprobes for multi-handler support */
    struct list_head list;
 
    /*count the number of times this probe was temporarily disarmed */
    unsigned long nmissed;
 
    /* location of the probe point */
    kprobe_opcode_t *addr;
 
    /* Allow user to indicate symbol name of the probe point */
    // 地址和name不能同时出现,之前提过kprobe可以hook函数和地址
    const char *symbol_name;
 
    /* Offset into the symbol */
    unsigned int offset;
 
    /* Called before addr is executed. */
    // 在单步执行原始指令前被调用
    kprobe_pre_handler_t pre_handler;
 
    /* Called after addr is executed, unless... */
    // 在单步执行原始指令后被调用
    kprobe_post_handler_t post_handler;
 
    /* Saved opcode (which has been replaced with breakpoint) */
    kprobe_opcode_t opcode;
 
    // 保存平台相关的被探测指令和下一条指令
    /* copy of the original instruction */
    struct arch_specific_insn ainsn;
 
    /*
    * Indicates various status flags.
    * Protected by kprobe_mutex after this kprobe is registered.
    */
    u32 flags;
};
// samples/kprobes/kprobe_example.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>
 
/* For each probe you need to allocate a kprobe structure */
static struct kprobe kp = {
    .symbol_name    = "_do_fork",
};
 
/* kprobe pre_handler: called just before the probed instruction is executed */
static int handler_pre(struct kprobe *p, struct pt_regs *regs)
{
#ifdef CONFIG_X86
    printk(KERN_INFO "pre_handler: p->addr = 0x%p, ip = %lx,"
            " flags = 0x%lx\n",
        p->addr, regs->ip, regs->flags);
#endif
#ifdef CONFIG_PPC
    printk(KERN_INFO "pre_handler: p->addr = 0x%p, nip = 0x%lx,"
            " msr = 0x%lx\n",
        p->addr, regs->nip, regs->msr);
#endif
#ifdef CONFIG_MIPS
    printk(KERN_INFO "pre_handler: p->addr = 0x%p, epc = 0x%lx,"
            " status = 0x%lx\n",
        p->addr, regs->cp0_epc, regs->cp0_status);
#endif
#ifdef CONFIG_TILEGX
    printk(KERN_INFO "pre_handler: p->addr = 0x%p, pc = 0x%lx,"
            " ex1 = 0x%lx\n",
        p->addr, regs->pc, regs->ex1);
#endif
 
    /* A dump_stack() here will give a stack backtrace */
    return 0;
}
 
/* kprobe post_handler: called after the probed instruction is executed */
static void handler_post(struct kprobe *p, struct pt_regs *regs,
                unsigned long flags)
{
#ifdef CONFIG_X86
    printk(KERN_INFO "post_handler: p->addr = 0x%p, flags = 0x%lx\n",
        p->addr, regs->flags);
#endif
#ifdef CONFIG_PPC
    printk(KERN_INFO "post_handler: p->addr = 0x%p, msr = 0x%lx\n",
        p->addr, regs->msr);
#endif
#ifdef CONFIG_MIPS
    printk(KERN_INFO "post_handler: p->addr = 0x%p, status = 0x%lx\n",
        p->addr, regs->cp0_status);
#endif
#ifdef CONFIG_TILEGX
    printk(KERN_INFO "post_handler: p->addr = 0x%p, ex1 = 0x%lx\n",
        p->addr, regs->ex1);
#endif
}
 
/*
 * fault_handler: this is called if an exception is generated for any
 * instruction within the pre- or post-handler, or when Kprobes
 * single-steps the probed instruction.
 */
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 because we don't handle the fault. */
    return 0;
}
 
static int __init kprobe_init(void)
{
    int ret;
    kp.pre_handler = handler_pre;
    kp.post_handler = handler_post;
    kp.fault_handler = handler_fault;
 
    ret = register_kprobe(&kp);
    if (ret < 0) {
        printk(KERN_INFO "register_kprobe failed, returned %d\n", ret);
        return ret;
    }
    printk(KERN_INFO "Planted kprobe at %p\n", kp.addr);
    return 0;
}
 
static void __exit kprobe_exit(void)
{
    unregister_kprobe(&kp);
    printk(KERN_INFO "kprobe at %p unregistered\n", kp.addr);
}
 
module_init(kprobe_init)
module_exit(kprobe_exit)
MODULE_LICENSE("GPL");
 
// include/linux/kprobes.h
struct kprobe {
    // 所有注册过的kprobe都会加入到kprobe_table哈希表中,hlist指向哈希表的位置
    struct hlist_node hlist;
 
    /* list of kprobes for multi-handler support */
    struct list_head list;
 
    /*count the number of times this probe was temporarily disarmed */
    unsigned long nmissed;
 
    /* location of the probe point */
    kprobe_opcode_t *addr;
 
    /* Allow user to indicate symbol name of the probe point */
    // 地址和name不能同时出现,之前提过kprobe可以hook函数和地址
    const char *symbol_name;
 
    /* Offset into the symbol */
    unsigned int offset;
 
    /* Called before addr is executed. */
    // 在单步执行原始指令前被调用
    kprobe_pre_handler_t pre_handler;
 
    /* Called after addr is executed, unless... */
    // 在单步执行原始指令后被调用
    kprobe_post_handler_t post_handler;
 
    /* Saved opcode (which has been replaced with breakpoint) */
    kprobe_opcode_t opcode;
 
    // 保存平台相关的被探测指令和下一条指令
    /* copy of the original instruction */
    struct arch_specific_insn ainsn;
 
    /*
    * Indicates various status flags.
    * Protected by kprobe_mutex after this kprobe is registered.
    */
    u32 flags;
};
static int __init init_kprobes(void)
{
    int i, err = 0;
 
    /* FIXME allocate the probe table, currently defined statically */
    /* initialize all list heads */
    //1. 初始化哈希表节点, 保存已注册的kprobe实例
    for (i = 0; i < KPROBE_TABLE_SIZE; i++) {
        INIT_HLIST_HEAD(&kprobe_table[i]);
        ......
    }
 
    ......
    //2. 初始化kprobe黑名单(非__krpobe属性又不能被kprobe的函数)
    if (kretprobe_blacklist_size) {
        /* lookup the function address from its name */
        for (i = 0; kretprobe_blacklist[i].name != NULL; i++) {
            kretprobe_blacklist[i].addr =
                kprobe_lookup_name(kretprobe_blacklist[i].name, 0);
            .....
        }
    }
 
    ......
    // 3. 架构相关的初始化,调用两个函数arm_kprobe_decode_init与register_undef_hook
    err = arch_init_kprobes();
    if (!err)
        // 4. 注册die通知链
        err = register_die_notifier(&kprobe_exceptions_nb);
    if (!err)
        // 5. 注册模块通知链
        err = register_module_notifier(&kprobe_module_nb);
 
    kprobes_initialized = (err == 0);
 
    if (!err)
        init_test_probes();
    return err;
}
 
// arch/arm/probes/kprobes/core.c
int __init arch_init_kprobes()
{
    return 0;
}
static int __init init_kprobes(void)
{
    int i, err = 0;
 
    /* FIXME allocate the probe table, currently defined statically */
    /* initialize all list heads */
    //1. 初始化哈希表节点, 保存已注册的kprobe实例
    for (i = 0; i < KPROBE_TABLE_SIZE; i++) {
        INIT_HLIST_HEAD(&kprobe_table[i]);
        ......
    }
 
    ......
    //2. 初始化kprobe黑名单(非__krpobe属性又不能被kprobe的函数)
    if (kretprobe_blacklist_size) {
        /* lookup the function address from its name */
        for (i = 0; kretprobe_blacklist[i].name != NULL; i++) {
            kretprobe_blacklist[i].addr =
                kprobe_lookup_name(kretprobe_blacklist[i].name, 0);
            .....
        }
    }
 
    ......
    // 3. 架构相关的初始化,调用两个函数arm_kprobe_decode_init与register_undef_hook
    err = arch_init_kprobes();
    if (!err)
        // 4. 注册die通知链
        err = register_die_notifier(&kprobe_exceptions_nb);
    if (!err)
        // 5. 注册模块通知链
        err = register_module_notifier(&kprobe_module_nb);
 
    kprobes_initialized = (err == 0);
 
    if (!err)
        init_test_probes();
    return err;
}
 
// arch/arm/probes/kprobes/core.c
int __init arch_init_kprobes()
{
    return 0;
}
// kernel/kprobes.c
static struct hlist_head kprobe_table[KPROBE_TABLE_SIZE];
 
for (i = 0; i < KPROBE_TABLE_SIZE; i++) {
    INIT_HLIST_HEAD(&kprobe_table[i]);
    ......
}
 
struct kprobe *get_kprobe(void *addr)
{
    struct hlist_head *head;
    struct kprobe *p;
    // 定位槽所对应的头结点
    head = &kprobe_table[hash_ptr(addr, KPROBE_HASH_BITS)];
    // 遍历链表,hlist指的是kprobe的hlist_node
    hlist_for_each_entry_rcu(p, head, hlist) {
        if (p->addr == addr)
            return p;
    }
 
    return NULL;
}
// kernel/kprobes.c
static struct hlist_head kprobe_table[KPROBE_TABLE_SIZE];
 
for (i = 0; i < KPROBE_TABLE_SIZE; i++) {
    INIT_HLIST_HEAD(&kprobe_table[i]);
    ......
}
 
struct kprobe *get_kprobe(void *addr)
{
    struct hlist_head *head;
    struct kprobe *p;
    // 定位槽所对应的头结点
    head = &kprobe_table[hash_ptr(addr, KPROBE_HASH_BITS)];
    // 遍历链表,hlist指的是kprobe的hlist_node
    hlist_for_each_entry_rcu(p, head, hlist) {
        if (p->addr == addr)
            return p;
    }
 
    return NULL;
}
static struct notifier_block kprobe_exceptions_nb = {
    .notifier_call = kprobe_exceptions_notify,
    .priority = 0x7fffffff /* we need to be notified first */
};
 
int __kprobes kprobe_exceptions_notify(struct notifier_block *self,
                       unsigned long val, void *data)
{
    struct die_args *args = data;
    unsigned long addr = args->err;
    int ret = NOTIFY_DONE;
 
    switch (val) {
    case DIE_IERR:
        if (arc_kprobe_handler(addr, args->regs))
            return NOTIFY_STOP;
        break;
 
    case DIE_TRAP:
        if (arc_post_kprobe_handler(addr, args->regs))
            return NOTIFY_STOP;
        break;
 
    default:
        break;
    }
 
    return ret;
}
static struct notifier_block kprobe_exceptions_nb = {
    .notifier_call = kprobe_exceptions_notify,
    .priority = 0x7fffffff /* we need to be notified first */
};
 
int __kprobes kprobe_exceptions_notify(struct notifier_block *self,
                       unsigned long val, void *data)
{
    struct die_args *args = data;
    unsigned long addr = args->err;
    int ret = NOTIFY_DONE;
 
    switch (val) {
    case DIE_IERR:
        if (arc_kprobe_handler(addr, args->regs))
            return NOTIFY_STOP;
        break;
 
    case DIE_TRAP:
        if (arc_post_kprobe_handler(addr, args->regs))
            return NOTIFY_STOP;
        break;
 
    default:
        break;
    }
 
    return ret;
}
static struct notifier_block kprobe_module_nb = {
    .notifier_call = kprobes_module_callback,
    .priority = 0
};
 
/* Module notifier call back, checking kprobes on the module */
static int kprobes_module_callback(struct notifier_block *nb,
                   unsigned long val, void *data)
{
    struct module *mod = data;
    struct hlist_head *head;
    struct kprobe *p;
    unsigned int i;
    int checkcore = (val == MODULE_STATE_GOING);
 
    if (val != MODULE_STATE_GOING && val != MODULE_STATE_LIVE)
        return NOTIFY_DONE;
 
    /*
     * When MODULE_STATE_GOING was notified, both of module .text and
     * .init.text sections would be freed. When MODULE_STATE_LIVE was
     * notified, only .init.text section would be freed. We need to
     * disable kprobes which have been inserted in the sections.
     */
    mutex_lock(&kprobe_mutex);
    for (i = 0; i < KPROBE_TABLE_SIZE; i++) {
        head = &kprobe_table[i];
        hlist_for_each_entry_rcu(p, head, hlist)
            if (within_module_init((unsigned long)p->addr, mod) ||
                (checkcore &&
                 within_module_core((unsigned long)p->addr, mod))) {
                /*
                 * The vaddr this probe is installed will soon
                 * be vfreed buy not synced to disk. Hence,
                 * disarming the breakpoint isn't needed.
                 *
                 * Note, this will also move any optimized probes
                 * that are pending to be removed from their
                 * corresponding lists to the freeing_list and
                 * will not be touched by the delayed
                 * kprobe_optimizer work handler.
                 */
                kill_kprobe(p);
            }
    }
    mutex_unlock(&kprobe_mutex);
    return NOTIFY_DONE;
}
static struct notifier_block kprobe_module_nb = {
    .notifier_call = kprobes_module_callback,
    .priority = 0
};
 
/* Module notifier call back, checking kprobes on the module */
static int kprobes_module_callback(struct notifier_block *nb,
                   unsigned long val, void *data)
{
    struct module *mod = data;
    struct hlist_head *head;
    struct kprobe *p;
    unsigned int i;
    int checkcore = (val == MODULE_STATE_GOING);
 
    if (val != MODULE_STATE_GOING && val != MODULE_STATE_LIVE)
        return NOTIFY_DONE;
 
    /*
     * When MODULE_STATE_GOING was notified, both of module .text and
     * .init.text sections would be freed. When MODULE_STATE_LIVE was
     * notified, only .init.text section would be freed. We need to
     * disable kprobes which have been inserted in the sections.
     */
    mutex_lock(&kprobe_mutex);
    for (i = 0; i < KPROBE_TABLE_SIZE; i++) {
        head = &kprobe_table[i];
        hlist_for_each_entry_rcu(p, head, hlist)
            if (within_module_init((unsigned long)p->addr, mod) ||
                (checkcore &&
                 within_module_core((unsigned long)p->addr, mod))) {
                /*
                 * The vaddr this probe is installed will soon
                 * be vfreed buy not synced to disk. Hence,
                 * disarming the breakpoint isn't needed.
                 *
                 * Note, this will also move any optimized probes
                 * that are pending to be removed from their
                 * corresponding lists to the freeing_list and
                 * will not be touched by the delayed
                 * kprobe_optimizer work handler.
                 */
                kill_kprobe(p);
            }
    }
    mutex_unlock(&kprobe_mutex);
    return NOTIFY_DONE;
}
int register_kprobe(struct kprobe *p)
{
    // 1. 获取地址,根据symbol_name、offset、address,如果是symbol最终会调用kallsyms_lookup_name
    addr = kprobe_addr(p);
    p->addr = addr;
    // 2. 判断是否重复注册
    ret = check_kprobe_rereg(p);
    if (ret)
        return ret;
    ......
    // 3. 判断是否合法
    ret = check_kprobe_address_safe(p, &probed_mod);
    ......
    // 4. 该地址是否已注册过其他函数
    old_p = get_kprobe(p->addr);
    if (old_p) {
        /* Since this may unoptimize old_p, locking text_mutex. */
        // 5 将所有handler挂载到链表上
        ret = register_aggr_kprobe(old_p, p);
        goto out;
    }
 
    // 5. 分配特定地址保存原有指令
    ret = prepare_kprobe(p);
    ......
    // 5.1 添加到hash表中
    INIT_HLIST_NODE(&p->hlist);
    hlist_add_head_rcu(&p->hlist,
               &kprobe_table[hash_ptr(p->addr, KPROBE_HASH_BITS)]);
    // 5.2 该地址代码修改
    if (!kprobes_all_disarmed && !kprobe_disabled(p))
        arm_kprobe(p);
 
    /* Try to optimize kprobe */
    try_to_optimize_kprobe(p);
out:
    mutex_unlock(&kprobe_mutex);
 
    if (probed_mod)
        module_put(probed_mod);
 
    return ret;
}
int register_kprobe(struct kprobe *p)
{
    // 1. 获取地址,根据symbol_name、offset、address,如果是symbol最终会调用kallsyms_lookup_name
    addr = kprobe_addr(p);
    p->addr = addr;
    // 2. 判断是否重复注册
    ret = check_kprobe_rereg(p);
    if (ret)
        return ret;
    ......
    // 3. 判断是否合法
    ret = check_kprobe_address_safe(p, &probed_mod);
    ......
    // 4. 该地址是否已注册过其他函数
    old_p = get_kprobe(p->addr);
    if (old_p) {
        /* Since this may unoptimize old_p, locking text_mutex. */
        // 5 将所有handler挂载到链表上
        ret = register_aggr_kprobe(old_p, p);
        goto out;
    }
 
    // 5. 分配特定地址保存原有指令
    ret = prepare_kprobe(p);
    ......
    // 5.1 添加到hash表中
    INIT_HLIST_NODE(&p->hlist);
    hlist_add_head_rcu(&p->hlist,
               &kprobe_table[hash_ptr(p->addr, KPROBE_HASH_BITS)]);
    // 5.2 该地址代码修改
    if (!kprobes_all_disarmed && !kprobe_disabled(p))
        arm_kprobe(p);
 
    /* Try to optimize kprobe */
    try_to_optimize_kprobe(p);
out:
    mutex_unlock(&kprobe_mutex);
 
    if (probed_mod)
        module_put(probed_mod);
 
    return ret;
}
/* Check passed kprobe is valid and return kprobe in kprobe_table. */
static struct kprobe *__get_valid_kprobe(struct kprobe *p)
{
    struct kprobe *ap, *list_p;
 
    ap = get_kprobe(p->addr);
    if (unlikely(!ap))
        return NULL;
 
    if (p != ap) {
        list_for_each_entry_rcu(list_p, &ap->list, list)
            if (list_p == p)
            /* kprobe p is a valid probe */
                goto valid;
        return NULL;
    }
valid:
    return ap;
}
/* Check passed kprobe is valid and return kprobe in kprobe_table. */
static struct kprobe *__get_valid_kprobe(struct kprobe *p)
{
    struct kprobe *ap, *list_p;
 
    ap = get_kprobe(p->addr);
    if (unlikely(!ap))
        return NULL;
 
    if (p != ap) {
        list_for_each_entry_rcu(list_p, &ap->list, list)
            if (list_p == p)
            /* kprobe p is a valid probe */
                goto valid;
        return NULL;
    }
valid:
    return ap;
}
// arch/arm64/kernel/probes/kprobes.c
int __kprobes arch_prepare_kprobe(struct kprobe *p)
{
    unsigned long probe_addr = (unsigned long)p->addr;
    extern char __start_rodata[];
    extern char __end_rodata[];
    ......
    p->opcode = le32_to_cpu(*p->addr);
    /* decode instruction */
    switch (arm_kprobe_decode_insn(p->addr, &p->ainsn)) {
    case INSN_REJECTED: /* insn not supported */
        return -EINVAL;
 
    case INSN_GOOD_NO_SLOT: /* insn need simulation */
        p->ainsn.api.insn = NULL;
        break;
 
    case INSN_GOOD: /* instruction uses slot */
    // 申请内存空间,用于存放原指令的数据
        p->ainsn.api.insn = get_insn_slot();
        if (!p->ainsn.api.insn)
            return -ENOMEM;
        break;
    };
 
    /* prepare the instruction */
    if (p->ainsn.api.insn)
        // 保存当前地址opcode,也就是保存原始指令
        arch_prepare_ss_slot(p);
    else
        arch_prepare_simulate(p);
 
    return 0;
}
 
static void __kprobes arch_prepare_ss_slot(struct kprobe *p)
{
    /* prepare insn slot */
    patch_text(p->ainsn.api.insn, p->opcode);
 
    flush_icache_range((uintptr_t) (p->ainsn.api.insn),
               (uintptr_t) (p->ainsn.api.insn) +
               MAX_INSN_SIZE * sizeof(kprobe_opcode_t));
 
    /*
     * Needs restoring of return address after stepping xol.
     */
    p->ainsn.api.restore = (unsigned long) p->addr +
      sizeof(kprobe_opcode_t);
}
// arch/arm64/kernel/probes/kprobes.c
int __kprobes arch_prepare_kprobe(struct kprobe *p)
{
    unsigned long probe_addr = (unsigned long)p->addr;
    extern char __start_rodata[];
    extern char __end_rodata[];
    ......
    p->opcode = le32_to_cpu(*p->addr);
    /* decode instruction */
    switch (arm_kprobe_decode_insn(p->addr, &p->ainsn)) {
    case INSN_REJECTED: /* insn not supported */
        return -EINVAL;
 
    case INSN_GOOD_NO_SLOT: /* insn need simulation */
        p->ainsn.api.insn = NULL;
        break;
 
    case INSN_GOOD: /* instruction uses slot */
    // 申请内存空间,用于存放原指令的数据
        p->ainsn.api.insn = get_insn_slot();
        if (!p->ainsn.api.insn)

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 13
支持
分享
最新回复 (4)
雪    币: 3004
活跃值: (30866)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2024-4-28 09:26
1
雪    币: 918
活跃值: (1875)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
3
5.10版本怎么改配置都说找不到register_Kprobe符号,有大佬知道怎么做吗
2024-4-28 10:15
0
雪    币: 2159
活跃值: (4087)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
4
感谢分享
2024-4-28 13:00
0
雪    币: 1378
活跃值: (2828)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
感谢分享
2024-9-6 14:42
0
游客
登录 | 注册 方可回帖
返回
//