# 2021q3 Homework1 (quiz1) contributed by < `ChinYikMing` > ###### tags: `Linux 作業系統` > [GitHub](https://github.com/ChinYikMing/Linux-2021-summer/blob/main/main.c) ## 此Bash Script簡化了新增和刪除 Note: 使用此 script 之前,必須先 insmod module_name ```shell #!/bin/bash usage() { echo "$0 add|del PID[,...]" echo "add for adding PID[,...] to list to hide" echo "del for deleting PID[,...] from list to show" echo "Note:" echo " del -1 will delete all PID from list to show" } dev="/dev/hideproc" re='^(-?[0-9]+,?)*$' if [ "$#" -ne 2 ] || [[ "$1" != "add" && "$1" != "del" ]] || ! [[ $2 =~ $re ]]; then usage else echo "$1 $2" | sudo tee "$dev" fi ``` ## 延伸問題 ### 1.0 解釋程式碼運作原理 目的 :隱藏特定的 PID 流程 : 1. 掛載核心模組時,建立 device file(`/dev/hideproc`) 且初始化 ftrace hook。ftrace hook可以註冊我們自定義的 function 來 hook 原本的 function。在這裡,我們是 hook find_ge_pid。get_ge_pid 均被 ps(1), pidof(1), top(1), pstree(1), pgrep(1)等其他查詢 process 狀態的 cmd 使用,所以 hook 這個 function,就可以達到以上的目的。使用 ftrace 來 hook get_ge_pid 的細節會在下面 1.1 討論。 2. 當我們使用 `add pid` 或 `del pid` 命令和 device file( /dev/hideproc ) 互動的時候,能夠分別顯示或隱藏特定的 PID。例如`echo "add 1" | sudo tee /dev/hideproc` 就會隱藏 process id 為 1 的 process; `echo "del 1" | sudo tee /dev/hideproc` 就會顯示 process id 為 1 的 process。實作方法是使用 Linux kernel API 的 List Management Functions 維護一個 doubly circular linked list。`add pid` 會增加這個 linked list 的節點;`del pid` 會從這個 linked list 刪除節點。由此可見,這個 linked list 的所有節點是要被隱藏的process id 3. 在 rmmod module 的時候,只需要把原本註冊的 ftrace hook 釋放掉,即可恢復 ps(1), pidof(1), top(1), pstree(1), pgrep(1)等其他查詢 process 狀態的 cmd 使用回 `find_ge_pid` ### 1.1 使用 ftrace 來 hook find_ge_pid ```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); if (!within_module(parent_ip, THIS_MODULE)) regs->ip = (unsigned long) hook->func; } static int hook_install(struct ftrace_hook *hook) { int err = hook_resolve_addr(hook); if (err) return err; hook->ops.func = hook_ftrace_thunk; hook->ops.flags = FTRACE_OPS_FL_SAVE_REGS | FTRACE_OPS_FL_RECURSION_SAFE | FTRACE_OPS_FL_IPMODIFY; err = ftrace_set_filter_ip(&hook->ops, hook->address, 0, 0); if (err) { printk("ftrace_set_filter_ip() failed: %d\n", err); return err; } err = register_ftrace_function(&hook->ops); if (err) { printk("register_ftrace_function() failed: %d\n", err); ftrace_set_filter_ip(&hook->ops, hook->address, 1, 0); return err; } return 0; } ``` * 在 hook_install 的時候,要使用的 callback function 是 hook_ftrace_thunk,這個 callback function 會在 process 每次使用 `get_ge_pid` 的時候把暫存器的 ip 改成 hook 的 func(`hook_find_ge_pid`),進而攔截 `get_ge_pid` * 要能夠修改暫存器的 ip,struct `ftrace_ops` 的 flags 必須有 `FTRACE_OPS_FL_IPMODIFY`,要使用 `FTRACE_OPS_FL_IPMODIFY` flag,還必須有 `FTRACE_OPS_FL_SAVE_REGS` flag,否則 callback function 的 regs 會是 garbage value 或 NULL * struct `ftrace_ops` 初始化之後,就可以使用 `ftrace_set_filter_ip` 指定我們要追蹤的函式(這裡是 `get_ge_pid`),然後使用 `register_ftrace_function` 註冊 hook 參考: [Using ftrace to hook to functions](https://www.kernel.org/doc/html/latest/trace/ftrace-uses.html) Note:在查資料的時候,有看到類似 ftrace 的 cmd 工具:trace-cmd * trace PID: `sudo trace-cmd record -p function -P PID` * trace PID(比較美的輸出格式): `sudo trace-cmd record -p function_graph -P PID` * 查看 report: `sudo trace-cmd report` * 參考: [ftrace: trace your kernel functions!](https://jvns.ca/blog/2017/03/19/getting-started-with-ftrace/) ### 2 本核心模組只能隱藏單一 PID,請擴充為允許其 PPID 也跟著隱藏,或允許給定一組 PID 列表,而非僅有單一 PID - 允許給定一組 PID 列表跟著隱藏的 kernel module Note: PID 和 PID 之間要用 ,隔開。e.g., `echo "add 1,2,3" | sudo tee /dev/hideproc` 會隱藏 PID 為 1, 2 和 3 的 process 或者可以使用上面一開始提供的 bash script: ./scriptname add 1,2,3 ```cpp typedef int (*hide_handler)(pid_t); void device_write_handler(char *message, size_t message_len, size_t cmd_len, hide_handler handler) { long pid; char *ptr, *qtr, *end; ptr = message + cmd_len; /* skip cmd */ end = message + message_len; while(1){ qtr = memchr(ptr, ',', end - ptr); if(!qtr){ kstrtol(ptr, 10, &pid); handler(pid); break; } *qtr = '\0'; kstrtol(ptr, 10, &pid); handler(pid); ptr += (qtr - ptr + 1); } } static ssize_t device_write(struct file *filep, const char *buffer, size_t len, loff_t *offset) { char *message; char add_message[] = "add", del_message[] = "del"; if (len < sizeof(add_message) - 1 && len < sizeof(del_message) - 1) goto err; /* guaratees message contains PID */ if(len >= 5){ 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)) { device_write_handler(message, len, sizeof(add_message), hide_process); } else if (!memcmp(message, del_message, sizeof(del_message) - 1)) { device_write_handler(message, len, sizeof(del_message), unhide_process); } else { kfree(message); goto err; } *offset = len; kfree(message); return len; } err: return -EAGAIN; } ``` - 允許 PID 的 PPID 跟著隱藏的 kernel module 把以上例子的 device_write_handler 改成以下即可` `Note: 刪除 PID 的時候,PPID 也會跟著刪除, 若 PID 為 -1 會刪除所有 PID ```cpp void device_write_handler(char *message, char *cmd, hide_handler handler) { struct task_struct *tsk; pid_t ppid; long pid; char *ptr, *qtr, *end; ptr = message + strlen(cmd) + 1; /* skip cmd */ end = message + strlen(message); while(1){ qtr = memchr(ptr, ',', end - ptr); if(!qtr){ kstrtol(ptr, 10, &pid); tsk = pid_task(find_vpid(pid), PIDTYPE_PID); if(!tsk){ if(!memcmp(cmd, "del", 3) && pid == -1) /* remove all hideproc */ handler(pid); break; } if(tsk->parent){ ppid = tsk->parent->pid; handler(ppid); } handler(pid); break; } *qtr = '\0'; kstrtol(ptr, 10, &pid); tsk = pid_task(find_vpid(pid), PIDTYPE_PID); if(!tsk) goto next; if(tsk->parent){ ppid = tsk->parent->pid; handler(ppid); } handler(pid); next: ptr += (qtr - ptr + 1); } } ``` 參考:<s>https://www.programmersought.com/article/8956801558/</s> :::warning 不要參照 programmersought 網站,其內容不完整。儘量找第一手材料 :notes: jserv ::: ### 3 指出程式碼可改進的地方,並動手實作 * `unhide_process` 函式在刪除 PID 的時候會把所有 PID 刪除,我認為可以提供多一點彈性,例如根據給定的 PID 或 一組 PID 來刪除 PID 以及若 PID 為 -1 代表要刪除所有 PID。此外,刪除 PID 的時候,應該要先檢查資料結構(以下以 linked list 為例)是否為空,若是空的就不必再去走訪 ```cpp static int unhide_process(pid_t pid) { pid_node_t *proc, *tmp_proc; if(hash_empty(hidden_proc)){ pr_info("hash table is empty\n"); return -ENOENT; } if(pid == -1){ list_for_each_entry_safe (proc, tmp_proc, &hidden_proc, list_node) { list_del(&proc->list_node); kfree(proc); } } else { 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; } ``` * 在 rmmod module 的時候,應該要釋放對應的資源 ```cpp static void _hideproc_exit(void) { printk(KERN_INFO "@ %s\n", __func__); /* FIXME: ensure the release of all allocated resources */ hook_remove(&hook); device_destroy(hideproc_class, MKDEV(dev_major, MINOR_VERSION)); cdev_del(&cdev); class_destroy(hideproc_class); } ``` * add PID,PID 可能不是合法的 PID,所以應該要去檢查 PID 是否合法再加到 linked list。(已在 device_write_handler 實作中) 範例請參考:https://github.com/ChinYikMing/Linux-2021-summer/blob/main/main.c * PPID 可能是很多個 process 共享的,所以 PPID 在資料結構可能會有重複,所以應該要去檢查 PPID 是否已經存在資料結構內,解決方法是修改 `hide_process` function 即可 ```cpp static int hide_process(pid_t pid) { ... if(is_hidden_proc(pid)){ pr_info("PID=%d is already hidden\n", pid); return -EINVAL; } ... } ``` * linked list的搜尋時間複雜度可能是 `O(n)`,所以把 linked list 改成 hash table 可能可以加快搜尋的時間 `O(1)`,但要在量大的時候才有明顯差別 主要修改程式碼有: * `pid_node_t` structure * hash table initialization * `is_hidden_proc` function * `hide_process` function * `unhide_process` function * `device_read` function 範例請參考: [main.c](https://github.com/ChinYikMing/Linux-2021-summer/blob/main/main.c) hash table API參考:https://github.com/torvalds/linux/blob/5bfc75d92efd494db37f5c4c173d3639d4772966/include/linux/hashtable.h