# 2021q3 Homework1 (quiz1)
contributed by < [`Hao-yu-lin`](https://github.com/Hao-yu-lin/linux2021.git) >
###### tags: `linux2021`
---
[2021 年暑期 Linux 核心 第 1 週測驗題](https://hackmd.io/@sysprog/linux2021-summer-quiz1)
> Linux 核心版本:GNU/Linux 5.4.0-80-generic x86_64
## 解釋上述程式碼運作原理,包含 ftrace 的使用
依照執行流程分成三部分,探討程式碼
### Part1:Module insert
當使用下方的指令後,便會開始載入 module
```
sudo insmod hideproc.ko
```
由 `/include/linux/module.h` 內容得知
```c
module_init(_hideproc_init);
module_exit(_hideproc_exit);
```
為程式的進入點
> module_init():driver initialization entry point
> module_exit():driver exit entry point
觀察`_hideproc_init` 得知,此程式主要在建立 hideproc 裝置環境,建立後執行`init_hook()` 建立 hook function
```c
static void init_hook(void)
{
real_find_ge_pid = (find_ge_pid_func) kallsyms_lookup_name("find_ge_pid");
...
hook_install(&hook);
}
```
`hook_install` 此處做的事情是初始化 `hook_ops` ,主要目的是將 hook 內的 func 成員指向 `hook_ftrace_thunk`
```c
static int hook_install(struct ftrace_hook *hook)
{
// 取得 Address
int err = hook_resolve_addr(hook);
...
// 將 ops 取代為 hook_ftrace_thunk 為 ftrace 再進入 kernel 內時,真正執行 callback 的 func
hook->ops.func = hook_ftrace_thunk;
hook->ops.flags = FTRACE_OPS_FL_SAVE_REGS | FTRACE_OPS_FL_RECURSION_SAFE |
FTRACE_OPS_FL_IPMODIFY;
// 使用 ftrace_set_filter_ip 建立一個 fliter 來篩選出需要的函數後才執行 hook
err = ftrace_set_filter_ip(&hook->ops, hook->address, 0, 0);
...
// 使用 register_ftrace_function 允許 ftrace 調用 hook_ftrace_thunk
err = register_ftrace_function(&hook->ops);
...
return 0;
}
```
`hook_resolve_addr` 在此處的目的就是找到 `find_ge_pid` 的函數 address
並保存在 `hook->orig` 中
```c
static int hook_resolve_addr(struct ftrace_hook *hook)
{
hook->address = kallsyms_lookup_name(hook->name);
...
*((unsigned long *) hook->orig) = hook->address;
return 0;
}
```
接續探討 `hook_ftrace_thunk`,將 自定義的 `hook_find_ge_pid` 透過更改 %rip 的下一步指令,強制跳轉到 hook function
```c
static void notrace hook_ftrace_thunk(unsigned long ip,
unsigned long parent_ip,
struct ftrace_ops *ops,
struct pt_regs *regs)
{
// 利用 container_of 來獲得 原始 hook 的 address
struct ftrace_hook *hook = container_of(ops, struct ftrace_hook, ops);
// 避免重複調用
if (!within_module(parent_ip, THIS_MODULE))
regs->ip = (unsigned long) hook->func;
}
```
### Part2:(Un)hide Process
當使用下方指令後,device 便會開始執行 `device_write` 指令
`device_write` 會根據你輸入在 buffer 內的指令,來執行 `hide_process` 或是 `unhide_process`
```
echo "add 644" | sudo tee /dev/hideproc
echo "del 644" | sudo tee /dev/hideproc
```
建立一個 proc,設定 id 為 PID,並將此 proc 連接到 `hidden_proc` list 串的最尾巴
```c
static int hide_process(pid_t pid)
{
pid_node_t *proc = kmalloc(sizeof(pid_node_t), GFP_KERNEL);
proc->id = pid;
list_add_tail(&proc->list_node, &hidden_proc);
return SUCCESS;
}
```
### Part3:Show PID
當使用下方指令後
```
pidof cron
```
由於 callback function 作用會導向執行 `hook_find_ge_pid`,透過 `is_hidden_proc` 來遍歷 `hidden_proc` 若有符合的 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;
}
```
## 本核心模組只能隱藏單一 PID,請擴充為允許其 PPID 也跟著隱藏,或允許給定一組 PID 列表,而非僅有單一 PID
透過 `find_get_pid` 取得 child 的 pid struct,藉由 `get_pid_task` 取得 task struct,再利用 `real_parent`,來得到 parent 的 pid 即可
```c
#include <linux/sched.h>
static pid_t get_ppid(long cpid)
{
struct task_struct *child = NULL;
struct pid *child_pid = NULL;
struct task_struct *parent = NULL;
child_pid = find_get_pid(cpid);
child = get_pid_task(child_pid, PIDTYPE_PID);
parent = child -> real_parent;
return parent -> pid;
}
```
```c
static ssize_t device_write(struct file *filep,
const char *buffer,
size_t len,
loff_t *offset)
{
...
if (!memcmp(message, add_message, sizeof(add_message) - 1)) {
kstrtol(message + sizeof(add_message), 10, &pid);
hide_process(pid);
ppid = get_ppid(pid);
hide_process(ppid);
} else if (!memcmp(message, del_message, sizeof(del_message) - 1)) {
kstrtol(message + sizeof(del_message), 10, &pid);
unhide_process(pid);
ppid = get_ppid(pid);
unhide_process(ppid);
} else {
kfree(message);
return -EAGAIN;
}
*offset = len;
kfree(message);
return len;
}
```
以下是原始尚未新增同時刪除 PPID 時的操作
```
$ pidof cron # 輸出 590
$ pstree -s -p 590 # 輸出 systemd(1)───cron(590) 得知 ppid = 1
$ echo "add 590" | sudo tee /dev/hideproc
$ pstree -s -p 590 # 發現 pid:590 被忽略找不到了
systemd(1)─┬─accounts-daemon(586)─┬─{accounts-daemon}(594)
│ └─{accounts-daemon}(618)
├─atd(612)
├─dbus-daemon(592)
├─irqbalance(598)───{irqbalance}(635)
├─login(629)───bash(1187)
├─multipathd(475)─┬─{multipathd}(476)
│ ├─{multipathd}(477)
│ ├─{multipathd}(478)
│ ├─{multipathd}(479)
│ ├─{multipathd}(480)
│ └─{multipathd}(481)
...... 省略
$ echo "add 1" | sudo tee /dev/hideproc
$ pstree -s -p 590 # 此時(1) 這地方直接變成 ?
?(1)─┬─accounts-daemon(586)─┬─{accounts-daemon}(594)
│ └─{accounts-daemon}(618)
├─atd(612)
├─dbus-daemon(592)
├─irqbalance(598)───{irqbalance}(635)
├─login(629)───bash(1187)
├─multipathd(475)─┬─{multipathd}(476)
│ ├─{multipathd}(477)
│ ├─{multipathd}(478)
│ ├─{multipathd}(479)
│ ├─{multipathd}(480)
│ └─{multipathd}(481)
├─networkd-dispat(600)
├─polkitd(647)─┬─{polkitd}(648)
│ └─{polkitd}(650)
├─sh(1328)───node(1335)─┬─node(1385)─┬─bash(1437)───pstree(7285)
│ │ ├─{node}(1386)
│ │ ├─{node}(1387)
│ │ ├─{node}(1388)
│ │ ├─{node}(1389)
$ echo "del 1" | sudo tee /dev/hideproc
$ echo "del 590" | sudo tee /dev/hideproc
$ pstree -s -p 590 # 輸出 systemd(1)───cron(590) 變正常了
```
若新增同時刪除 PPID
>> 因為要重新編譯 code 此時發現 linux 當機了,此問題留到下一題解決
```
$ pidof cron # 輸出 584
$ pstree -s -p 584 # 輸出 systemd(1)───cron(584) 得知 ppid = 1
$ echo "add 584" | sudo tee /dev/hideproc
$ pstree -s -p 584 # ppid 成功消失了
?(1)─┬─accounts-daemon(581)─┬─{accounts-daemon}(586)
│ └─{accounts-daemon}(615)
├─atd(605)
├─dbus-daemon(587)
├─irqbalance(593)───{irqbalance}(630)
├─login(613)───bash(888)
├─multipathd(472)─┬─{multipathd}(473)
│ ├─{multipathd}(474)
│ ├─{multipathd}(475)
│ ├─{multipathd}(476)
│ ├─{multipathd}(477)
│ └─{multipathd}(478)
```
## 指出程式碼可改進的地方,並動手實作
每當我 sudo rmmod hideproc,linux 就會直接當機
### \_hideproc_exit() 沒有釋放相關資源
首先要把 `MKDEV(dev_major, MINOR_VERSION)`,不然一直輸入看起來很不簡潔,首先釋放 hook 資源,利用已經寫好的 `hook_remove` 來釋放,接著對照 `_hideproc_init` 反向操作的回收資源
- `device_create` / `device_destroy`
- `cdev_add` / `cdev_del`
- `class_create` / `class_destroy`
- `alloc_chrdev_region` / `unregister_chrdev_region`
```c
static void _hideproc_exit(void)
{
printk(KERN_INFO "@ %s\n", __func__);
hook_remove(&hook);
device_destroy(hideproc_class, MK_DEV);
class_destroy(hideproc_class);
cdev_del(&cdev);
unregister_chrdev_region(MK_DEV, MINOR_VERSION);
/* FIXME: ensure the release of all allocated resources */
}
```
### unhide_process() 全部刪除
在實作嘗試刪除其他 pid,然後將其復原時發現,復原時他會一併復原,因此對 `unhide_process` 加點判斷式
```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(pid == proc->id){
list_del(&proc->list_node);
kfree(proc);
}
}
return SUCCESS;
}
```
## 參考資料
[Using ftrace to hook to functions](https://www.kernel.org/doc/html/v4.17/trace/ftrace-uses.html)
[ftrace來進行內核hook,part2](https://www.twblogs.net/a/5d1eed29bd9eee1e5c8374ab)