--- ###### tags: `sysprog2021q1` --- # 2021q3 Homework1 (quiz1) contributed by <`93i7xo2`> > Source: [quiz1](https://hackmd.io/@sysprog/linux2021-summer-quiz1) ## 解釋上述程式碼運作原理,包含 ftrace 的使用 ftrace 的功用是在目標函式前附加上 callback 來追蹤在 kernel 內的流程,使用這項功能必須用 `struct ftrace_ops` 來告知 ftrace 何者為 callback。 ```cpp struct ftrace_hook { ... struct ftrace_ops ops; }; ``` 啟用/停用方法如下: ```cpp register_ftrace_function(&ops); unregister_ftrace_function(&ops); ``` 以 `ftrace_set_filter()` 對特定名稱的函式進行 hook,若函式名稱重複可以指定 `ip` 的方式進行,`hook_install()` 即採用後者: > [ftrace.c](https://elixir.bootlin.com/linux/latest/source/kernel/trace/ftrace.c#L5401) ```cpp /** * ftrace_set_filter_ip - set a function to filter on in ftrace by address * @ops - the ops to set the filter with * @ip - the address to add to or remove from the filter. * @remove - non zero to remove the ip from the filter * @reset - non zero to reset all filters before applying this filter. * * Filters denote which functions should be enabled when tracing is enabled * If @ip is NULL, it fails to update filter. */ int ftrace_set_filter_ip(struct ftrace_ops *ops, unsigned long ip, int remove, int reset) ``` ```cpp ret = ftrace_set_filter(&ops, "schedule", strlen("schedule"), 0); ret = ftrace_set_filter_ip(&ops, ip, 0, 0); ``` 在 callback 換掉 `regs->ip` 達到劫持的作用: ```cpp static void notrace hook_ftrace_thunk(unsigned long ip, unsigned long parent_ip, struct ftrace_ops *ops, struct pt_regs *regs) { ... regs->ip = (unsigned long)hook->func; } ``` ### `hook_install()` 行為 1. 以 `kallsyms_lookup_name` 找尋名稱為 `find_ge_pid` 的 address 2. 設定 func & flags,呼叫 ftrace API 進行 hook :::warning Problem: 1. `hook_find_ge_pid` 行為不明,明明有簡單的 `find_pid_ns` 可用。 ```cpp static struct pid *hook_find_ge_pid(int nr, struct pid_namespace *ns) { struct pid *pid = real_find_ge_pid(nr, ns); while (pid && is_hidden_proc(pid->numbers->nr)) pid = real_find_ge_pid(pid->numbers->nr + 1, ns); return pid; } ``` 2. 使用`hook.func = hook_find_ge_pid;` 而非 `hook.func = &hook_find_ge_pid;` ::: :::info Reference - [Using ftrace to hook to functions](https://www.kernel.org/doc/html/latest/trace/ftrace-uses.html) - [The Linux Kernel API](https://www.kernel.org/doc/html/latest/core-api/kernel-api.html) - [LABS - Character device drivers](https://linux-kernel-labs.github.io/refs/heads/master/labs/device_drivers.html) ::: ## 本程式僅在 Linux v5.4 測試,若你用的核心較新,請試著找出替代方案 ``` $ uname -r 5.4.0-80-generic ``` ## 本核心模組只能隱藏單一 PID,請擴充為允許其 PPID 也跟著隱藏,或允許給定一組 PID 列表,而非僅有單一 PID #### 隱藏 ppid To-do #### 多重 pid 輸入 在 `device_write()` 內以 `strsep` 分離字串(核心內無法使用 `strtok`),達到多個 pid 同時輸入的可能,最後去除輸入時重複 pid 的 entry。 ```cpp static ssize_t device_write(struct file *filep, const char *buffer, size_t len, loff_t *offset) { ... while ((found = strsep(&start, " ")) != NULL) { if ((ret = kstrtol(found, 10, &pid)) == 0) { switch (input_mode) { case _ADD: hide_process(pid); break; case _DEL: unhide_process(pid); break; } } } /* remove duplicate entries */ list_sort(priv, &hidden_proc, &list_cmp); list_for_each_entry_safe(proc, tmp_proc, &hidden_proc, list_node) { if (&tmp_proc->list_node != (&hidden_proc) && tmp_proc->id == proc->id) { list_del(&proc->list_node); kfree(proc); } } ... } ``` 假設 `pid = {1, 2, 7, 8, 6}` 的 process 存在,批次隱藏/復原 pid 的步驟如下: ```bash $ sudo insmod hideproc.ko $ echo "add 8 7 6" | sudo tee /dev/hideproc add 8 7 6 $ echo "del 1 2 8" | sudo tee /dev/hideproc del 1 2 8 $ sudo cat /dev/hideproc pid 6 pid 7 $ echo "del 6" | sudo tee /dev/hideproc del 6 $ sudo cat /dev/hideproc pid 7 $ sudo rmmod hideproc ``` ## 指出程式碼可改進的地方,並動手實作 #### a. module init/exit 適當的初始化及釋放資源 ```cpp static int _hideproc_init(void) { int dev_major; printk(KERN_INFO "@ %s\n", __func__); if (alloc_chrdev_region(&dev, 0, MINOR_VERSION, DEVICE_NAME) < 0) { return -1; } dev_major = MAJOR(dev); cdev_init(&cdev, &fops); if (cdev_add(&cdev, MKDEV(dev_major, MINOR_VERSION), 1) == -1) { unregister_chrdev_region(dev, 1); return -1; } if (IS_ERR(hideproc_class = class_create(THIS_MODULE, DEVICE_NAME))) { cdev_del(&cdev); unregister_chrdev_region(dev, 1); return -1; } if (IS_ERR(device_create(hideproc_class, NULL, MKDEV(dev_major, MINOR_VERSION), NULL, DEVICE_NAME))) { class_destroy(hideproc_class); cdev_del(&cdev); unregister_chrdev_region(dev, 1); return -1; } init_hook(); return 0; } ``` ```cpp static void _hideproc_exit(void) { hook_remove(&hook); device_destroy(hideproc_class, MKDEV(MAJOR(dev), MINOR_VERSION)); class_destroy(hideproc_class); cdev_del(&cdev); unregister_chrdev_region(dev, 1); printk(KERN_INFO "@ %s\n", __func__); } ``` 卸載模組前告知 ftrace 並釋放 list 上所有元素 ```cpp void hook_remove(struct ftrace_hook *hook) { pid_node_t *proc, *tmp_proc; list_for_each_entry_safe(proc, tmp_proc, &hidden_proc, list_node) { list_del(&proc->list_node); kfree(proc); } int err = unregister_ftrace_function(&hook->ops); if (err) printk("unregister_ftrace_function() failed: %d\n", err); err = ftrace_set_filter_ip(&hook->ops, hook->address, 1, 0); if (err) printk("ftrace_set_filter_ip() failed: %d\n", err); } ```