# 2021q3 Homework1 (quiz1) contributed by < [`Hao-yu-lin`](https://github.com/Hao-yu-lin/linux2021.git) > ###### tags: `linux2021` --- [2021 年暑期 Linux 核心 第 1 週測驗題](https://hackmd.io/@sysprog/linux2021-summer-quiz1) > Linux 核心版本:GNU/Linux 5.4.0-80-generic x86_64 ## 解釋上述程式碼運作原理,包含 ftrace 的使用 依照執行流程分成三部分,探討程式碼 ### Part1:Module insert 當使用下方的指令後,便會開始載入 module ``` sudo insmod hideproc.ko ``` 由 `/include/linux/module.h` 內容得知 ```c module_init(_hideproc_init); module_exit(_hideproc_exit); ``` 為程式的進入點 > module_init():driver initialization entry point > module_exit():driver exit entry point 觀察`_hideproc_init` 得知,此程式主要在建立 hideproc 裝置環境,建立後執行`init_hook()` 建立 hook function ```c static void init_hook(void) { real_find_ge_pid = (find_ge_pid_func) kallsyms_lookup_name("find_ge_pid"); ... hook_install(&hook); } ``` `hook_install` 此處做的事情是初始化 `hook_ops` ,主要目的是將 hook 內的 func 成員指向 `hook_ftrace_thunk` ```c static int hook_install(struct ftrace_hook *hook) { // 取得 Address int err = hook_resolve_addr(hook); ... // 將 ops 取代為 hook_ftrace_thunk 為 ftrace 再進入 kernel 內時,真正執行 callback 的 func hook->ops.func = hook_ftrace_thunk; hook->ops.flags = FTRACE_OPS_FL_SAVE_REGS | FTRACE_OPS_FL_RECURSION_SAFE | FTRACE_OPS_FL_IPMODIFY; // 使用 ftrace_set_filter_ip 建立一個 fliter 來篩選出需要的函數後才執行 hook err = ftrace_set_filter_ip(&hook->ops, hook->address, 0, 0); ... // 使用 register_ftrace_function 允許 ftrace 調用 hook_ftrace_thunk err = register_ftrace_function(&hook->ops); ... return 0; } ``` `hook_resolve_addr` 在此處的目的就是找到 `find_ge_pid` 的函數 address 並保存在 `hook->orig` 中 ```c static int hook_resolve_addr(struct ftrace_hook *hook) { hook->address = kallsyms_lookup_name(hook->name); ... *((unsigned long *) hook->orig) = hook->address; return 0; } ``` 接續探討 `hook_ftrace_thunk`,將 自定義的 `hook_find_ge_pid` 透過更改 %rip 的下一步指令,強制跳轉到 hook function ```c static void notrace hook_ftrace_thunk(unsigned long ip, unsigned long parent_ip, struct ftrace_ops *ops, struct pt_regs *regs) { // 利用 container_of 來獲得 原始 hook 的 address struct ftrace_hook *hook = container_of(ops, struct ftrace_hook, ops); // 避免重複調用 if (!within_module(parent_ip, THIS_MODULE)) regs->ip = (unsigned long) hook->func; } ``` ### Part2:(Un)hide Process 當使用下方指令後,device 便會開始執行 `device_write` 指令 `device_write` 會根據你輸入在 buffer 內的指令,來執行 `hide_process` 或是 `unhide_process` ``` echo "add 644" | sudo tee /dev/hideproc echo "del 644" | sudo tee /dev/hideproc ``` 建立一個 proc,設定 id 為 PID,並將此 proc 連接到 `hidden_proc` list 串的最尾巴 ```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; } ``` ### Part3:Show PID 當使用下方指令後 ``` pidof cron ``` 由於 callback function 作用會導向執行 `hook_find_ge_pid`,透過 `is_hidden_proc` 來遍歷 `hidden_proc` 若有符合的 pid,就會 pid ++ 直到不相符的 pid,此時就會因為找不到相符的 pid 而輸出找不到 ```c 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; } ``` ## 本核心模組只能隱藏單一 PID,請擴充為允許其 PPID 也跟著隱藏,或允許給定一組 PID 列表,而非僅有單一 PID 透過 `find_get_pid` 取得 child 的 pid struct,藉由 `get_pid_task` 取得 task struct,再利用 `real_parent`,來得到 parent 的 pid 即可 ```c #include <linux/sched.h> static pid_t get_ppid(long cpid) { struct task_struct *child = NULL; struct pid *child_pid = NULL; struct task_struct *parent = NULL; child_pid = find_get_pid(cpid); child = get_pid_task(child_pid, PIDTYPE_PID); parent = child -> real_parent; return parent -> pid; } ``` ```c static ssize_t device_write(struct file *filep, const char *buffer, size_t len, loff_t *offset) { ... if (!memcmp(message, add_message, sizeof(add_message) - 1)) { kstrtol(message + sizeof(add_message), 10, &pid); hide_process(pid); ppid = get_ppid(pid); hide_process(ppid); } else if (!memcmp(message, del_message, sizeof(del_message) - 1)) { kstrtol(message + sizeof(del_message), 10, &pid); unhide_process(pid); ppid = get_ppid(pid); unhide_process(ppid); } else { kfree(message); return -EAGAIN; } *offset = len; kfree(message); return len; } ``` 以下是原始尚未新增同時刪除 PPID 時的操作 ``` $ pidof cron # 輸出 590 $ pstree -s -p 590 # 輸出 systemd(1)───cron(590) 得知 ppid = 1 $ echo "add 590" | sudo tee /dev/hideproc $ pstree -s -p 590 # 發現 pid:590 被忽略找不到了 systemd(1)─┬─accounts-daemon(586)─┬─{accounts-daemon}(594) │ └─{accounts-daemon}(618) ├─atd(612) ├─dbus-daemon(592) ├─irqbalance(598)───{irqbalance}(635) ├─login(629)───bash(1187) ├─multipathd(475)─┬─{multipathd}(476) │ ├─{multipathd}(477) │ ├─{multipathd}(478) │ ├─{multipathd}(479) │ ├─{multipathd}(480) │ └─{multipathd}(481) ...... 省略 $ echo "add 1" | sudo tee /dev/hideproc $ pstree -s -p 590 # 此時(1) 這地方直接變成 ? ?(1)─┬─accounts-daemon(586)─┬─{accounts-daemon}(594) │ └─{accounts-daemon}(618) ├─atd(612) ├─dbus-daemon(592) ├─irqbalance(598)───{irqbalance}(635) ├─login(629)───bash(1187) ├─multipathd(475)─┬─{multipathd}(476) │ ├─{multipathd}(477) │ ├─{multipathd}(478) │ ├─{multipathd}(479) │ ├─{multipathd}(480) │ └─{multipathd}(481) ├─networkd-dispat(600) ├─polkitd(647)─┬─{polkitd}(648) │ └─{polkitd}(650) ├─sh(1328)───node(1335)─┬─node(1385)─┬─bash(1437)───pstree(7285) │ │ ├─{node}(1386) │ │ ├─{node}(1387) │ │ ├─{node}(1388) │ │ ├─{node}(1389) $ echo "del 1" | sudo tee /dev/hideproc $ echo "del 590" | sudo tee /dev/hideproc $ pstree -s -p 590 # 輸出 systemd(1)───cron(590) 變正常了 ``` 若新增同時刪除 PPID >> 因為要重新編譯 code 此時發現 linux 當機了,此問題留到下一題解決 ``` $ pidof cron # 輸出 584 $ pstree -s -p 584 # 輸出 systemd(1)───cron(584) 得知 ppid = 1 $ echo "add 584" | sudo tee /dev/hideproc $ pstree -s -p 584 # ppid 成功消失了 ?(1)─┬─accounts-daemon(581)─┬─{accounts-daemon}(586) │ └─{accounts-daemon}(615) ├─atd(605) ├─dbus-daemon(587) ├─irqbalance(593)───{irqbalance}(630) ├─login(613)───bash(888) ├─multipathd(472)─┬─{multipathd}(473) │ ├─{multipathd}(474) │ ├─{multipathd}(475) │ ├─{multipathd}(476) │ ├─{multipathd}(477) │ └─{multipathd}(478) ``` ## 指出程式碼可改進的地方,並動手實作 每當我 sudo rmmod hideproc,linux 就會直接當機 ### \_hideproc_exit() 沒有釋放相關資源 首先要把 `MKDEV(dev_major, MINOR_VERSION)`,不然一直輸入看起來很不簡潔,首先釋放 hook 資源,利用已經寫好的 `hook_remove` 來釋放,接著對照 `_hideproc_init` 反向操作的回收資源 - `device_create` / `device_destroy` - `cdev_add` / `cdev_del` - `class_create` / `class_destroy` - `alloc_chrdev_region` / `unregister_chrdev_region` ```c static void _hideproc_exit(void) { printk(KERN_INFO "@ %s\n", __func__); hook_remove(&hook); device_destroy(hideproc_class, MK_DEV); class_destroy(hideproc_class); cdev_del(&cdev); unregister_chrdev_region(MK_DEV, MINOR_VERSION); /* FIXME: ensure the release of all allocated resources */ } ``` ### unhide_process() 全部刪除 在實作嘗試刪除其他 pid,然後將其復原時發現,復原時他會一併復原,因此對 `unhide_process` 加點判斷式 ```c 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(pid == proc->id){ list_del(&proc->list_node); kfree(proc); } } return SUCCESS; } ``` ## 參考資料 [Using ftrace to hook to functions](https://www.kernel.org/doc/html/v4.17/trace/ftrace-uses.html) [ftrace來進行內核hook,part2](https://www.twblogs.net/a/5d1eed29bd9eee1e5c8374ab)