# 2021q3 Homework1 (quiz1)
###### contributed by <`JyunD`>
###### tags: `linux 2021`
- [ ] 解釋程式碼運作原理,包含 ftrace 的使用
- [ ] 本核心模組只能隱藏單一 PID,請擴充為允許其 PPID 也跟著隱藏
- [ ] 允許給定一組 PID 列表,而非僅有單一 PID
- [ ] 指出程式碼可改進的地方,並動手實作
## Development environment
* VM
* 2 Vcpu | 2048 MB Ram | 40.0 GB Disk
* OS: Ubuntu 20.04 LTS
* Kernel: Linux 5.4.0-80-generic #90-Ubuntu
## 程式碼運作原理
定義好一個 character device 讓對它讀寫時有特定行為,對它寫入會新增想要隱藏或想要取消隱藏的 pid,對它讀取會印出當前想要隱藏的 pid
能夠達到這樣的功能全都是仰賴 `ftrace`,`ftrace` 將原先 pidof 會 call 的 `find_ge_pid` hook 成自定義的 function - `hook_find_ge_pid`,`hook_find_ge_pid` 除了會 call 原本的 `find_ge_pid`,還會在檢查這 pid 有沒有在 `hidden_proc` 之中,有的話就不會印出 pid,沒有的話才會正常印出
### 程式碼詳解
* `_hideproc_init` 為 kernel module 進入點 (insmod 時觸發)
1. `alloc_chrdev_region` 註冊 device
2. 初始化 `cdev`
3. 將 char device 加入 system
4. 創建 device 並 register in 檔案系統
5. 初始化 ftrace_hook hook
```c=
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;
}
```
* 初始化 ftrace_hook hook
* name: 要被 hooked 的原 function 名稱
* function: hook fuction 的 address
* original: 原 function 的 pointer
* address: 原 function 的 address
* ops: type 是 ftrace_ops
* ops.func: callback function,call ftrace 時會執行
* ops.flags:
```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);
}
```
* 設定 hook
* 初始化 `hook->ops.func` 和 `hook->ops.flags`
```c=
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 的 function address (用 `kallsyms_lookup_name`)
```c=
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;
}
```
* ftrace 的 callback function
```c=
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 function
拿來 hijack 掉 `find_ge_pid` 的 function
這裡要先簡單探討 pidof 這個 command 是如何找到 pid 的
因為 `find_ge_pid` 是找到大於等於特定 pid 的 function,可以推論 pidof 在呼叫這個函數的時候應該期望的是找到相符合的 pid,如果找不到還是會回傳比較大的,但是最後結果不會印出,所以應該是有在比對是不是目標 process,不是的話還是不會印出
那只要我們故意將找到的 pid 加 1,那回傳的就不會是符合的 pid (若剛好還是的話,繼續加 1),pidof 就不會印出我們要的 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;
}
```
* 定義 device 各個 member
* .owner
* 設定 owner 為自己避免用到一半被 unload
* .open
* device_open
* 回傳成功
* .release
* device_close
* 回傳成功
* .read
* device_read
* 讀取 `hidden_proc` 中的內容 copy 到 user space 後印出
* .write
* device_write
* 將 pid 加到 `hidden_proc` 的 tail
`_hideproc_exit` 為 kernel module 離開點 (rmmod 時觸發)
### 補充
#### alloc_chrdev_region
```c=
int alloc_chrdev_region ( dev_t * dev,
unsigned baseminor,
unsigned count,
const char *name);
```
kernel 提供了 3 個函數來註冊 character device
* register_chrdev_region
* 註冊一直範圍的 device number
* alloc_chrdev_region
* 讓 kernel 主動分配未使用的 device number
* register_chrdev
* 註冊指定 device number
#### class_create
建立 class structure
#### cdev_init
初始化 cdev structure
#### cdev_add
把 character device 加到 system
#### device_create
建立 device 並將他註冊到 sysfs
## 隱藏 PID 列表
修該 `device_write` 讓他可以處理多個 pid,用 `strsep` 做分割
```diff=
static ssize_t device_write(struct file *filep,
const char *buffer,
size_t len,
loff_t *offset)
{
...
+ char *message, *number, *tmp;
...
if (!memcmp(message, add_message, sizeof(add_message) - 1)) {
- kstrtol(message + sizeof(add_message), 10, &pid);
- hide_process(pid);
+ number = message + sizeof(del_message);
+ while ((tmp = strsep(&number, " ")) != NULL) \\ 用空白做分割,直到無法分割
+ if(strlen(tmp) && !kstrtol(tmp, 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);
+ number = message + sizeof(del_message);
+ while ((tmp = strsep(&number, " ")) != NULL)
+ if(strlen(tmp) && !kstrtol(tmp, 10, &pid))
+ unhide_process(pid);
...
return len;
}
```
## 隱藏 PPID
找 PPID 我主要使用到以下 3 個 fucntion
* struct pid *find_get_pid(int nr);
* 從 number 找 struct pid
* struct task_struct *get_pid_task(struct pid *pid, enum pid_type);
* 從 struct pid 和 pid_type 找其 task_struct
* task_ppid_nr(const struct task_struct *tsk)
* 從 task_struct 找到其 PPID
修該 `device_write` 讓他在寫入的時候將其 PPID 也加進去
```diff=
static ssize_t device_write(struct file *filep,
const char *buffer,
size_t len,
loff_t *offset)
{
+ struct pid *tmp_pid;
+ struct task_struct *tmp_task_struct;
+ pid_t tmp_pid_t;
...
if (!memcmp(message, add_message, sizeof(add_message) - 1)) {
number = message + sizeof(del_message);
while ((tmp = strsep(&number, " ")) != NULL)
- if(strlen(tmp) && !kstrtol(tmp, 10, &pid))
- hide_process(pid);
+ if(strlen(tmp) && !kstrtol(tmp, 10, &pid)) {
+ tmp_pid = find_get_pid(pid);
+ tmp_task_struct = get_pid_task(tmp_pid, PIDTYPE_PID);
+ tmp_pid_t = task_ppid_nr(tmp_task_struct);
+ hide_process(pid);
+ hide_process(tmp_pid_t);
+ }
} else if (!memcmp(message, del_message, sizeof(del_message) - 1)) {
number = message + sizeof(del_message);
while ((tmp = strsep(&number, " ")) != NULL)
- if(strlen(tmp) && !kstrtol(tmp, 10, &pid))
- unhide_process(pid);
+ if(strlen(tmp) && !kstrtol(tmp, 10, &pid)) {
+ tmp_pid = find_get_pid(pid);
+ tmp_task_struct = get_pid_task(tmp_pid, PIDTYPE_PID);
+ tmp_pid_t = task_ppid_nr(tmp_task_struct);
+ unhide_process(pid);
+ unhide_process(tmp_pid_t);
+ }
...
return len;
}
```
## 改進
`unhide_process` 實質上是清空整個 `hidden_proc`,並不是刪除指定 pid
```c=
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) {
if (proc->id == pid)
{
list_del(&proc->list_node);
kfree(proc);
break;
}
}
return SUCCESS;
}
```
module 在 exit 時並沒有將 device 取消註冊,也沒有把該釋放的資源釋放掉,hook 也要拔掉不然會出問題
```c=
static void _hideproc_exit(void)
{
pid_node_t *proc, *tmp_proc;
dev_t dev;
printk(KERN_INFO "@ %s\n", __func__);
/* FIXME: ensure the release of all allocated resources */
dev = cdev.dev;
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);
class_destroy(hideproc_class);
cdev_del(&cdev);
unregister_chrdev_region(dev, MINOR_VERSION);
}
```
在 `hide_process` 中檢查 pid 是不是本來就在裡面,避免一個 pid 有多筆紀錄
```diff=
static int hide_process(pid_t pid)
{
pid_node_t *proc = kmalloc(sizeof(pid_node_t), GFP_KERNEL);
+ if (is_hidden_proc(pid)) {
+ kfree(proc);
+ return SUCCESS;
+ }
proc->id = pid;
list_add_tail(&proc->list_node, &hidden_proc);
return SUCCESS;
}
```
* sprintf overflow