linux源码解读(十三):内核驱动module加载kprobe&字节跳动Elkied简要分析

  要想在计算机里干点事,权限肯定是越高越好的。正常情况下,cpu硬件层面保证了运行在0环的操作系统和运行在3环的用户app互相隔离,3环app要想进入0环执行代码只能通过中断或系统调用的形式,执行最多代码的应该就是硬件的驱动了,常见的屏幕打印、磁盘读写、网卡/wifi收发数据都要执行硬件驱动。因为需要被保护(防止被恶意篡改),同时也需要在多个3环进程间互斥,所以驱动都是被操作系统加载到0环的,天然拥有和操作系统其他内核代码一样的权限,因此很多需要高权限运行的功能都是以驱动的形式落地的。这里先以window为例:

  windows早期防护措施不够,杀毒或逆向外挂等软件都喜欢hook SSDT来监控3环的应用程序;SSDT被hook多了容易导致蓝屏,严重影响用户体验;微软直接怒了,搞了驱动签名来严控操作系统对驱动的加载(还有pathguard监控内核代码,一旦发现被更改,直接蓝屏);同时如果发现厂家还在通过驱动恶意hook SSDT,直接吊销签名执照,不再给驱动签名;但是部分厂家从正常的业务角度考虑确实需要hook SSDT咋办了? 比如杀毒软件、驱动保护等业务确实需要监控系统调用,如果一刀切不让hook了,怎么保证windows系统和3环应用的安全了?微软也没赶尽杀绝,提供了回调注册的接口ObRegisterCallbacks,让厂家注册自己的回调函数。每当系统调用被执行时,就调用用户注册的回调函数执行,由此达到和hook一样的效果!

  1、回到linux,毕竟也是和windows齐名的os,也是基于x86硬件架构的os,原理和windows是一样的:驱动是以模块(module)的形式加载到内核0环的,代码和操作系统内核其他代码拥有同样的权限,自然也能读写内核其他代码,这就让通过驱动hook内核代码的方式水到渠成了!那么linux面临了和早期windows一样的问题:如果放任开发人员通过驱动随意hook系统调用,可能造成的恶果:(1)不同的hook程序可能会“互相踩踏”,导致hook失效,甚至错误修改内核代码;(2)hook的代码如果没能完整地还原被破坏的机器码,或则跳转回来的地址填写出错,都会导致内核执行出错。为了避免开发人员hook内核带来的出错,linux和windows一样提供了完整的内核驱动代码hook机制:kprobe,它可以在任意的位置放置探测点(就连函数内部的某条指令处也可以),它提供了探测点的调用前、调用后和内存访问出错3种回调方式,分别是pre_handler、post_handler和fault_handler,其中pre_handler函数将在被探测指令被执行前回调,post_handler会在被探测指令执行完毕后回调(注意不是被探测函数),fault_handler会在内存访问出错时被调用;主要优势特点:

  •   允许在同一个被探测位置注册多个kprobe,避免多个第三方程序同时hook同一段内核代码时发生“互相踩踏”,做到“有序hook”;
  •        除了kernel/kprobes.c和arch/*/kernel/kprobes.c程序中用于实现kprobes自身的函数,外加do_page_fault和notifier_call_chain外,其他内核函数都能hook;do_page_fault和notifier_call_chain被调用太频繁,hook会降低操作系统的运行效率;并且这两个都是在中断的handler代码,需要尽快执行完毕;而且hook的业务意义也不大,所以没必要hook了!
  •        同一个hook点的回调只会被执行一次,比如hook printk后在回调函数中再调用printk,回调函数只执行一次,避免无限嵌套递归,导致栈资源耗尽
  •        kprobes的注册和注销过程中不会使用mutex锁和动态的申请内存,避免占用mutex阻塞其他线程,或sleep让出cpu,导致回调长时间执行,严重影响原函数的效率

  2、kprobe工作hook流程总结如下:

          

  • 当用户注册一个探测点后,kprobe首先备份被探测点的对应指令,然后将原始指令的入口点替换为断点指令,该指令是CPU架构相关的,如i386和x86_64是int3,arm是设置一个未定义指令(目前的x86_64架构支持一种跳转优化方案Jump Optimization,内核需开启CONFIG_OPTPROBES选项,该种方案使用跳转指令来代替断点指令);
  • 当CPU流程执行到探测点的断点指令时(把原机器码用int 3替代),就触发了一个trap,在trap处理流程中会保存当前CPU的寄存器信息并调用对应的trap处理函数,该处理函数会设置kprobe的调用状态并调用用户注册的pre_handler回调函数,kprobe会向该函数传递注册的struct kprobe结构地址以及保存的CPU寄存器信息;
  • 随后kprobe单步执行前面所拷贝的被探测指令,具体执行方式各个架构不尽相同,arm会在异常处理流程中使用模拟函数执行,而x86_64架构则会设置单步调试flag并回到异常触发前的流程中执行;
  • 在单步执行完成后,kprobe执行用户注册的post_handler回调函数;
  • 最后,执行流程回到被探测指令之后的正常流程继续执行。

  和其他内核功能类似,kprobe功能的实现也是由结构体+函数构成了(这里顺便多说几句:C语言没有对象的语法,所以类的继承采用了结构体包含结构体的形式;也没有成员函数的语法,只能采用结构体包含指针函数的形式实现),结构体字段如下:

struct kprobe {
    struct hlist_node hlist://被用于kprobe全局hash,索引值为被探测点的地址;
    struct list_head list://用于链接同一被探测点的不同探测kprobe;
    kprobe_opcode_t *addr://被探测点的地址;
    const char *symbol_name://被探测函数的名字;
    unsigned int offset://被探测点在函数内部的偏移,用于探测函数内部的指令,如果该值为0表示函数的入口;
    kprobe_pre_handler_t pre_handler://在被探测点指令执行之前调用的回调函数;
    kprobe_post_handler_t post_handler://在被探测指令执行之后调用的回调函数;
    kprobe_fault_handler_t fault_handler://在执行pre_handler、post_handler或单步执行被探测指令时出现内存异常则会调用该回调函数;
    kprobe_break_handler_t break_handler://在执行某一kprobe过程中触发了断点指令后会调用该函数,用于实现jprobe;
    kprobe_opcode_t opcode://保存的被探测点原始指令;
    struct arch_specific_insn ainsn://被复制的被探测点的原始指令,用于单步执行,架构强相关(可能包含指令模拟函数);
    u32 flags://状态标记。       
}

  最关键的字段:addr、symbol_name、offset、handler、opcode等;这里居然还有list,明显是用来连接其他hook点的!相关配套初始化、使用、卸载探测点的函数如下:

int register_kprobe(struct kprobe *kp)      //向内核注册kprobe探测点
void unregister_kprobe(struct kprobe *kp)   //卸载kprobe探测点
int register_kprobes(struct kprobe **kps, int num)     //注册探测函数向量,包含多个探测点
void unregister_kprobes(struct kprobe **kps, int num)  //卸载探测函数向量,包含多个探测点
int disable_kprobe(struct kprobe *kp)       //临时暂停指定探测点的探测
int enable_kprobe(struct kprobe *kp)        //恢复指定探测点的探测

  3、由于需要hook内核代码,权限也必须是0环的,所以也要被加载到内核。windows下的api是driver_entry和driver_exit,linux类似,用的api是module_init和moud_exit;这里以字节跳动的Elkied工具为例,在driver\LKM\src\init.c文件中,加载驱动的module的代码如下:

static int __init do_kprobe_initcalls(void)
{
    int ret = 0;
    struct kprobe_initcall const *const *initcall_p;

    for (initcall_p = __start_kprobe_initcall;
         initcall_p < __stop_kprobe_initcall; initcall_p++) {
        struct kprobe_initcall const *initcall = *initcall_p;

        if (initcall->init) {
            ret = initcall->init();
            if (ret < 0)
                goto exit;
        }
    }

    return 0;
exit:
    while (--initcall_p >= __start_kprobe_initcall) {
        struct kprobe_initcall const *initcall = *initcall_p;

        if (initcall->exit)
            initcall->exit();
    }

    return ret;
}

static void do_kprobe_exitcalls(void)
{
    struct kprobe_initcall const *const *initcall_p =
            __stop_kprobe_initcall;

    while (--initcall_p >= __start_kprobe_initcall) {
        struct kprobe_initcall const *initcall = *initcall_p;

        if (initcall->exit)
            initcall->exit();
    }
}

static int __init kprobes_init(void)
{
    int ret;
    ret = do_kprobe_initcalls();
    if (ret < 0)
        return ret;

    return ret;
}

static void __exit kprobes_exit(void)
{
    do_kprobe_exitcalls();
}

module_init(kprobes_init);
module_exit(kprobes_exit);

  上面的代码只看到了通过驱动加载和初始化kprobe,但是具体hook了哪些系统调用了还不清楚,继续看driver\LKM\src\smith_hook.c代码就能发现端倪了:重要的系统调用都被hook了,https://github.com/bytedance/Elkeid/blob/main/driver/README-zh_CN.md  这里也有hook的系统调用列举;

struct kretprobe execve_kretprobe = {
        .kp.symbol_name = P_GET_SYSCALL_NAME(execve),
        .entry_handler = execve_entry_handler,
        .data_size = sizeof(struct execve_data),
        .handler = execve_handler,
};
struct kprobe call_usermodehelper_exec_kprobe = {
        .symbol_name = "call_usermodehelper_exec",
        .pre_handler = call_usermodehelper_exec_pre_handler,
};

struct kprobe rename_kprobe = {
        .symbol_name = P_GET_SYSCALL_NAME(rename),
        .pre_handler = rename_pre_handler,
};

struct kprobe renameat_kprobe = {
        .symbol_name = P_GET_SYSCALL_NAME(renameat),
        .pre_handler = renameat_pre_handler,
};

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,15,0)
struct kprobe renameat2_kprobe = {
        .symbol_name = P_GET_SYSCALL_NAME(renameat2),
        .pre_handler = renameat_pre_handler,
};
#endif

struct kprobe link_kprobe = {
        .symbol_name = P_GET_SYSCALL_NAME(link),
        .pre_handler = link_pre_handler,
};

struct kprobe linkat_kprobe = {
        .symbol_name = P_GET_SYSCALL_NAME(linkat),
        .pre_handler = linkat_pre_handler,
};

struct kprobe ptrace_kprobe = {
        .symbol_name = P_GET_SYSCALL_NAME(ptrace),
        .pre_handler = ptrace_pre_handler,
};

struct kretprobe udp_recvmsg_kretprobe = {
        .kp.symbol_name = "udp_recvmsg",
        .data_size = sizeof(struct udp_recvmsg_data),
        .handler = udp_recvmsg_handler,
        .entry_handler = udp_recvmsg_entry_handler,
};

#if IS_ENABLED(CONFIG_IPV6)
struct kretprobe udpv6_recvmsg_kretprobe = {
        .kp.symbol_name = "udpv6_recvmsg",
        .data_size = sizeof(struct udp_recvmsg_data),
        .handler = udp_recvmsg_handler,
        .entry_handler = udpv6_recvmsg_entry_handler,
};

struct kretprobe ip6_datagram_connect_kretprobe = {
        .kp.symbol_name = "ip6_datagram_connect",
        .data_size = sizeof(struct connect_data),
        .handler = connect_handler,
        .entry_handler = ip6_datagram_connect_entry_handler,
};

struct kretprobe tcp_v6_connect_kretprobe = {
        .kp.symbol_name = "tcp_v6_connect",
        .data_size = sizeof(struct connect_data),
        .handler = connect_handler,
        .entry_handler = tcp_v6_connect_entry_handler,
};
#endif

struct kretprobe ip4_datagram_connect_kretprobe = {
        .kp.symbol_name = "ip4_datagram_connect",
        .data_size = sizeof(struct connect_data),
        .handler = connect_handler,
        .entry_handler = ip4_datagram_connect_entry_handler,
};

struct kretprobe tcp_v4_connect_kretprobe = {
        .kp.symbol_name = "tcp_v4_connect",
        .data_size = sizeof(struct connect_data),
        .handler = connect_handler,
        .entry_handler = tcp_v4_connect_entry_handler,
};

struct kretprobe connect_syscall_kretprobe = {
        .kp.symbol_name = P_GET_SYSCALL_NAME(connect),
        .data_size = sizeof(struct connect_syscall_data),
        .handler = connect_syscall_handler,
        .entry_handler = connect_syscall_entry_handler,
};

struct kretprobe accept_kretprobe = {
        .kp.symbol_name = P_GET_SYSCALL_NAME(accept),
        .data_size = sizeof(struct accept_data),
        .handler = accept_handler,
        .entry_handler = accept_entry_handler,
};

struct kretprobe accept4_kretprobe = {
        .kp.symbol_name = P_GET_SYSCALL_NAME(accept4),
        .data_size = sizeof(struct accept_data),
        .handler = accept_handler,
        .entry_handler = accept4_entry_handler,
};

struct kprobe do_init_module_kprobe = {
        .symbol_name = "do_init_module",
        .pre_handler = do_init_module_pre_handler,
};

struct kretprobe update_cred_kretprobe = {
        .kp.symbol_name = "commit_creds",
        .data_size = sizeof(struct update_cred_data),
        .handler = update_cred_handler,
        .entry_handler = update_cred_entry_handler,
};

struct kprobe security_inode_create_kprobe = {
        .symbol_name = "security_inode_create",
        .pre_handler = security_inode_create_pre_handler,
};

struct kretprobe bind_kretprobe = {
        .kp.symbol_name = P_GET_SYSCALL_NAME(bind),
        .data_size = sizeof(struct bind_data),
        .handler = bind_handler,
        .entry_handler = bind_entry_handler,
};

struct kprobe mprotect_kprobe = {
        .symbol_name = "security_file_mprotect",
        .pre_handler = mprotect_pre_handler,
};

struct kprobe setsid_kprobe = {
        .symbol_name = P_GET_SYSCALL_NAME(setsid),
        .pre_handler = setsid_pre_handler,
};

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 17, 0)
struct kprobe memfd_create_kprobe = {
        .symbol_name = P_GET_SYSCALL_NAME(memfd_create),
        .pre_handler = memfd_create_kprobe_pre_handler,
};
#endif

struct kprobe prctl_kprobe = {
        .symbol_name = P_GET_SYSCALL_NAME(prctl),
        .pre_handler = prctl_pre_handler,
};

struct kprobe open_kprobe = {
        .symbol_name = P_GET_SYSCALL_NAME(open),
        .pre_handler = open_pre_handler,
};

struct kprobe kill_kprobe = {
        .symbol_name = P_GET_SYSCALL_NAME(kill),
        .pre_handler = kill_pre_handler,
};

struct kprobe tkill_kprobe = {
        .symbol_name = P_GET_SYSCALL_NAME(tkill),
        .pre_handler = tkill_pre_handler,
};

struct kprobe nanosleep_kprobe = {
        .symbol_name = P_GET_SYSCALL_NAME(nanosleep),
        .pre_handler = nanosleep_pre_handler,
};

struct kprobe exit_kprobe = {
        .symbol_name = P_GET_SYSCALL_NAME(exit),
        .pre_handler = exit_pre_handler,
};

struct kprobe exit_group_kprobe = {
        .symbol_name = P_GET_SYSCALL_NAME(exit_group),
        .pre_handler = exit_group_pre_handler,
};

struct kprobe security_path_rmdir_kprobe = {
        .symbol_name = "security_path_rmdir",
        .pre_handler = security_path_rmdir_pre_handler,
};

struct kprobe security_path_unlink_kprobe = {
        .symbol_name = "security_path_unlink",
        .pre_handler = security_path_unlink_pre_handler,
};

  这里就看的很清楚了,下面找几个重点函数的回调分析;

     (1)execve_handler:execve可以执行指定位置的文件,hook execve后可以监控操作系统运行的所有文件,属于大杀器级别的hook,整个操作系统在干啥看得一清二楚,特别适合监控病毒、木马的运行!

int execve_handler(struct kretprobe_instance *ri, struct pt_regs *regs)
{
    int sa_family = -1;
    int dport = 0, sport = 0;

    __be32 dip4;
    __be32 sip4;
    pid_t socket_pid = -1;

    char *pname = DEFAULT_RET_STR;
    char *tmp_stdin = DEFAULT_RET_STR;
    char *tmp_stdout = DEFAULT_RET_STR;
    char *buffer = NULL;
    char *pname_buf = NULL;
    char *pid_tree = NULL;
    char *socket_pname = "-1";
    char *socket_pname_buf = NULL;
    char *tty_name = "-1";
    char *exe_path = DEFAULT_RET_STR;
    char *pgid_exe_path = "-1";
    char *stdin_buf = NULL;
    char *stdout_buf = NULL;

    struct in6_addr dip6;
    struct in6_addr sip6;
    struct file *file;
    struct execve_data *data;
    struct tty_struct *tty;

    data = (struct execve_data *)ri->data;
    buffer = kzalloc(PATH_MAX, GFP_ATOMIC);
    /*当前进程所执行文件的路径*/
    exe_path = smith_get_exe_file(buffer, PATH_MAX);

    tty = get_current_tty();//得到当前终端
    if(tty && strlen(tty->name) > 0)
        tty_name = tty->name;

    //exe filter check and argv filter check
    if (execve_exe_check(exe_path) || execve_argv_check(data->argv))
        goto out;
    /*得到当前进程的socket,从这里也可以看出是不是中了木马的反弹webshell*/
    get_process_socket(&sip4, &sip6, &sport, &dip4, &dip6, &dport,
                       &socket_pname, &socket_pname_buf, &socket_pid,
                       &sa_family);

    //if socket exist,get pid tree
    if (sa_family == AF_INET6 || sa_family == AF_INET)
        pid_tree = smith_get_pid_tree(PID_TREE_LIMIT);
    else
        pid_tree = smith_get_pid_tree(PID_TREE_LIMIT_LOW);

    // get stdin
    file = fget_raw(0);
    if (file) {
        stdin_buf = kzalloc(256, GFP_ATOMIC);
        tmp_stdin = smith_d_path(&(file->f_path), stdin_buf, 256);
        fput(file);
    }

    //get stdout
    file = fget_raw(1);
    if (file) {
        stdout_buf = kzalloc(256, GFP_ATOMIC);
        tmp_stdout = smith_d_path(&(file->f_path), stdout_buf, 256);
        fput(file);
    }

    pname_buf = kzalloc(PATH_MAX, GFP_ATOMIC);
    pname = smith_d_path(&current->fs->pwd, pname_buf, PATH_MAX);
    /*打印收集到的各种数据*/
    if (sa_family == AF_INET) {
        execve_print(pname,
                     exe_path, pgid_exe_path, data->argv,
                     tmp_stdin, tmp_stdout,
                     dip4, dport, sip4, sport,
                     pid_tree, tty_name, socket_pid, socket_pname,
                     data->ssh_connection, data->ld_preload,
                     regs_return_value(regs));
    }
#if IS_ENABLED(CONFIG_IPV6)
        else if (sa_family == AF_INET6) {
        execve6_print(pname,
                  exe_path, pgid_exe_path, data->argv,
                  tmp_stdin, tmp_stdout,
                  &dip6, dport, &sip6, sport,
                  pid_tree, tty_name, socket_pid, socket_pname,
                  data->ssh_connection, data->ld_preload,
                  regs_return_value(regs));
    }
#endif
    else {
        execve_nosocket_print(pname,
                              exe_path, pgid_exe_path, data->argv,
                              tmp_stdin, tmp_stdout,
                              pid_tree, tty_name,
                              data->ssh_connection, data->ld_preload,
                              regs_return_value(regs));
    }

out:
    if (pname_buf)
        kfree(pname_buf);

    if (stdin_buf)
        kfree(stdin_buf);

    if (stdout_buf)
        kfree(stdout_buf);

    if (buffer)
        kfree(buffer);

    if (data->free_argv)
        kfree(data->argv);

    if (pid_tree)
        kfree(pid_tree);

    if (data->free_ld_preload)
        kfree(data->ld_preload);

    if (data->free_ssh_connection)
        kfree(data->ssh_connection);

    if(tty)
        tty_kref_put(tty);

    return 0;
}

  (2)mprotect_pre_handler:hook了security_file_mprotect的回调函数,这个函数可能不出名,她是在do_mprotect_pkey中被调用的,整个mprotect的调用链如下:

SYSCALL_DEFINE3(mprotect, .., start, .., len, .., prot)
    -> do_mprotect_pkey(start, len, prot, pkey=-1)
        -> mprotect_fixup(vma, .., start, end, newflags)
            -> change_protection(vma, start, end, newprot, cp_flags)
                -> change_protection_range(vma, addr, end, newprot, cp_flags)
                    -> change_p4d_range(vma, pgd, add, end, newprot, cp_flags)
                        -> change_pmd_range(vma, pud, addr, end, newprot, cp_flags)
                            -> change_pte_range(vma, pmd, addr, end, newprot, cp_flags)

   从底层的change_pte_rang、change_pmd_range、change_p4d_range可以看出,mprotect函数最终改变的是页属性,这个也可以用来做反调试的:把关键的代码地址改为可读可执行,但是不可写,就没法下断点调试了;正常情况下,代码也只会被读,然后执行。如果被改写,肯定不是正常情况,所以可以hook这个链条上的方法来监控关键代码是否被调试了。如果页面不可写,强行更改数据会报SIGSEGV错,用ida调试x音的时候没少遇到这种弹窗吧?不过从公开的资料看,Elkeid貌似并没用在x音客户端的防护,只是在字节内部的生产环境,监控的是服务器操作系统的运行;回调函数代码如下:

int mprotect_pre_handler(struct kprobe *p, struct pt_regs *regs)
{
    int target_pid = -1;
    unsigned long prot;

    char *file_path = "-1";
    char *file_buf = NULL;
    char *vm_file_path = "-1";
    char *vm_file_buff = NULL;
    char *exe_path = "-1";
    char *abs_buf = NULL;
    char *pid_tree = NULL;

    struct vm_area_struct *vma;

    //only get PROT_EXEC mprotect info
    //The memory can be used to store instructions which can then be executed. On most architectures,
    //this flag implies that the memory can be read (as if PROT_READ had been specified).
    prot = (unsigned long)p_regs_get_arg2(regs);
    if (prot & PROT_EXEC) {
        abs_buf = kzalloc(PATH_MAX, GFP_ATOMIC);
        exe_path = smith_get_exe_file(abs_buf, PATH_MAX);//当前进程对应文件的路径

        vma = (struct vm_area_struct *)p_regs_get_arg1(regs);
        if (IS_ERR_OR_NULL(vma)) {
            mprotect_print(exe_path, prot, "-1", -1, "-1", "-1");
        } else {
            rcu_read_lock();
            if (!IS_ERR_OR_NULL(vma->vm_mm)) {
                if (!IS_ERR_OR_NULL(&vma->vm_mm->exe_file)) {
                    if (get_file_rcu(vma->vm_mm->exe_file)) {
                        file_buf = kzalloc(PATH_MAX, GFP_ATOMIC);
                        file_path = smith_d_path(&vma->vm_mm->exe_file->f_path, file_buf, PATH_MAX);
                        fput(vma->vm_mm->exe_file);
                    }
                }
#ifdef CONFIG_MEMCG
                target_pid = vma->vm_mm->owner->pid;
#endif
            }

            if (!IS_ERR_OR_NULL(vma->vm_file)) {
                if (get_file_rcu(vma->vm_file)) {
                    vm_file_buff =
                            kzalloc(PATH_MAX, GFP_ATOMIC);
                    vm_file_path = smith_d_path(&vma->vm_file->f_path, vm_file_buff, PATH_MAX);
                    fput(vma->vm_file);
                }
            }
            rcu_read_unlock();
            /*被修改内存属性的pid树,从这里可以看到关键进程是不是在被调试等*/
            pid_tree = smith_get_pid_tree(PID_TREE_LIMIT);
            mprotect_print(exe_path, prot, file_path, target_pid, vm_file_path, pid_tree);
        }

        if (pid_tree)
            kfree(pid_tree);

        if (file_buf)
            kfree(file_buf);

        if (abs_buf)
            kfree(abs_buf);

        if (vm_file_buff)
            kfree(vm_file_buff);
    }
    return 0;
}

  (3)ptrace:调试全靠它了,frida和ida这俩卧龙凤雏用的都是这个。要想监控自己的进程是不是被调试了,必须监控ptrace了!回调代码如下:

int ptrace_pre_handler(struct kprobe *p, struct pt_regs *regs)
{
    long request;
    request = (long)p_get_arg1(regs);

    //only get PTRACE_POKETEXT/PTRACE_POKEDATA ptrace
    //Read a word at the address addr in the tracee's memory,
    //returning the word as the result of the ptrace() call.  Linux
    //does not have separate text and data address spaces, so these  linux居然没区分代码和数据空间
    //two requests are currently equivalent.  (data is ignored; but
    //see NOTES.)
    /*PTRACE_PEEKTEXT或PTRACE_PEEKDATA:从addr参数指示的地址开始读取一个WORD的长度的数据并通过返回值传递回来*/
    if (request == PTRACE_POKETEXT || request == PTRACE_POKEDATA) {
        long pid;
        void *addr;
        char *exe_path = DEFAULT_RET_STR;
        char *buffer = NULL;
        char *pid_tree = NULL;

        pid = (long)p_get_arg2(regs);
        addr = (void *)p_get_arg3(regs);

        if (IS_ERR_OR_NULL(addr))
            return 0;

        buffer = kzalloc(PATH_MAX, GFP_ATOMIC);
        /*得到当前文件路径*/
        exe_path = smith_get_exe_file(buffer, PATH_MAX);
        /*得到当前进程的进程树*/
        pid_tree = smith_get_pid_tree(PID_TREE_LIMIT);
        /*打印结果*/
        ptrace_print(request, pid, addr, "-1", exe_path, pid_tree);

        if(buffer)
            kfree(buffer);

        if(pid_tree)
            kfree(pid_tree);
    }

    return 0;
}

  不过和ida有点不同,frida使用ptrace attach到进程之后,往进程中注入一个frida-agent-32.so模块,此模块是frida和frida-server通信的重要模块,所以frida不会一直占用ptrace,注入模块完成后便detach,所以ptrace回调函数收集到的数据可能不会太多!

  目前所有的handler仅仅是收集进程名称、进程id树、文件路径、文件名或其他相关信息,貌似并未才取任何反制措施。也难怪,这个是用在后台服务端的,不是客户端,没必要才取exit或其他反制措施.....

    4、大家平时用frida hook native代码的时候,有onEnter和onLeave两个分支,分别是进入native函数和离开native函数时挂钩,前者一般用来查看或修改函数的参数,后者一般用来查看和修改函数返回值,其实在linux底层已经提供了相应的方式来达到同样的目的:jprobe和kretprobe,并且这两个都是基于kprobe实现的!

 

参考:

1、https://cloud.tencent.com/developer/article/1874723?from=15425%20%20%20L   Linux内核调试技术——kprobe使用与实现

2、https://yaotingting.net/2021/05/09/mprotect-analysis/    Linux/mprotect源码分析

3、https://github.com/bytedance/Elkeid/blob/main/driver/README-zh_CN.md    About Elkeid(AgentSmith-HIDS) Driver

4、https://zhuanlan.zhihu.com/p/347313289 systemtap介绍

posted @ 2022-01-08 17:13  第七子007  阅读(1591)  评论(0编辑  收藏  举报