# 2021q3 Homework1 (quiz1) contributed by < `new-type` > ## 簡介 * **問題描述**: * 請參閱 [2021 年暑期 Linux 核心 第 1 週測驗題](https://hackmd.io/@sysprog/linux2021-summer-quiz1) * [原程式](https://gist.github.com/jserv/b2562431901905b6f826521e3d3bb865) * **開發環境**: * Linux kernel: 5.4.0-80-generic * OS: ubuntu-20.04.2 * Processors: 2 processor cores * Memory: 4096 MB ## 延伸問題 - 1:解釋上述程式碼運作原理,包含 ftrace 的使用 此次測驗程式主要功能是讓 ps 中特定的 PID 消失。 原理是建立一個 char device 記錄需要被隱藏 process 的串列 (hidden_proc)。隱藏的方式是利用原本的 find_ge_pid 的回傳元素若是在需隱藏的串列中,則回傳下一個元素;反之就是回傳該元素。 char device 的建立在 ```c static int _hideproc_init(void) ``` 而如何取代原本的 find_ge_pid 則要利用 Ftrace 的 callback 實現。這部分是在 init_hook 中,先利用 kallsyms_lookup_name 找到 find_ge_pid 實際的位置記錄到 hook.orig,另外填上我們更改後的到 hook.func。 ```c struct ftrace_hook { const char *name; void *func, *orig; unsigned long address; struct ftrace_ops ops; }; ``` 實際安裝 Ftrace hook 則是在 hook_install 中。首先是設定 **ftrace_ops** ```c struct ftrace_ops ops = { .func = my_callback_func, .flags = MY_FTRACE_FLAGS .private = any_private_data_structure, }; ``` 測試程式裏對應的設定是 ```c hook->ops.func = hook_ftrace_thunk; hook->ops.flags = FTRACE_OPS_FL_SAVE_REGS | FTRACE_OPS_FL_RECURSION_SAFE | FTRACE_OPS_FL_IPMODIFY; ``` 再來要設定 **filter** ,為了讓特定的函數才會呼叫 callback,這裏的 ip 就是原本 find_ge_pid 的實際位置: ```c int ftrace_set_filter_ip(struct ftrace_ops *ops, unsigned long ip, int remove, int reset); ``` 最後要**啟用 tracing**: ```c register_ftrace_function(&ops); ``` 以上完成後的 char device 利用 insmod 安裝在 /dev 目錄。當對它寫入時,就會呼叫到程式中的 device_write,這裏分成是要加入隱藏或是刪除隱藏,對應到相對的 hide_process 和 unhide_process。 而 hide_process 是將目前的 node 加到 hidden_proc 的串列中。反之;unhide_process 就是自 hidden_proc 串列中刪除該 node。 ```c static int hide_process(pid_t pid) { pid_node_t *proc = kmalloc(sizeof(pid_node_t), GFP_KERNEL); proc->id = pid; list_add_tail(&proc->list_node, &hidden_proc); return SUCCESS; } static int unhide_process(pid_t pid) { pid_node_t *proc, *tmp_proc; list_for_each_entry_safe (proc, tmp_proc, &hidden_proc, list_node) { if (proc->id == pid) { list_del(&proc->list_node); kfree(proc); } } return SUCCESS; } ``` 此外為什麼選擇 find_ge_pid?由於 ps 是從 /proc 目錄下得到目前系統上所有 process 的資訊,而系統會幫每個 process 在 /proc 目錄下建立名為 PID 的目錄,這裏的 PID 也就是用 pidof 得到的數字。 在 Linux kernel 的 fs/proc/base.c 中可以看到讀取 /proc 目錄的函數是 ```c /* for the /proc/ directory itself, after non-process stuff has been done */ int proc_pid_readdir(struct file *file, struct dir_context *ctx) ... ``` 在此函數中會有對其下的每個目錄做處理 ```c ... for (iter = next_tgid(ns, iter); iter.task; iter.tgid += 1, iter = next_tgid(ns, iter)) { ... ``` 而 next_tgid 又會利用 find_ge_pid 來搜尋特定的 PID。這裏的 find_ge_pid 也就是測驗程式要 hook 的對象。 ```c static struct tgid_iter next_tgid(struct pid_namespace *ns, struct tgid_iter iter) { struct pid *pid; if (iter.task) put_task_struct(iter.task); rcu_read_lock(); retry: iter.task = NULL; pid = find_ge_pid(iter.tgid, ns) ... ``` ## 延伸問題 - 2:本程式僅在 Linux v5.4 測試,若你用的核心較新,請試著找出替代方案 ## 延伸問題 - 3:本核心模組只能隱藏單一 PID,請擴充為允許其 PPID 也跟著隱藏,或允許給定一組 PID 列表,而非僅有單一 PID ### **允許其 PPID 也跟著隱藏**: ```c static int hide_process(pid_t pid) { pid_node_t *proc; if (! is_hidden_proc(pid)) { proc = kmalloc(sizeof(pid_node_t), GFP_KERNEL); proc->id = pid; list_add_tail(&proc->list_node, &hidden_proc); } return SUCCESS; } static int hide_parent_process(pid_t pid) { struct task_struct *p; p = pid_task(find_vpid(pid), PIDTYPE_PID); if(p && p->parent) { hide_process(p->parent->pid); } return SUCCESS; } static int unhide_process(pid_t pid) { pid_node_t *proc, *tmp_proc; list_for_each_entry_safe (proc, tmp_proc, &hidden_proc, list_node) { if (proc->id == pid) { list_del(&proc->list_node); kfree(proc); } } return SUCCESS; } static int unhide_parent_process(pid_t pid) { struct task_struct *p; p = pid_task(find_vpid(pid), PIDTYPE_PID); if(p && p->parent) { unhide_process(p->parent->pid); } return SUCCESS; } 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); hide_process(pid); hide_parent_process(pid); } else if (!memcmp(message, del_message, sizeof(del_message) - 1)) { kstrtol(message + sizeof(del_message), 10, &pid); unhide_process(pid); unhide_parent_process(pid); } else { kfree(message); return -EAGAIN; } *offset = len; kfree(message); return len; } ``` ### **給定一組 PID 列表**隱藏 這裏是利用 Linux kernel API 中的 strsep 將輸入的字串分離開,再照原流程進行: ```c static ssize_t device_write(struct file *filep, const char *buffer, size_t len, loff_t *offset) { long pid; char *message; char *token, *cur; int (*do_action)(pid_t pid); 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)) { do_action = hide_process; } else if (!memcmp(message, del_message, sizeof(del_message) - 1)) { do_action = unhide_process; } else { kfree(message); return -EAGAIN; } cur = message + sizeof(add_message); while ((token = strsep(&cur, " "))) { kstrtol(token, 10, &pid); do_action(pid); } *offset = len; kfree(message); return len; } ``` ## 延伸問題 - 4:指出程式碼可改進的地方,並動手實作 有關處理程式結束時記憶體管理的部份,以觀察程式在 _hideproc_init 時的呼叫順序找出其對應的呼叫: * alloc_chrdev_region unregister_chrdev_region * class_create class destory * cdev_add cdev_del * device_create device_destory * register_ftrace_function unregister_ftrace_function 另外在 hidden_proc 串列中正在使用的記憶體也需要釋放。 ```c static void _hideproc_exit(void) { pid_node_t *proc, *tmp_proc; printk(KERN_INFO "@ %s\n", __func__); list_for_each_entry_safe (proc, tmp_proc, &hidden_proc, list_node) { list_del(&proc->list_node); kfree(proc); } unregister_ftrace_function(&hook.ops); cdev_del(&cdev); device_destroy(hideproc_class, MKDEV(MAJOR(cdev.dev), MINOR_VERSION)); class_destroy(hideproc_class); unregister_chrdev_region(MKDEV(MAJOR(cdev.dev), MINOR_VERSION), MINOR_VERSION); } ``` ## 參考 * [The Linux Kernel Module Programming Guide](https://github.com/sysprog21/lkmpg) * [The Linux Kernel API](https://www.kernel.org/doc/htmldocs/kernel-api/index.html) * [Ftrace]( https://www.kernel.org/doc/Documentation/trace/ftrace.txt)