# 2021q3 Homework1 (quiz1) contributed by < [vectorr](https://github.com/vectorr) > ## 解釋上述程式碼運作原理,包含 ftrace 的使用 ```c 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); } ``` 從程式碼看,是 `find_ge_pid` 被自訂的 `hook_find_ge_pid` 所取代。 **TODO**: 了解使用 [ftrace hook function](https://www.kernel.org/doc/html/v4.17/trace/ftrace-uses.html) 的機制。 以下是推導如何使用修改 `find_ge_pid` 來隱藏 cron 的 pid 的過程: 1. 先 `strace` 觀察 `pidof cron` 使用了哪些系統呼叫。 ```clike chdir("/proc") = 0 openat(AT_FDCWD, ".", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3 fstat(3, {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0 brk(NULL) = 0x55be244e8000 brk(0x55be24509000) = 0x55be24509000 getdents64(3, /* 364 entries */, 32768) = 9568 openat(AT_FDCWD, "1/stat", O_RDONLY) = 4 fstat(4, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0 read(4, "1 (systemd) S 0 1 1 0 -1 4194560"..., 4096) = 195 read(4, "", 3072) = 0 close(4) = 0 openat(AT_FDCWD, "1/cmdline", O_RDONLY) = 4 fstat(4, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0 read(4, "/sbin/init\0splash\0", 1024) = 18 close(4) = 0 stat("/proc/1/exe", 0x7ffead440920) = -1 EACCES (Permission denied) readlink("/proc/1/exe", 0x7ffead4419d0, 4096) = -1 EACCES (Permission denied) openat(AT_FDCWD, "2/stat", O_RDONLY) = 4 fstat(4, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0 read(4, "2 (kthreadd) S 0 0 0 0 -1 212998"..., 4096) = 150 read(4, "", 3072) = 0 close(4) = 0 openat(AT_FDCWD, "2/cmdline", O_RDONLY) = 4 ``` 在進入 `/proc` ( `chdir("/proc")` ) 後,呼叫 `getdents64`,然後就開始依序地進入每個 pid 的目錄,讀取 `stat`, `cmdline`, `exe` 的內容,這邊推測應該是在比對有沒有 cron 字串。 [`getdents64`](https://man7.org/linux/man-pages/man2/getdents.2.html) 系統呼叫,通常是透過 C 執行時期函式庫的 [`readdir`](https://man7.org/linux/man-pages/man3/readdir.3.html) 去呼叫,是要取得這個目錄內的子目錄列表。 :::warning :warning: 留意用語: * directory 的翻譯是「目錄」,這從 1960 年代就使用 * folder 的翻譯才是「檔案夾」,這是 MS-Windows 引入的詞彙 Linux 沿襲 UNIX 傳統,使用 "directory" :notes: jserv ::: > 好的,已更改,謝謝~[name=Vectorr] 推測 `pidof` 是取得 `/proc` 下的所有子目錄名稱,然後分別輪詢每個目錄內的相關檔案是否有 cron 的字串,因為有相關檔案的子目錄的名稱即是此 process 的 pid,藉此取得 cron 的 pid。 從完整的 `strace` 輸出看出,當使用這個 driver 隱藏 cron 的 pid 後,不會去輪詢到 cron pid 的那個子目錄,推測 `getdents64` 在當下不會回傳 cron 的 pid 那個子目錄。 1. 接下來要尋找 `find_ge_pid` 跟 `getdents64` 有什麼關係, trace 一下 kernel source code 得到下面的呼叫關係。 ``` find_ge_pid <- next_tgid <- proc_pid_readdir <- proc_root_readdir ``` 再用 `ftrace` 來追蹤 `pidof cron` 使用到的 function call,也可以看到 `proc_root_readdir` 跟 `proc_pid_readdir`。 ``` pidof-54198 [002] .... 184843.742896: ksys_getdents64 <-__x64_sys_getdents64 pidof-54198 [002] .... 184843.742896: __fdget_pos <-ksys_getdents64 pidof-54198 [002] .... 184843.742896: __fget_light <-__fdget_pos pidof-54198 [002] .... 184843.742896: iterate_dir <-ksys_getdents64 pidof-54198 [002] .... 184843.742896: security_file_permission <-iterate_dir pidof-54198 [002] .... 184843.742896: apparmor_file_permission <-security_file_permission pidof-54198 [002] .... 184843.742896: common_file_perm <-apparmor_file_permission pidof-54198 [002] .... 184843.742896: aa_file_perm <-common_file_perm pidof-54198 [002] .... 184843.742897: __fsnotify_parent <-security_file_permission pidof-54198 [002] .... 184843.742897: fsnotify <-security_file_permission pidof-54198 [002] .... 184843.742897: down_read_killable <-iterate_dir pidof-54198 [002] .... 184843.742897: _cond_resched <-down_read_killable pidof-54198 [002] .... 184843.742897: rcu_all_qs <-_cond_resched pidof-54198 [002] .... 184843.742897: proc_root_readdir <-iterate_dir pidof-54198 [002] .... 184843.742897: proc_pid_readdir <-proc_root_readdir ``` kernel裡的 x86_64 的 system call table `arch/x86/entry/syscalls/syscall_64.tbl` 可以看到 `217 common getdents64 __x64_sys_getdents64` ,即 `getdents64` 會觸發 `__x64_sys_getdents64` 被呼叫。 再搭配上面 ftrace `pidof cron` 的紀錄可以看到,`__x64_sys_getdents64` 會一路呼叫到 `proc_pid_readdir` 。 再根據更早之前得到的呼叫關係可以得到 `find_ge_pid` 是一路從 user space 呼叫 `getdents64` system call,然後跳到 kernel space 後觸發 `__x64_sys_getdents64` 被呼叫,然後一路被呼叫到的。 **TODO**: 不知道為什麼有的 function 不會出現在 ftrace 的輸出,像 `find_ge_pid`,應該是有什麼規則,需要再去了解。 1. 從上面的追蹤推論,`pidof cron` 會去取得 `/proc` 的所有子目錄列表,根據檢查 cron 是在哪個 pid 的子目錄中,來判斷 cron 的 pid 為何。在取得 `/proc` 的子目錄的路徑中,有一個地方會呼叫到 `find_ge_pid` ,我們透過 ftrace hook function 的機制, 用 `hook_find_ge_pid` 去取代原本 `find_ge_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; } ``` ## 擴充允許其 PPID 也跟著隱藏,或允許給定一組 PID 列表,而非僅有單一 PID ## 指出程式碼可改進的地方,並動手實作 1. 資源釋放,包含下面各項目 ([commit 1df7d1d](https://github.com/vectorr/linux2021q3_q1_hideproc/commit/1df7d1dc76d9ba738983503f3f2aeb9d75645e47)) 1. character device 相關資源。 2. class (/sys) 相關資源。 3. ftrace hook function 的恢復。 4. driver 內建 hidden pid list 的釋放。 1. 修正 unhide_process 會把目前所有放在 list 中要被隱藏的所有 pid 都刪除的問題。([commit 847450a](https://github.com/vectorr/linux2021q3_q1_hideproc/commit/847450a4d3ea00687b2b23e143620e2b678e324d)) 1. 將 global variables (hidden_proc, hook, cdev, hideproc_class, dev) 包裝在一個屬於這個 module 的自訂 struct 裡。([commit 6387153](https://github.com/vectorr/linux2021q3_q1_hideproc/commit/6387153d15132730b860a9671eb75db572bc6453))