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介紹


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM