# 2021q3 Homework1 (quiz1) ###### tags: `linux2021` contributed by < `ekangmonyet` > ## 起點 ![](https://i.imgur.com/lsuAkvS.png) :::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 )。