<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<atom:link href="http://gentoo-zh.org/extern.php?action=feed&amp;tid=455&amp;type=rss" rel="self" type="application/rss+xml" />
		<title><![CDATA[Gentoo中文社区 / linux源码解读（十三）：内核驱动module加载kprobe&字节跳动Elkied简要分析]]></title>
		<link>http://www.gentoo-zh.org/viewtopic.php?id=455</link>
		<description><![CDATA[linux源码解读（十三）：内核驱动module加载kprobe&字节跳动Elkied简要分析 最近发表的帖子。]]></description>
		<lastBuildDate>Sun, 09 Oct 2022 03:47:58 +0000</lastBuildDate>
		<generator>FluxBB</generator>
		<item>
			<title><![CDATA[linux源码解读（十三）：内核驱动module加载kprobe&字节跳动Elkied简要分析]]></title>
			<link>http://www.gentoo-zh.org/viewtopic.php?pid=462#p462</link>
			<description><![CDATA[<p>要想在计算机里干点事，权限肯定是越高越好的。正常情况下，cpu硬件层面保证了运行在0环的操作系统和运行在3环的用户app互相隔离，3环app要想进入0环执行代码只能通过中断或系统调用的形式，执行最多代码的应该就是硬件的驱动了，常见的屏幕打印、磁盘读写、网卡/wifi收发数据都要执行硬件驱动。因为需要被保护（防止被恶意篡改），同时也需要在多个3环进程间互斥，所以驱动都是被操作系统加载到0环的，天然拥有和操作系统其他内核代码一样的权限，因此很多需要高权限运行的功能都是以驱动的形式落地的。这里先以window为例：</p><p>&#160; windows早期防护措施不够，杀毒或逆向外挂等软件都喜欢hook SSDT来监控3环的应用程序；SSDT被hook多了容易导致蓝屏，严重影响用户体验；微软直接怒了，搞了驱动签名来严控操作系统对驱动的加载（还有pathguard监控内核代码，一旦发现被更改，直接蓝屏）；同时如果发现厂家还在通过驱动恶意hook SSDT，直接吊销签名执照，不再给驱动签名；但是部分厂家从正常的业务角度考虑确实需要hook SSDT咋办了？ 比如杀毒软件、驱动保护等业务确实需要监控系统调用，如果一刀切不让hook了，怎么保证windows系统和3环应用的安全了？微软也没赶尽杀绝，提供了回调注册的接口ObRegisterCallbacks，让厂家注册自己的回调函数。每当系统调用被执行时，就调用用户注册的回调函数执行，由此达到和hook一样的效果！</p><p>&#160; 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会在内存访问出错时被调用；主要优势特点：</p><p>&#160; &#160; &#160; 允许在同一个被探测位置注册多个kprobe，避免多个第三方程序同时hook同一段内核代码时发生“互相踩踏”，做到“有序hook”；<br />&#160; &#160; &#160; &#160; &#160; &#160;除了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了！<br />&#160; &#160; &#160; &#160; &#160; &#160;同一个hook点的回调只会被执行一次，比如hook printk后在回调函数中再调用printk，回调函数只执行一次，避免无限嵌套递归，导致栈资源耗尽；<br />&#160; &#160; &#160; &#160; &#160; &#160;kprobes的注册和注销过程中不会使用mutex锁和动态的申请内存，避免占用mutex阻塞其他线程，或sleep让出cpu，导致回调长时间执行，严重影响原函数的效率；</p><p>&#160; 2、kprobe工作hook流程总结如下：<br /><span class="postimg"><img src="https://img2020.cnblogs.com/blog/2052730/202201/2052730-20220107213057583-262241121.png" alt="FluxBB bbcode 测试" /></span> </p><br /><p>&#160; &#160; 当用户注册一个探测点后，kprobe首先备份被探测点的对应指令，然后将原始指令的入口点替换为断点指令，该指令是CPU架构相关的，如i386和x86_64是int3，arm是设置一个未定义指令（目前的x86_64架构支持一种跳转优化方案Jump Optimization，内核需开启CONFIG_OPTPROBES选项，该种方案使用跳转指令来代替断点指令）；<br />&#160; &#160; 当CPU流程执行到探测点的断点指令时（把原机器码用int 3替代），就触发了一个trap，在trap处理流程中会保存当前CPU的寄存器信息并调用对应的trap处理函数，该处理函数会设置kprobe的调用状态并调用用户注册的pre_handler回调函数，kprobe会向该函数传递注册的struct kprobe结构地址以及保存的CPU寄存器信息；<br />&#160; &#160; 随后kprobe单步执行前面所拷贝的被探测指令，具体执行方式各个架构不尽相同，arm会在异常处理流程中使用模拟函数执行，而x86_64架构则会设置单步调试flag并回到异常触发前的流程中执行；<br />&#160; &#160; 在单步执行完成后，kprobe执行用户注册的post_handler回调函数；<br />&#160; &#160; 最后，执行流程回到被探测指令之后的正常流程继续执行。</p><p>&#160; 和其他内核功能类似，kprobe功能的实现也是由结构体+函数构成了（这里顺便多说几句：C语言没有对象的语法，所以类的继承采用了结构体包含结构体的形式；也没有成员函数的语法，只能采用结构体包含指针函数的形式实现），结构体字段如下：</p><div class="codebox"><pre><code>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：//状态标记。       
}</code></pre></div><p>&#160; 最关键的字段：addr、symbol_name、offset、handler、opcode等；这里居然还有list，明显是用来连接其他hook点的！相关配套初始化、使用、卸载探测点的函数如下：</p><div class="codebox"><pre><code>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)        //恢复指定探测点的探测</code></pre></div><p>&#160; 3、由于需要hook内核代码，权限也必须是0环的，所以也要被加载到内核。windows下的api是driver_entry和driver_exit，linux类似，用的api是module_init和moud_exit；这里以字节跳动的Elkied工具为例，在driver\LKM\src\init.c文件中，加载驱动的module的代码如下：</p><div class="codebox"><pre class="vscroll"><code>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 &lt; __stop_kprobe_initcall; initcall_p++) {
        struct kprobe_initcall const *initcall = *initcall_p;

        if (initcall-&gt;init) {
            ret = initcall-&gt;init();
            if (ret &lt; 0)
                goto exit;
        }
    }

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

        if (initcall-&gt;exit)
            initcall-&gt;exit();
    }

    return ret;
}

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

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

        if (initcall-&gt;exit)
            initcall-&gt;exit();
    }
}

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

    return ret;
}

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

module_init(kprobes_init);
module_exit(kprobes_exit);</code></pre></div><p>&#160; 上面的代码只看到了通过驱动加载和初始化kprobe，但是具体hook了哪些系统调用了还不清楚，继续看driver\LKM\src\smith_hook.c代码就能发现端倪了：重要的系统调用都被hook了，https://github.com/bytedance/Elkeid/blob/main/driver/README-zh_CN.md&#160; 这里也有hook的系统调用列举；</p><div class="codebox"><pre class="vscroll"><code>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 = &quot;call_usermodehelper_exec&quot;,
        .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 &gt;= 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 = &quot;udp_recvmsg&quot;,
        .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 = &quot;udpv6_recvmsg&quot;,
        .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 = &quot;ip6_datagram_connect&quot;,
        .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 = &quot;tcp_v6_connect&quot;,
        .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 = &quot;ip4_datagram_connect&quot;,
        .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 = &quot;tcp_v4_connect&quot;,
        .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 = &quot;do_init_module&quot;,
        .pre_handler = do_init_module_pre_handler,
};

struct kretprobe update_cred_kretprobe = {
        .kp.symbol_name = &quot;commit_creds&quot;,
        .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 = &quot;security_inode_create&quot;,
        .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 = &quot;security_file_mprotect&quot;,
        .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 &gt;= 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 = &quot;security_path_rmdir&quot;,
        .pre_handler = security_path_rmdir_pre_handler,
};

struct kprobe security_path_unlink_kprobe = {
        .symbol_name = &quot;security_path_unlink&quot;,
        .pre_handler = security_path_unlink_pre_handler,
};</code></pre></div><p>&#160; 这里就看的很清楚了，下面找几个重点函数的回调分析；</p><p>&#160; &#160; &#160;（1）execve_handler：execve可以执行指定位置的文件，hook execve后可以监控操作系统运行的所有文件，属于大杀器级别的hook，整个操作系统在干啥看得一清二楚，特别适合监控病毒、木马的运行！</p><div class="codebox"><pre class="vscroll"><code>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 = &quot;-1&quot;;
    char *socket_pname_buf = NULL;
    char *tty_name = &quot;-1&quot;;
    char *exe_path = DEFAULT_RET_STR;
    char *pgid_exe_path = &quot;-1&quot;;
    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-&gt;data;
    buffer = kzalloc(PATH_MAX, GFP_ATOMIC);
    /*当前进程所执行文件的路径*/
    exe_path = smith_get_exe_file(buffer, PATH_MAX);

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

    //exe filter check and argv filter check
    if (execve_exe_check(exe_path) || execve_argv_check(data-&gt;argv))
        goto out;
    /*得到当前进程的socket，从这里也可以看出是不是中了木马的反弹webshell*/
    get_process_socket(&amp;sip4, &amp;sip6, &amp;sport, &amp;dip4, &amp;dip6, &amp;dport,
                       &amp;socket_pname, &amp;socket_pname_buf, &amp;socket_pid,
                       &amp;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(&amp;(file-&gt;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(&amp;(file-&gt;f_path), stdout_buf, 256);
        fput(file);
    }

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

    if (pid_tree)
        kfree(pid_tree);

    if (data-&gt;free_ld_preload)
        kfree(data-&gt;ld_preload);

    if (data-&gt;free_ssh_connection)
        kfree(data-&gt;ssh_connection);

    if(tty)
        tty_kref_put(tty);

    return 0;
}</code></pre></div><p>&#160; （2）mprotect_pre_handler：hook了security_file_mprotect的回调函数，这个函数可能不出名，她是在do_mprotect_pkey中被调用的，整个mprotect的调用链如下：</p><div class="codebox"><pre><code>SYSCALL_DEFINE3(mprotect, .., start, .., len, .., prot)
    -&gt; do_mprotect_pkey(start, len, prot, pkey=-1)
        -&gt; mprotect_fixup(vma, .., start, end, newflags)
            -&gt; change_protection(vma, start, end, newprot, cp_flags)
                -&gt; change_protection_range(vma, addr, end, newprot, cp_flags)
                    -&gt; change_p4d_range(vma, pgd, add, end, newprot, cp_flags)
                        -&gt; change_pmd_range(vma, pud, addr, end, newprot, cp_flags)
                            -&gt; change_pte_range(vma, pmd, addr, end, newprot, cp_flags)</code></pre></div><p>&#160; &#160;从底层的change_pte_rang、change_pmd_range、change_p4d_range可以看出，mprotect函数最终改变的是页属性，这个也可以用来做反调试的：把关键的代码地址改为可读可执行，但是不可写，就没法下断点调试了；正常情况下，代码也只会被读，然后执行。如果被改写，肯定不是正常情况，所以可以hook这个链条上的方法来监控关键代码是否被调试了。如果页面不可写，强行更改数据会报SIGSEGV错，用ida调试x音的时候没少遇到这种弹窗吧？不过从公开的资料看，Elkeid貌似并没用在x音客户端的防护，只是在字节内部的生产环境，监控的是服务器操作系统的运行；回调函数代码如下：</p><div class="codebox"><pre class="vscroll"><code>int mprotect_pre_handler(struct kprobe *p, struct pt_regs *regs)
{
    int target_pid = -1;
    unsigned long prot;

    char *file_path = &quot;-1&quot;;
    char *file_buf = NULL;
    char *vm_file_path = &quot;-1&quot;;
    char *vm_file_buff = NULL;
    char *exe_path = &quot;-1&quot;;
    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 &amp; 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, &quot;-1&quot;, -1, &quot;-1&quot;, &quot;-1&quot;);
        } else {
            rcu_read_lock();
            if (!IS_ERR_OR_NULL(vma-&gt;vm_mm)) {
                if (!IS_ERR_OR_NULL(&amp;vma-&gt;vm_mm-&gt;exe_file)) {
                    if (get_file_rcu(vma-&gt;vm_mm-&gt;exe_file)) {
                        file_buf = kzalloc(PATH_MAX, GFP_ATOMIC);
                        file_path = smith_d_path(&amp;vma-&gt;vm_mm-&gt;exe_file-&gt;f_path, file_buf, PATH_MAX);
                        fput(vma-&gt;vm_mm-&gt;exe_file);
                    }
                }
#ifdef CONFIG_MEMCG
                target_pid = vma-&gt;vm_mm-&gt;owner-&gt;pid;
#endif
            }

            if (!IS_ERR_OR_NULL(vma-&gt;vm_file)) {
                if (get_file_rcu(vma-&gt;vm_file)) {
                    vm_file_buff =
                            kzalloc(PATH_MAX, GFP_ATOMIC);
                    vm_file_path = smith_d_path(&amp;vma-&gt;vm_file-&gt;f_path, vm_file_buff, PATH_MAX);
                    fput(vma-&gt;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;
}</code></pre></div><p>&#160; （3）ptrace：调试全靠它了，frida和ida这俩卧龙凤雏用的都是这个。要想监控自己的进程是不是被调试了，必须监控ptrace了！回调代码如下：</p><div class="codebox"><pre class="vscroll"><code>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&#039;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, &quot;-1&quot;, exe_path, pid_tree);

        if(buffer)
            kfree(buffer);

        if(pid_tree)
            kfree(pid_tree);
    }

    return 0;
}</code></pre></div><p>&#160; 不过和ida有点不同，frida使用ptrace attach到进程之后，往进程中注入一个frida-agent-32.so模块，此模块是frida和frida-server通信的重要模块，所以frida不会一直占用ptrace，注入模块完成后便detach，所以ptrace回调函数收集到的数据可能不会太多！<br />&#160; 目前所有的handler仅仅是收集进程名称、进程id树、文件路径、文件名或其他相关信息，貌似并未才取任何反制措施。也难怪，这个是用在后台服务端的，不是客户端，没必要才取exit或其他反制措施.....</p><p>&#160; &#160; 4、大家平时用frida hook native代码的时候，有onEnter和onLeave两个分支，分别是进入native函数和离开native函数时挂钩，前者一般用来查看或修改函数的参数，后者一般用来查看和修改函数返回值，其实在linux底层已经提供了相应的方式来达到同样的目的：jprobe和kretprobe，并且这两个都是基于kprobe实现的！</p><p> </p><p>参考：</p><p>1、https://cloud.tencent.com/developer/article/1874723?from=15425%20%20%20L&#160; &#160;Linux内核调试技术——kprobe使用与实现</p><p>2、https://yaotingting.net/2021/05/09/mprotect-analysis/&#160; &#160; Linux/mprotect源码分析</p><p>3、https://github.com/bytedance/Elkeid/blob/main/driver/README-zh_CN.md&#160; &#160; About Elkeid(AgentSmith-HIDS) Driver</p><p>4、https://zhuanlan.zhihu.com/p/347313289 systemtap介绍</p>]]></description>
			<author><![CDATA[dummy@example.com (batsom)]]></author>
			<pubDate>Sun, 09 Oct 2022 03:47:58 +0000</pubDate>
			<guid>http://www.gentoo-zh.org/viewtopic.php?pid=462#p462</guid>
		</item>
	</channel>
</rss>
