# 2021q3 Homework1 (quiz1)
contributed by < `leon123858` >
:::danger
請尊重台灣資訊科技前輩的篳路藍縷,使用繁體中文和傳統詞彙 (1970 年代即出現,非「中共製漢語」)
:notes: jserv
:::
*了解, 我修改後也會開始注意, 有些名詞在撰寫時我不清楚其出處就直接使用了*
## 1. 解釋上述程式碼運作原理,包含 ftrace 的使用
:::warning
中英文間用一個半形空白區隔
:notes: jserv
:::
*了解我再檢查一次*
1. 首先翻了一遍所有被用到的 `ftrace` 函數的 document ,可以發現全部的核心在於以下函數
> register_ftrace_function(&ops);
> ------------
> The registered callback will start being called some time after the register_ftrace_function() is called and before it returns. The exact time that callbacks start being called is dependent upon architecture and scheduling of services. The callback itself will have to handle any synchronization if it must begin at an exact moment.
可知 `ftrace` 提供使用者在指定的函數執行時運行自定義的 callback function ,如此一來只要在軟體運行查詢 pid 相關的指令時替換掉其輸出結果即可。
2. 接著看一下被調用的 callback function
> void callback_func(unsigned long ip, unsigned long parent_ip,struct ftrace_ops *op, struct pt_regs *regs);
> ---------------------
> @ip
This is the instruction pointer of the function that is being traced. (where the fentry or mcount is within the function)
@parent_ip
This is the instruction pointer of the function that called the the function being traced (where the call of the function occurred).
@op
This is a pointer to ftrace_ops that was used to register the callback. This can be used to pass data to the callback via the private pointer.
@regs
If the FTRACE_OPS_FL_SAVE_REGS or FTRACE_OPS_FL_SAVE_REGS_IF_SUPPORTED flags are set in the ftrace_ops structure, then 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. Otherwise it either contains garbage, or NULL.
```cpp
if (!within_module(parent_ip, THIS_MODULE))
fregs->ip = (unsigned long)hook->func;
```
此處利用 `hook->func` 所代表的函數替換掉了原始函數
:::warning
改進上述漢語表達。
:notes: jserv
:::
*了解*
3. 替換原始函數的新函數
```cpp
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;
}
```
發現他不會輸出隱藏列表內的 pid , 整個流程到這裡結束。
4. 接著觀察一下為甚麼選擇置換 "find_ge_pid" 這個函數
分析取得 pid 所使用的指令 :
在 shell 中輸入 `strace pidof cron`
得到冗長流程, 取其中一段觀察
```shell
stat("/proc/2564/exe", {st_mode=S_IFREG|0755, st_size=1583592, ...}) = 0
readlink("/proc/2564/exe", "/usr/bin/strace", 4096) = 15
openat(AT_FDCWD, "2567/stat", O_RDONLY) = 4
fstat(4, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
read(4, "2567 (pidof) R 2564 2564 2543 34"..., 4096) = 305
read(4, "", 3072) = 0
close(4) = 0
openat(AT_FDCWD, "2567/cmdline", O_RDONLY) = 4
fstat(4, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
read(4, "pidof\0cron\0", 1024) = 11
close(4) = 0
stat("/proc/2567/exe", {st_mode=S_IFREG|0755, st_size=27016, ...}) = 0
readlink("/proc/2567/exe", "/usr/sbin/killall5", 4096) = 18
getdents64(3, /* 0 entries */, 32768) = 0
close(3) = 0
getuid() = 1000
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0
write(1, "518\n", 4518
) = 4
exit_group(0) = ?
+++ exited with 0 +++
```
查閱 [readdir(3)](https://man7.org/linux/man-pages/man3/readdir.3.html),發現 getdents64 是 readdir 的介面, 其效果如下
> The readdir() function returns a pointer to a dirent structure representing the next directory entry in the directory stream pointed to by dirp. It returns NULL on reaching the end of the directory stream or if an error occurred.
可以看出其用途, 顧名思義是在取得 read directory 所需的指標。在此處應該是在查閱存有 pid 資料的 directory 的指標。
5. 接著我們來看一下 readdir 調用後發生甚麼事 ?
###### 預期挖到最後應該會出現find_ge_pid, 但目前不知道怎麼往下挖, 待努力。
:::warning
不要濫用「理論上」這詞,在這裡你只要說「預期會」即可
:notes: jserv
:::
## 2. 本程式僅在 Linux v5.4 測試,若你用的核心較新,請試著找出替代方案
2020 年的變更使 kallsyms_lookup_name() 不再是一個可以直接存取的 function, 導致現在無法透過該方法直接存取到指定函數的 address 。 此時我想起 kernel mode 的記憶體碎片化問題與相關解決方案, 若是從這裡思考我猜測在 kernel mode 的記憶體應該只有在較為特殊的情況下才會變動(除了重開機), 所以我可以直接在程式外面取得 address , 再寫死進程式裡即可, 最後此專案可以順利運行。雖然這不是個好方法, 但確實可以讓我輕鬆地開始運行本專案。
```shell
# in shell 直接取得 address
sudo cat /proc/kallsyms | grep find_ge_pid
```
```cpp
//把兩個 kallsyms_lookup_name(XXX) 寫死成該 Address;
//real_find_ge_pid = (find_ge_pid_func)kallsyms_lookup_name("find_ge_pid");
real_find_ge_pid = (find_ge_pid_func)(0xffffffff826bfcb0);
```
### 更
發現利用 <linux/livepatch.h> lib 也很簡單, 我是直接參考其他同學的, 因此就不在這裡做過多敘述, 詳情請見我下方發布的原始程式碼。
:::warning
發布原始程式碼本是作業要求,不是「公布」。程式碼就是給人指教批評的,否則不會進步。
:notes: jserv
:::
*了解, 我會注意用詞*
## 3. 本核心模組只能隱藏單一 PID,請擴充為允許其 PPID 也跟著隱藏,或允許給定一組 PID 列表,而非僅有單一 PID
### 以下試做把一組 PID 一起隱藏
新的輸入
```shell
$ sudo insmod hideproc.ko
$ pidof firefox # 看見firefox 的 PID 有 4 個是: 3367,2258,4466,4488
$ echo "add 3367,2258,4466,4488" | sudo tee /dev/hideproc
$ pidof firefox # 無法見到 firefox
$ echo "del 3367" | sudo tee /dev/hideproc
$ pidof firefox # 再次見到 firefox
$ sudo rmmod hideproc
```
```cpp
// 用於取得分隔符號位置
static int char_place(char *str, char c) {
int len = strlen(str);
int i;
for (i = 0; i < len; i++) {
if (str[i] == c)
return i;
}
return -1;
}
//改寫device_write, 以下變更處會有註解
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);
printk(KERN_INFO "@ %s\n", message);
if (!memcmp(message, add_message, sizeof(add_message) - 1)) {
//進行字串處理 ex: add 3367,2258,4466,4488 => [3367,2258,4466,4488]
char *goal;
char *tmpCharPtr;
//掠過add ex: add 3367,2258,4466,4488 => 3367,2258,4466,4488
goal = message + sizeof(add_message);
while (1) {
//取得分隔符號位置, 沒分隔符則-1 ex: 4
int dotPlace = char_place(goal, ',');
if (dotPlace < 0) {
//沒分隔符, 直接轉換且進行操作
kstrtol(goal, 10, &pid);
hide_process(pid);
printk(KERN_INFO "@ %lu\n", pid);
break;
}
//配置臨時空間
tmpCharPtr = kmalloc(dotPlace + 1, GFP_KERNEL);
//提取分隔符前字串
strncpy(tmpCharPtr, goal, dotPlace);
//轉換且進行操作
kstrtol(tmpCharPtr, 10, &pid);
hide_process(pid);
printk(KERN_INFO "@ %lu\n", pid);
kfree(tmpCharPtr);
//將pointer移至分隔符後方
goal += (dotPlace + 1) * sizeof(char);
}
} 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;
}
```
### 以下試做 連帶隱藏 PPID
// 變更 hide_processc 函數, 以下變更處會有註解
```cpp
static int hide_process(pid_t pid) {
pid_node_t *proc = kmalloc(sizeof(pid_node_t), GFP_KERNEL);
pid_node_t *procParent;
// task_struct用來存放任務
struct task_struct *task_struct_child;
struct task_struct *task_struct_parent;
struct pid *realPID;
proc->id = pid;
list_add_tail(&proc->list_node, &hidden_proc);
// 利用PID取得PID物件, 包含PID相關資料
realPID = find_get_pid(pid);
// 取得linux任務列表中該任務的結構
task_struct_child = get_pid_task(realPID, PIDTYPE_PID);
if (task_struct_child != NULL) {
procParent = kmalloc(sizeof(pid_node_t), GFP_KERNEL);
// 取得PID所指向任務的父(母)任務
task_struct_parent = task_struct_child->real_parent;
// 把該pid放入 hidden_proc ,似乎因為編號問題, 所以取得的PID要加 1 才是真正的PID
procParent->id = (pid_t)((int)task_struct_parent->pid + 1);
printk(KERN_INFO "@ ppid: %d\n", (int)procParent->id);
list_add_tail(&procParent->list_node, &hidden_proc);
}
return SUCCESS;
}
```
:::warning
思考: 為何用 `+ 1` 呢?是否會遇到 PID 碰撞?
:notes: jserv
:::
## 4. 指出程式碼可改進的地方,並動手實作
[原始碼](https://github.com/leon123858/linuxClass/blob/main/hidenProcess/main.c)
- 變更1: 使刪去隱藏process可以指定PID
調整 unhide_process 使其可以指定 PID 刪除, 且當 PID 為0時維持直接全刪
```cpp
static int unhide_process(pid_t pid) {
pid_node_t *proc, *tmp_proc;
if (pid == 0) {
list_for_each_entry_safe(proc, tmp_proc, &hidden_proc, list_node) {
list_del(&proc->list_node);
kfree(proc);
}
return SUCCESS;
}
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;
}
```
- 變更2: 釋放所有資源
在 `_hideproc_init` 中,按照順序向核心進行以下操作:
1. 配置裝置所需空間
2. 產生裝置編號
3. 設定編號類別
4. 註冊 character 裝置
5. 建立裝置所需檔案
6. 註冊 ftrace hook
所以只需要在卸載模組時( `_hideproc_exit` 中)全部釋放掉即可
```cpp
static struct cdev cdev;
static struct class *hideproc_class = NULL;
static dev_t *devPtr = NULL;
static int dev_major;
//中間略
static int _hideproc_init(void) {
int err, r;
dev_t dev;
printk(KERN_INFO "@ %s\n", __func__);
// 配置裝置所需空間
err = alloc_chrdev_region(&dev, 0, MINOR_VERSION, DEVICE_NAME);
devPtr = &dev;
// 產生裝置編號
dev_major = MAJOR(dev);
// 設定編號類別
hideproc_class = class_create(THIS_MODULE, DEVICE_NAME);
// 註冊 character 裝置
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);
r = klp_enable_patch(&patch);
if (!r)
return -1;
//註冊ftrace hook
init_hook();
return 0;
}
static void _hideproc_exit(void) {
// 清空 hideProcess List, 已設定好設為0就是全清
unhide_process(0);
// 註銷 ftrace 的 hook ,包含設定 callback function 及設定綁定的 address
hook_remove(&hook);
/* 釋放 character 裝置*/
// 刪除裝置所需檔案
device_destroy(hideproc_class, MKDEV(dev_major, MINOR_VERSION));
// 註銷裝置編號
cdev_del(&cdev);
// 刪除該裝置類別
class_destroy(hideproc_class);
// 釋放裝置空間
unregister_chrdev_region(*devPtr, MINOR_VERSION);
printk(KERN_INFO "@ %s\n", __func__);
/* FIXME: ensure the release of all allocated resources */
}
```
:::warning
列出對應的 git commit 和超連結
:notes: jserv
:::
*了解! 我會進行整理*