# 2021q3 Homework1 (quiz1) contributed by < `ccs100203` > ###### tags: `linux2021q3` > [quiz1](https://hackmd.io/@sysprog/linux2021-summer-quiz1) ## 解釋程式碼運作原理 > reference: [Character device drivers](https://linux-kernel-labs.github.io/refs/heads/master/labs/device_drivers.html) ### How device_write & list of hide_process work #### _hideproc_init > reference: [class_create](https://elixir.bootlin.com/linux/latest/source/include/linux/device/class.h#L273), [alloc_chrdev_region](https://www.kernel.org/doc/htmldocs/kernel-api/API-alloc-chrdev-region.html), [cdev_init](https://www.kernel.org/doc/htmldocs/kernel-api/API-cdev-init.html), [MKDEV](https://elixir.bootlin.com/linux/v5.4.134/source/include/uapi/linux/kdev_t.h) ```cpp static int _hideproc_init(void) { int err, dev_major; dev_t dev; printk(KERN_INFO "@ %s\n", __func__); err = alloc_chrdev_region(&dev, 0, MINOR_VERSION, DEVICE_NAME); dev_major = MAJOR(dev); hideproc_class = class_create(THIS_MODULE, DEVICE_NAME); cdev_init(&cdev, &fops); cdev_add(&cdev, MKDEV(dev_major, MINOR_VERSION), 1); device_create(hideproc_class, NULL, MKDEV(dev_major, MINOR_VERSION), NULL, DEVICE_NAME); init_hook(); return 0; } ``` Declaring and registering a character device, and initialize `hook`. Most function definitions are put in references。 ```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); hide_process(pid); } 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; } *offset = len; kfree(message); return len; } ``` - 當我們使用 `echo "add 1093" | sudo tee /dev/hideproc`就是在找 device_write。 - 利用 `copy_from_user` 將訊息讀入,藉 [memcmp](https://man7.org/linux/man-pages/man3/memcmp.3.html) 判斷是否為 add / del,以及 [kstrtol](https://www.kernel.org/doc/htmldocs/kernel-api/API-kstrtol.html) 將數字轉換為 long 存入 `pid`。 - 如果是 add 的話就要呼叫 `hide_process` 反之則是 `unhide_process`。 ```cpp 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); // CCC 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) { //BBB list_del(&proc->list_node); // DDD kfree(proc); } return SUCCESS; } ``` 由於我們維護的列表 `hidden_proc`,是用來儲存要被隱藏的 process,所以在 hide 時要將其加入 list 中,反之則移除。 ### Ftrace Hook > reference: [Using ftrace to hook to functions](https://www.kernel.org/doc/html/v4.17/trace/ftrace-uses.html) #### Struct ```cpp struct ftrace_hook { const char *name; void *func, *orig; unsigned long address; struct ftrace_ops ops; }; ``` 自訂 ftrace hook,並將需要的 ftrace_ops 包在裡面 - name: 原函式的名字 - orig: 保留原函式 - func: 取代原函式,並對其 hook - address: 保存原函式位置,並在後面用來做 ip_filter ```cpp struct ftrace_ops ops = { .func = my_callback_func, .flags = MY_FTRACE_FLAGS .private = any_private_data_structure, }; ``` 這邊的 `func` 是用來存放 callback 時所呼叫的函式。 #### Functions ```cpp static void init_hook(void) { real_find_ge_pid = (find_ge_pid_func) kallsyms_lookup_name("find_ge_pid"); hook.name = "find_ge_pid"; hook.func = hook_find_ge_pid; hook.orig = &real_find_ge_pid; hook_install(&hook); } ``` - 利用 [kallsyms_lookup_name](https://elixir.bootlin.com/linux/latest/source/include/linux/kallsyms.h#L80) 去取得 find_ge_pid 的 address,保存起來後進行後續 hook 的動作 - 而 [find_ge_pid](https://elixir.bootlin.com/linux/latest/source/include/linux/pid.h#L132) 是用來在 hash table 中尋找 pid 的函式 ```cpp static bool is_hidden_proc(pid_t pid) { pid_node_t *proc, *tmp_proc; list_for_each_entry_safe (proc, tmp_proc, &hidden_proc, list_node) { // AAA if (proc->id == pid) return true; } return false; } 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; } ``` `hook_find_ge_pid` 除了原本 `find_ge_pid` 的功能之外,還會檢查該 pid 是不是在 `hidden_proc` 內,如若是的話,就回傳 pid + 1,使其找不到原本的 pid。 ==尚未驗證 pid+1 運作原理== TODO ```cpp 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_resolve_addr: 會再一次的將 `hook->address` 放入 `hook->orig`,但前面已經做過了,不確定原因 - ftrace_set_filter_ip: 讓 callback 只會在特定的 functions 上執行 - register_ftrace_function: enable tracing call - FTRACE_FLAGS - FTRACE_OPS_FL_SAVE_REGS >If the callback requires reading or modifying the pt_regs passed to the callback, then it must set this flag. 如果需要更改或讀取 pt_regs 的話,就需要 set - FTRACE_OPS_FL_RECURSION_SAFE > By default, a wrapper is added around the callback to make sure that recursion of the function does not occur. That is, if a function that is called as a result of the callback’s execution is also traced, ftrace will prevent the callback from being called again. But this wrapper adds some overhead, and if the callback is safe from recursion, it can set this flag to disable the ftrace protection. Note, if this flag is set, and recursion does occur, it could cause the system to crash, and possibly reboot via a triple fault. 原先是不會讓該 callback 被遞迴呼叫,但如果設立了這個 flag,那 ftrace 不會對他進行 recursion call 的保護,要注意開啟後程式沒寫好會導致當機。 - FTRACE_OPS_FL_IPMODIFY > Requires FTRACE_OPS_FL_SAVE_REGS set. If the callback is to “hijack” the traced function (have another function called instead of the traced function), it requires setting this flag. This is what live kernel patches uses. Without this flag the pt_regs->ip can not be modified. 如果要更改 pt_regs->ip,那就必須設立該 flag ```cpp static int hook_resolve_addr(struct ftrace_hook *hook) { hook->address = kallsyms_lookup_name(hook->name); if (!hook->address) { printk("unresolved symbol: %s\n", hook->name); return -ENOENT; } *((unsigned long *) hook->orig) = hook->address; return 0; } ``` 利用 `kallsyms_lookup_name` 得出 `find_ge_pid` 的位置,並放入 `address` 以及 `orig` 中。 ```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; } ``` - `hook_ftrace_thunk` 為 ftrace 內放入的 callback function。 而 ip 為 instruction pointer,又可以說是 program counter - 因為在這裡只能得到 callback function,所以利用 `container_of` 去得到整個 `struct ftrace_hook` 的結構,這也是為什麼要特地寫一個 struct 將 `struct ftrace_ops` 包裝起來,就是為了能在這邊拿到一開始放入 `hook->func` 內的 `hook_find_ge_pid`,這樣就可以用他取代掉 `regs->ip`,已達到隱藏 pid 的效果。 - `regs->ip` 的話,同樣在 [The callback function](https://www.kernel.org/doc/html/v4.17/trace/ftrace-uses.html#the-callback-function) 有解釋 > this will be pointing to the pt_regs structure like it would be if an breakpoint was placed at the start of the function where ftrace was tracing. 因為我們上面將 `FTRACE_OPS_FL_SAVE_REGS` set,所以這邊會是一個 `struct pt_regs` 的指標,並像一個 breakpoint 放在被 trace 的 function 的一開始。 所以用 `hook_find_ge_pid` 去取代掉原先的 ip,已達到取代的作用。 - `within_module` 的使用則對應到 flag 將 `FTRACE_OPS_FL_RECURSION_SAFE` set,用來檢查 parent_ip 是否已經為當前的 module,避免其取代成功後還一直在遞迴呼叫。 ## 若核心版本較新,請試著找出替代方案 [Unexporting kallsyms_lookup_name()](https://lwn.net/Articles/813350/) [Access to kallsyms on Linux 5.7+](https://github.com/h33p/kallsyms-mod) TODO ## 擴充為允許隱藏 PPID,以及能隱藏一組 PID 列表 應該是不用一個一個輸入 PID 的意思? 像是 Chrome 一次可能有幾十個 processor 在運行。 [struct pid](https://elixir.bootlin.com/linux/latest/source/include/linux/pid.h) TODO ## Bug Fix ### Resources Release 因為資源沒釋放,重新 insmod, rmmod 就會當機,差點以為電腦要掰了,所以優先處理... ```cpp static void _hideproc_exit(void) { printk(KERN_INFO "@ %s\n", __func__); /* FIXME: ensure the release of all allocated resources */ 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); } hook_remove(&hook); device_destroy(hideproc_class, cdev.dev); cdev_del(&cdev); class_destroy(hideproc_class); } ``` 基本上就是照著 `_hideproc_init` 反過來釋放,先把 list `hidden_proc` 清空,然後移除 hook,接著依序處理掉 device, cdev, class。 遇到的小問題是該去哪裡找 `dev`,後來查閱 [cdev.h](https://elixir.bootlin.com/linux/latest/source/include/linux/cdev.h) 找到他在 struct cdev 內。 ### Purpose of hook_resolve_addr 貌似做了重複的事情,且 `Orig` 好像沒有功用,還未釐清原因。