# 2021q3 Homework1 (quiz1)
###### tags: `linux2021`
contributed by < `ekangmonyet` >
## 起點

:::danger
- CCC = `list_add(proc, hidden_proc)`
- DDD = `if (proc->id == pid) list_del(proc)`
:::
### 邏輯性錯誤
- 沒掌握到程式運用 list 的方式,錯誤使用 `list_add()`
- 執著於正確把單一 pid 從 list 上移除,硬加了 `if()` 邏輯
### 技術性錯誤
- 不清楚 list 的結構,錯把整個 `pid_node_t` 傳入
- 太匆忙忘了該傳入指標(也可說是對 API 不夠了解)
## (可 skip)花了數小時把 linux 架起來
目標:用 User Mode Linux / qemu 啟動可以掛 gdb 的 kernel
### 不能編譯??
用 custom linux 無論怎麼編都編不過
```
error: field ‘ops’ has incomplete type
18 | struct ftrace_ops ops;
| ^~~
...
```
才發現問題是 build kernel 時缺少 config flag :
```
CONFIG_FUNCTION_TRACER=y
```
_而且 UML 似乎沒有這個功能 (待確認),最後只在 qemu 上成功_
### QEMU 食譜
需求
- qemu-system-x86_64 (或是其他 native 的架構來避免 cross-compilation )
- [linux-5.X 原始碼](https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/)
- [alpine rootfs](https://alpinelinux.org/downloads/) 或你喜歡的 rootfs
- fdisk
#### 1. 編譯 linux 核心 (目標: vmlinux )
1. 解壓縮 linux-X.tar.xz ,cd 進去
2. `make xconfig` ,必開:
- Kernel hacking/Tracers/Kernel Function Tracer
- Kernel hacking/Generic Kernel Debugging/KGDB
- Enable loadable module support
3. `make` _-jN_
#### 2. 生成 hd image (目標: rootimg )
:::warning
不是最方便操作的方法,歡迎建議
> 可改用 [virtme](https://git.kernel.org/pub/scm/utils/kernel/virtme/virtme.git),參見 [測試 Linux 核心的虛擬化環境](https://hackmd.io/@sysprog/linux-virtme) --jserv
:::
1. `fallocate -l 512MiB ./rootimg`
2. `fdisk ./rootimg` ,做一個 partition
3. `sudo losetup --partscan --show --find rootimg`
4. `mkdir mnt`
5. `sudo mount /dev/loop0p1 mnt`
6. `cd mnt`
7. `tar -xzf <alpine-minirootfs-X.tar.gz>`
8. `chmod +x ./sbin/init` (內容參考下方)
9. `cd ../ && sudo umount mnt` (必須先 unmount!)
init 檔:
```bash
#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
/bin/sh -m
```
#### 3. 開機
```bash
qemu-system-x86_64 \
-kernel ./arch/x86_64/boot/bzimage \
-hda ./rootimg \
-append "root=/dev/sda1 console=ttys0 rw panic=-1" \
-nographic \
-no-reboot \
-s
```
- `-s` 啟動 gdb server
- `panic=-1` 讓 kernel panic 後自動重開,加上 `-no-reboot` 則讓 qemu 重開時退出 (在 init shell 死掉後 kernel 就會 panic ,用這方法方便退出 qemu )
- `-nographic` + `console=ttys0` 直接在 terminal 裡操作
## 初版問題
- 在 unhide 時會把全部都移除,而非只是指定的 PID - [commit 088f729](https://github.com/ekangmonyet/linux-2021q3quiz1/commit/088f729d057c3b941dc6db89b30739ccca8c9a0a)
- 不支援 PPID - [commit 34967eb](https://github.com/ekangmonyet/linux-2021q3quiz1/commit/34967eba99b814b7dd4afa7abb9ebdf862af2afe)
- ftrace API 變動 - [commit b5ebce0](https://github.com/ekangmonyet/linux-2021q3quiz1/commit/b5ebce096b2c7545c25a3de557184677df71c731)
- `rmmod` 後無法再 `insmod` (缺少 cleanup code ,感謝 @st9540808 發現) - [commit 05f5c25](https://github.com/ekangmonyet/linux-2021q3quiz1/commit/05f5c25fb5267c8805d04c56de929ea329f476bd)
## 更新到 5.11+
### 1. `ftrace_func_t` prototype 的變動
> https://lore.kernel.org/lkml/20201113171939.162178036@goodmis.org/
callback prototype 中的 `struct pt_regs *regs` 在 5.11 後改成了 `struct ftrace_regs *fregs` 來包裝 `struct pt_regs*`
### 2. `FTRACE_OPS_FL_RECURSION_SAFE` 沒了
> https://lore.kernel.org/lkml/20201106023547.904270143@goodmis.org/
同樣在 5.11 後, 所有 callback 都應該要是 recursion safe ,不需要加 flag 註明。因為在 callback 中有測試了 `within_module()`,判定 `hook_ftrace_thunk` 是安全的,因此也不須要開新的 `FTRACE_OPS_FL_RECURSION` 來保護。
### 3. `kallsyms_lookup_name` 也沒了
> [Unexporting kallsyms_lookup_name()](https://lwn.net/Articles/813350/)
除了可以用 [kallsyms_mod](https://github.com/h33p/kallsyms-mod) 之外,`find_ge_pid()` 的實作其實很簡單 ([kernel/pid.c](https://elixir.bootlin.com/linux/latest/source/kernel/pid.c#L518)),當中需要用到的 `idr_get_next()` 則是在 [lib/idr.c](https://elixir.bootlin.com/linux/latest/source/lib/idr.c#L264) 被 export,可放心使用。因此只需要將 `find_ge_pid()` 再實作一次就可以了。
```c
/*
* Used by proc to find the first pid that is greater than or equal to nr.
*
* If there is a pid at nr this function is exactly the same as find_pid_ns.
*/
struct pid *find_ge_pid(int nr, struct pid_namespace *ns)
{
return idr_get_next(&ns->idr, &nr);
}
```
而 `ftrace_set_filter_ip()` 則可以直接用 `ftrace_set_filter(..., "find_ge_pid", ...)` 來 hook 所有 `find_ge_pid()` 的呼叫。
## 支援 PPID
> [commit 34967eb](https://github.com/ekangmonyet/linux-2021q3quiz1/commit/34967eba99b814b7dd4afa7abb9ebdf862af2afe)
給定一個 PID ,能呼叫 `pid_task()` 取得它的 `struct task_struct` 並從中找到它的**親父** _(在 0.12 以前是存在 `long father` 裡)_ `struct task_struct *real_parent`,並得到親父的 PID (也就是 PPID )。其中一種簡單的實作方法是在檢查一個 PID 是否被隱藏時,也檢查它的 PPID ,這樣就能把它今後所有的子行程隱藏起來。
---
## How ftrace works
### DISCLAIMER
主要從程式碼反推猜測運作之原理,絕對會漏掉或是有錯
### 正文
當程式用 `gcc -pg` 編譯的時候,所有 function call 都會置入一個 `mcount()` 的呼叫: _(或是 `_mcount` , `__mcount` 等等)_
```diff
void func(...)
{
+ mcount();
// your code here
}
```
:::warning
`mcount` 其實來自 GNU toolchain,參見 [Implementation of Profiling](https://sourceware.org/binutils/docs/gprof/Implementation.html)
:notes: jserv
:::
_這裡的 mcount 應該是由 linux 核心定義的 (?)_,當中會呼叫 ftrace 相關的函式 (每個架構都有各自的實作),並可運用 `ftrace_location(ip)` 來確認一個 instruction pointer 有沒有掛 function tracer 並呼叫它。可推論呼叫 `register_function_tracer()` 時便會更新相關的 data structrure 。
### 探索 Filter hash
...
### ftrace_hook
...
---
## hideproc 原理
### procfs, ps 和 find_ge_pid
在使用 `ps` 命令來列出所有行程時,它會讀取 `/proc` 的資訊,也就是核心裡的 `procfs` 。而底下的 structure 可以當成一個 ordered list,`procfs` 能呼叫 `find_ge_pid(number, ...)` 來尋找 PID 為大於等於 `number` 的行程。不斷呼叫便能列出所有的行程。
### ftrace hook
首先透過 `ftrace_set_filter` 和 `register_ftrace_function` 來設定 hook 。當 `find_ge_pid()` 被呼叫時,因為已經被 trace ,便會先呼叫註冊過的 callback `hook_ftrace_thunk()` 。callback 裡的關鍵:
```c
regs->ip = hook->func
```
此行把 register 裡的 `ip` (instruction pointer) 指向 `hook->func` 的位置,使 CPU 下一個指令從設定好的 `hook_find_ge_pid` 開始。這和直接呼叫 `hook->func()` 關鍵的差異在於不會更動到其他 register ,尤其是 return pointer 。這樣在 `hook_find_ge_pid()` 裡 `return()` 時會直接 return 到真正 `find_ge_pid()` 的 caller ,而不是回到 `find_ge_pid` 裡面。簡言之,整個 `find_ge_pid()` 的函式就這樣被 patch 掉了。
### is_hidden_proc
若把行程列想成一個簡單的 ordered list:
```graphviz
digraph {
node[shape=record]
rankdir="LR"
1->2->5
}
```
```c
find_ge_pid(1) = 1
find_ge_pid(2) = 2
find_ge_pid(3) = 5
```
透過 hook 來包裝便可達到隱匿的效果:
```c
find_ge_pid(2)
|- hook_find_ge_pid(2)
| |- real_find_ge_pid(2)
| | |- return 2
| |- is_hidden(2) = true
| |- real_find_ge_pid(3)
| | |- return 5
| |- is_hidden(5) = false
| |- return 5
|- return 5
```
```graphviz
digraph {
node[shape=record]
rankdir="LR"
1->2 [style=dashed, color=red]
2 [color=red]
2->5 [style=dashed, color=red]
1->5
}
```
而 `is_hidden` 可以透過任何支援 add , remove , find 的資料結構來實作(比如 linked list )。