# h12021q3 Homework1 (quiz1) contributed by <meyr> 題目 : [第 1 週測驗題](https://hackmd.io/@sysprog/linux2021-summer-quiz1) 開發環境 OS : Ubuntu 18.04.5 LTS kernel : 4.15.0-151-generic gcc : 7.5.0 ## 為什可以hide process?? 在還沒分析程式前,對以下的操作結果,**感覺到非常神奇**。所以覺得先分析一下為什麼可以隱藏process id. ```shell $ sudo insmod hideproc.ko $ pidof cron $ echo "add 644" | sudo tee /dev/hideproc $ pidof cron # 無法見到 cron $ echo "del 644" | sudo tee /dev/hideproc $ pidof cron # 再次見到 cron $ sudo rmmod hideproc ``` 首先針對pidof的行為進行分析。 執行以下command,得到如下的結果。 ```shell $ strace pidof cron ``` 只擷取其中一部分。 ```shell stat("/proc/1428/exe", 0x7ffdf5ff0820) = -1 EACCES (Permission denied) openat(AT_FDCWD, "1430/stat", O_RDONLY) = 4 fstat(4, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0 read(4, "1430 (NetworkManager) S 1 1430 1"..., 1024) = 185 close(4) = 0 openat(AT_FDCWD, "1430/cmdline", O_RDONLY) = 4 fstat(4, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0 read(4, "/usr/sbin/NetworkManager\0--no-da"..., 1024) = 37 read(4, "", 1024) = 0 close(4) = 0 stat("/proc/1430/exe", 0x7ffdf5ff0820) = -1 EACCES (Permission denied) openat(AT_FDCWD, "1450/stat", O_RDONLY) = 4 fstat(4, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0 read(4, "1450 (cron) S 1 1450 1450 0 -1 1"..., 1024) = 171 close(4) = 0 openat(AT_FDCWD, "1450/cmdline", O_RDONLY) = 4 fstat(4, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0 read(4, "/usr/sbin/cron\0-f\0", 1024) = 18 read(4, "", 1024) = 0 close(4) = 0 stat("/proc/1450/exe", 0x7ffdf5ff0820) = -1 EACCES (Permission denied) openat(AT_FDCWD, "1453/stat", O_RDONLY) = 4 fstat(4, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0 read(4, "1453 (thermald) S 1 1453 1453 0 "..., 1024) = 176 close(4) = 0 ``` 可見pidof會不斷的從/proc/<pid> 讀取資料,其中會去開起stat, cmdline和exe這三個檔案。 但是,exe因為權限問題不能讀取,所以實際上是讀取stat和cmdline這兩個檔案。 再打開stat和cmdline這兩個檔案。 /proc/1430/stat ```shell 1283 (cron) S 1 1283 1283 0 -1 1077936384 286 1364 1 0 0 0 1 0 20 0 1 0 2869 32374784 849 18446744073709551615 1 1 0 0 0 0 0 0 65537 0 0 0 17 0 0 0 185 0 0 0 0 0 0 0 0 0 0 ``` /proc/1430/cmdline ```shell /usr/sbin/cron -f ``` 因為我們的輸入只有"cron"這個字串,所以合理推測應該是不斷比較/proc/<pid>/stat或cmdline是否有包含相同的字串。 另外使使ltrace來看使用的library call,確實也符合猜想。當strcmp成功時,執行malloc來給查詢的process一個struct。 ```c strcmp("/usr/sbin/cron", "cron") = -52 strcmp("cron", "cron") = 0 strlen("cron") = 4 strchr("/usr/sbin/cron", ' ') = nil malloc(16) = 0x5580ed2c3280 ``` 檢查執行完下面command前後的/proc資料夾,確實/proc/1430/就不見了,***cron deamon一定還在跑,只是kernel在列舉的時候跳過了這一個process。*** ```shell $ sudo insmod hideproc.ko $ echo "add 1430" | sudo tee /dev/hideproc ``` 範例程式中,使用ftrace來hook find_ge_pid()這個function,可見這個function的重要性。使用以下command來確認,執行pidof是否真的會呼叫 find_ge_pid()。 ```shell $ sudo stackcount-bpfcc -K -U find_ge_pid or $ sudo trace-bpfcc -K -U find_ge_pid ``` 得到以下的結果。其中495為我執行pidof cron之後call到find_ge_pid所經過一樣的call stack次數。可見如推論一樣,當使用pidof指令,會去把/proc的資料夾所有process讀出,就會用到find_ge_pid的function。 ```shell find_ge_pid proc_pid_readdir proc_root_readdir iterate_dir sys_getdents do_syscall_64 entry_SYSCALL_64_after_hwframe 495 ``` 老師給的hook function如下。 ```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; } ``` 當系統要列舉1430(cron)這個process時,因為和hidden_proc裡面註冊的process一樣, ==所以把nr + 1,這時候real_find_ge_pid就會把下一個process的pid丟回去,達到隱藏的效果==。 ## ftrace 研究 ref[1] : [內核熱補丁的黑科技](https://richardweiyang-2.gitbook.io/kernel-exploring/00-index-3/05-kernel_live_patch) ref[2] : [探秘ftrace](https://richardweiyang-2.gitbook.io/kernel-exploring/00-index-3/04-ftrace_internal) 老師給的sample code中,有兩個和ftrace相關的API 1. ftrace_set_filter_ip 2. register_ftrace_function ### ftrace_set_filter_ip 根據kernel的source code註解此API如下: ```c /** * 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 failes to update filter. */ int ftrace_set_filter_ip(struct ftrace_ops *ops, unsigned long ip, int remove, int reset) ``` 其中remove不為0,即為刪除的意思。reset不為0,就是再動作前先清掉filter。ip為想要hook的function,當kernel呼叫到這個function時,想要執行的func由第一個參數ops決定。 根據ref[1],註冊想要hook的func其實就是把ip加到ftrace_ops struct裡的filter_hash之中。 所以當register_ftrace_function不成功,或是程式要離開的時候,必須呼叫相同的API但是, ==remove = 1== 。 ```c ftrace_set_filter_ip(&hook->ops, hook->address, 1, 0); ``` ### register_ftrace_function 根據kernel source,以下為此API的定義。 ```c /** * register_ftrace_function - register a function for profiling * @ops - ops structure that holds the function for profiling. * * Register a function to be called by all functions in the * kernel. * * Note: @ops->func and all the functions it calls must be labeled * with "notrace", otherwise it will go into a * recursive loop. */ int register_ftrace_function(struct ftrace_ops *ops) ``` 根據ref[1],這個func的用意是把ftrace_ops加到global的ftrace_ops_list中。==因為一個hook func可以允許有多個ftrace_ops==,且會通過ftrace_ops_test()來判斷現在的func是否符合這個ftrace_ops。如果符合才會執行op->func。 當然有register就會有unregister,所以在離開程式的時候一樣要呼叫 ```c unregister_ftrace_function(&hook->ops); ``` 來把註冊的ftrace_ops從ftrace_ops_list中移除。 ## 擴充為允許其 PPID 也跟著隱藏 除了隱藏pid之外,連帶也隱藏其parent process,那問題就變成怎麼從pid得到ppid。找了一下網路資料還是不知道怎麼做,最後是參考了同學的解答。為了回推ppid,使用到以下的API。 ### find_get_pid 從API的定義可以得知,輸入process id之後得到struct pid。這個struct是下一個API必須用到的input argument。 ```c struct pid *find_get_pid(pid_t nr); ``` ### get_pid_task 從struct pid得到struct task_struct,其中real_parent->pid即為我們要的parent id。 ```c struct task_struct *get_pid_task(struct pid *pid, enum pid_type type); ``` 把取得parent id獨立成一個function,方便後續使用。return value使用負值,是避免以後判斷函數執行結果和process id混淆。 ```c static pid_t get_parent_pid(pid_t pid) { struct task_struct *ts; struct pid *realPID; realPID = find_get_pid(pid); if(!realPID) return -FAIL; ts = get_pid_task(realPID, PIDTYPE_PID); if(!ts) return -FAIL; printk(KERN_INFO "@ %s parent_id : %d\n", __func__, ts->real_parent->pid); return ts->real_parent->pid; } ``` ## 程式碼可改進的地方 1. pid重複插入的問題 2. ERROR number ###### tags: `linux2021`