# 2021q3 Homework1 (quiz1) contributed by < [fdgkhdkgh](https://) > ###### tags: `linux2021` `kernel` [第一週隨堂測驗題](https://hackmd.io/@sysprog/linux2021-summer-quiz1) ## 解釋上述程式碼運作原理,包含 ftrace 的使用 在 kernel module 初始化的地方,呼叫 init_hook ```cpp static int _hideproc_init(void) { . . . init_hook(); return 0; } ``` --- ```cpp struct ftrace_hook { const char *name; void *func, *orig; unsigned long address; struct ftrace_ops ops; }; . . . static void init_hook(void) { // 使用 kallsyms_lookup_name 去找到 linux kernel 中 "find_ge_pid" 這個 function 的位址 real_find_ge_pid = (find_ge_pid_func) kallsyms_lookup_name("find_ge_pid"); hook.name = "find_ge_pid"; // 儲存我們想執行的 find_ge_pid 的位址 hook.func = hook_find_ge_pid; // 儲存真正的 find_ge_pid 的位址 hook.orig = &real_find_ge_pid; // 開始安裝我們在這裡設定的 ftrace hook hook_install(&hook); } ``` --- ```cpp static int hook_install(struct ftrace_hook *hook) { // 再抓取一次我們想 hook 的 kernel function 的位址 // 若抓取失敗則回傳錯誤碼 int err = hook_resolve_addr(hook); if (err) return err; // 註冊 ftrace 的 call back function hook->ops.func = hook_ftrace_thunk; hook->ops.flags = FTRACE_OPS_FL_SAVE_REGS | FTRACE_OPS_FL_RECURSION_SAFE | FTRACE_OPS_FL_IPMODIFY; // hook->address : 被我們 hook 的 kernel function // ftrace_set_filter_ip : 哪些 ip address 可以使用這個 ftrace 的 call back function。 // e.g. 這邊使用的是真正的 find_ge_pid 的位址,表示當我們呼叫 find_ge_pid 時, // 會進入此 ftrace 設定的 call back function ( 也就是 hook_ftrace_thunk ) // 而呼叫其他的 function 則不會進入此 ftrace 的 call back function err = ftrace_set_filter_ip(&hook->ops, hook->address, 0, 0); if (err) { printk("ftrace_set_filter_ip() failed: %d\n", err); return err; } // 註冊這個 ftrace,並開始啟用 err = register_ftrace_function(&hook->ops); if (err) { printk("register_ftrace_function() failed: %d\n", err); // 註冊失敗的話,要把這個 filter 關掉 ftrace_set_filter_ip(&hook->ops, hook->address, 1, 0); return err; } return 0; } ``` --- 設定好後,每當 linux kernel 裡除了我們的 kernel module 以外的任何地方呼叫 "find_ge_pid" ,就會執行我們所設定的 frace 的 call back function "hook_ftrace_thunk"。 ```cpp static void notrace hook_ftrace_thunk(unsigned long ip, unsigned long parent_ip, struct ftrace_ops *ops, struct pt_regs *regs) { struct ftrace_hook *hook = container_of(ops, struct ftrace_hook, ops); // 假如 caller 的 instruction pointer 並不在我們這個 kernel module 的範疇內, // 就將其 instruction pointer 指向我們這個 kernel module 版本的 hook_find_ge_pid if (!within_module(parent_ip, THIS_MODULE)) regs->ip = (unsigned long) hook->func; } ``` --- 從 ftrace 的 call back function "hook_ftrace_thunk" 會跳轉到我們的 kernel module 所實作的 hook_find_ge_pid。 ```cpp static struct pid *hook_find_ge_pid(int nr, struct pid_namespace *ns) { struct pid *pid = real_find_ge_pid(nr, ns); // 只要想要搜尋的 pid_t 位於 hidden_list 內,就讓 "real_find_ge_pid" 去搜尋 pid_t + 1 ,藉此隱藏 pid_t while (pid && is_hidden_proc(pid->numbers->nr)) pid = real_find_ge_pid(pid->numbers->nr + 1, ns); return pid; } ``` --- ## 若你用的核心較新,請試著找出替代方案 使用的是 linux-5.4.134 ,剛好不會碰上這個問題。 ``` $ uname -a Linux tommy-X450VP 5.4.134 #1 SMP Sun Jul 25 17:18:47 CST 2021 x86_64 x86_64 x86_64 GNU/Linux ``` --- ## 本核心模組只能隱藏單一 PID,請擴充為允許其 PPID 也跟著隱藏 1. 使用 find_vpid 從 pid_t 拿到 struct pid 2. 再用 pid_task 從 struct pid 拿到 struct task_struct 3. 使用 task_ppid_nr 從 struct task_struct 拿到 ppid 4. 把 pid 以及 ppid 都用 hide_process 來隱藏起來 ```cpp= static ssize_t device_write(struct file *filep, const char *buffer, size_t len, loff_t *offset) { long pid; char *message; char add_message[] = "add", del_message[] = "del"; if (len < sizeof(add_message) - 1 && len < sizeof(del_message) - 1) return -EAGAIN; message = kmalloc(len + 1, GFP_KERNEL); memset(message, 0, len + 1); copy_from_user(message, buffer, len); if (!memcmp(message, add_message, sizeof(add_message) - 1)) { kstrtol(message + sizeof(add_message), 10, &pid); struct task_struct *task = pid_task(find_vpid(pid), PIDTYPE_PID); struct pid_t *ppid = task_ppid_nr(task); hide_process(pid); hide_process(ppid); } else if (!memcmp(message, del_message, sizeof(del_message) - 1)) { kstrtol(message + sizeof(del_message), 10, &pid); unhide_process(pid); } else { kfree(message); return -EAGAIN; } ``` --- ## 允許給定一組 PID 列表,而非僅有單一 PID 使用簡易的 parser 將 PID 列表裡的所有 PID 用 hide_process 隱藏起來 ```cpp= static ssize_t device_write(struct file *filep, const char *buffer, size_t len, loff_t *offset) { long pid; char *message; char add_message[] = "add", del_message[] = "del"; if (len < sizeof(add_message) - 1 && len < sizeof(del_message) - 1) return -EAGAIN; message = kmalloc(len + 1, GFP_KERNEL); memset(message, 0, len + 1); copy_from_user(message, buffer, len); if (!memcmp(message, add_message, sizeof(add_message) - 1)) { hide_processes((char *) message + sizeof(add_message)); } else if (!memcmp(message, del_message, sizeof(del_message) - 1)) { unhide_processes((char *) message + sizeof(del_message)); } else { kfree(message); return -EAGAIN; } *offset = len; kfree(message); return len; } . . . void hide_processes(char *pids) { char num[32]; memset(num, 0, sizeof(num)); int stat = 0; int i = 0; int numi = 0; long pid = 0; while(pids[i] != 0) { if(stat == 0) { if(pids[i] >= '0' && pids[i] <= '9') { num[numi++] = pids[i]; stat = 1; } } else if(stat == 1) { if(pids[i] >= '0' && pids[i] <= '9') { num[numi++] = pids[i]; } else { kstrtol(num, 10, &pid); printk("%d\n", pid); hide_process(pid); memset(num, 0, sizeof(num)); numi = 0; stat = 0; } } i++; } if(num[0] != 0) { kstrtol(num, 10, &pid); printk("%d\n", pid); hide_process(pid); } } ``` --- ## 指出程式碼可改進的地方,並動手實作 ### 釋放 ftrace 的 hook 在釋放 kernel module 前使用老師寫好的 hook_remove 將 hook 釋放。 ```cpp= static void _hideproc_exit(void) { printk(KERN_INFO "@ %s\n", __func__); hook_remove(&hook); } ``` --- ### 處理重複加兩次相同 pid 的情形 避免同一個 pid 重複出現在 linked list 裡。 ```cpp= static int hide_process(pid_t pid) { if(!is_hidden_proc(pid)) { pid_node_t *proc = kmalloc(sizeof(pid_node_t), GFP_KERNEL); proc->id = pid; // TODO list_add(&(proc->list_node), &hidden_proc); } return SUCCESS; } ``` --- ### 使用 rbtree 實作 hideproc 插入節點由 O(1) 變慢成 O(logn) 刪除節點由 O(n) 加快成 O(logn) 查詢節點由 O(n) 加快成 O(logn) https://github.com/fdgkhdkgh/linux2021-summer-q1/tree/main/main_rb ---