contributed by < hankluo6
>
The ftrace infrastructure was originially created to attach callbacks to the beginning of functions in order to record and trace the flow of the kernel. But callbacks to the start of a function can have other use cases. Either for live kernel patching, or for security monitoring.
自訂 ftrace_hook
方便我們建立 ftrace hook。其中 name
為 kernel 內要被取代的原始函式名字;orig
為原始函式地址;func
為要 hook 的函式而 ops
則儲存 ftrace 內部執行 callback 需要的資料。
透過 init_hook
開始建立 hook function:
kallsyms_lookup_name
可以取得對應函式在 kernel 內的位址,並設置相關參數。
hook_resolve_addr
給定 hook-address
要被取代的函式在 kernel 內的位置(與 hook.orig
相同);hook->ops.func
則為 ftrace 在進入 kernel 內函式時,真正會執行的 callback function;ftrace_set_filter_ip
及 register_ftrace_function
皆為讓 ftrace 知道我們需要註冊的函數資訊。
ftrace 在追蹤時主要會呼叫此 callback function,而內部通過 regs->ip
(其意義等同於 x86 內的 %rip
暫存器,儲存下個指令的位置)直接將下次指令改為我們想要執行的 function (此處為 hook_find_ge_pid
)。within_module
用來防止遞迴呼叫此函式,parent_id
會指向呼叫此參數的地址。
透過上述的 ftrace hook,我們可以把 kernel 內的 find_ge_pid
改為我們撰寫的 hook_find_ge_pid
來執行。而 pidof
內部應會使用 find_ge_pid
來實現,而被改成執行 hook_find_ge_pid
。
hook_find_ge_pid
利用 real_find_ge_pid
取得原本的 pid
,如果此 process 為我們隱藏的 process,則強制搜尋其 pid
值加一的 process,便能防止回傳到我們 process 的 pid。
在 module_exit
時,要正確的解除 ftrace 的綁定,否則下次 insmod
時可能會出現錯誤,將 hook_remove
的 condition build 移除,並在 _hideproc_exit
增加 hook_remove(&hook);
。
此時呼叫 insmod
後再透過 rmmod
卸載後,要再重新安裝一次 driver 時會出現以下錯誤:
kobject_add_internal failed for hideproc with -EEXIST, don't try to register things with the same name in the same directory.
這是因為原本 hook_remove
內沒有完全釋放所有註冊的裝置,可以透過以下命令查看:
添加對應的釋放函式即可解決問題
MINOR_VERSION
從原始碼的註解可以理解 alloc_chrdev_region
的參數意義:
其中 baseminor
為指定 range of minor numbers 的第一個數,count
為需要 minor numbers 的數量。
而在原本 hideproc 中指定 0 為 minor numbers 的第一個數,range 則設定為 1
應要改成 alloc_chrdev_region(&dev, MINOR_VERSION, 1, DEVICE_NAME);
才正確。
但如果按照原本程式執行時好像也能得到正確的結果,可以從原始碼中看出端倪,alloc_chrdev_region
內部會呼叫 __register_chrdev_region
分配區域:
第 16 ~ 24 行從沒使用的 devices 內找到一個可用的 device number,重點在 27 ~ 41 行的 for 迴圈。在用過得 devices 中,如果 major 為相同時,其對應的 minor numbers 範圍便不能重疊,第 34 ~ 38 行便是在檢查是否有重疊的情況發生。
但因為 hideproc 中 alloc_chrdev_region
內的 major 參數為 0,表示會由 find_dynamic_major
找到合適的 major number 來使用,意味著不會有兩個 devices 使用相同的 major number,也就不會有重疊的問題產生,所以程式能如期執行。
kallsyms
符號 kallsyms_lookup_name
在 Linux 5.7+ 以上的版本不再對外揭露 (Unexporting kallsyms_lookup_name()),故需要利用其他方法來取得核心內的符號地址。
這邊我使用 Kprobes,添加以下程式碼:
並將原本的 kallsyms_lookup_name
改為 lookup_name
即可。
Kretprobes
[GitHub]
要使用 kprobes 取得 symbol address 後再用 ftrace 去 hook 有點多此一舉的感覺,我們可以使用 kretprobes (kprobes 的一種包裝) 來達到與 ftrace hook 類似的效果。
先宣告一個 kretprobe
物件,其中註冊兩個 function: entry_handler
會在函式 (即 find_ge_pid
) 進入前呼叫;ret_handler
則在函式執行完呼叫。
我們的目標是要更改 find_ge_pid
回傳的內容,所以 entry_handler
可以不用特別改動,參考範例:
重點在於 ret_handler
,我們的目標是在函式結束後,想辦法更改回傳值:
pt_regs
儲存目前在 userspace 下的 stack register,所以可以透過
regs_return_value
回傳剛剛執行的 function 內的 return value。此時就可以透過檢查 find_ge_pid
的回傳是否在我們程式內的 hidden process 中,如果該 process 是我們隱藏的 process 時,直接修改 regs
內對應到 return value 的 register ax
即可。
只有在 x86 架構中, regs->ax
為 return value,其他架構可自行參考 arch/alpha/include/asm/ptrace.h