# 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`