zoukankan      html  css  js  c++  java
  • 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介绍

  • 相关阅读:
    生物神经网络和人工神经网络浅谈
    卷积神经网络
    DOM进阶之HTMl属性操作(对象属性)
    01 selenium基本使用补充
    01 selenium基本使用
    day4笔记
    03 获取豆瓣电影top250
    02 爬取视频
    day3笔记
    01 requests基本使用
  • 原文地址:https://www.cnblogs.com/theseventhson/p/15776229.html
Copyright © 2011-2022 走看看