Firmadyne MIPS kernel-v2.6.32 `hooks.c` === --- ###### tags: `firmadyne` 本篇紀錄 `hooks.c` 閱讀筆記 目標是了解探測器(probe)使用方式 Link: https://github.com/firmadyne/kernel-v2.6.32/blob/cf452ab2c3673998f037bcd0a8662f724d15286b/drivers/firmadyne/hooks.c --- **目錄:** [TOC] # 分析 Source Code Source code 中定義了許多 hooks,先只追其中一個 hook 方便觀察。 選定觀察 `__inet_insert_ifa` 的 hook。 --- ```c=31 #define SYSCALL_HOOKS \ ... /* Hook assignments of IP addresses to network interfaces */ \ HOOK("__inet_insert_ifa", inet_hook, inet_probe) \ ... ``` `SYSCALL_HOOKS` 定義了許多 `HOOK` Macro --- ```c=321 #define HOOK(a, b, c) \ static struct jprobe c = { \ .entry = b, \ .kp = { \ .symbol_name = a, \ }, \ }; SYSCALL_HOOKS #undef HOOK ``` 定義 `HOOK` 後,緊接著寫下 `SYSCALL_HOOKS` 所以所有 `SYSCALL_HOOKS` 裡的每一條 `HOOK` 都會被展開成 Line 321 ~ 327 的長相 以 `HOOK("__inet_insert_ifa", inet_hook, inet_probe)` 來說 會被展開成 ```c static struct jprobe inet_probe = { .entry = inet_hook, .kp = { .symbol_name = "__inet_insert_ifa", }, }; ``` --- 在 Line 333 的 `register_probes()` 實作中 ```c=342 #define HOOK(a, b, c) \ if ((tmp = register_jprobe(&c)) < 0) { \ printk(KERN_WARNING MODULE_NAME": register jprobe: %s = %d\n", c.kp.symbol_name, tmp); \ ret = tmp; \ } SYSCALL_HOOKS #undef HOOK ``` 以 `HOOK("__inet_insert_ifa", inet_hook, inet_probe)` 來說 會被展開成 ```c if((tmp = register_jprobe(&inet_probe)) < 0) { printk(KERN_WARNING MODULE_NAME": register jprobe: %s = %d\n", inet_probe.kp.symbol_name, tmp); ret = tmp; } ``` --- 在 Line 355 的 `unregister_probes()` 實作中 ```c=359 #define HOOK(a, b, c) \ unregister_jprobe(&c); SYSCALL_HOOKS #undef HOOK ``` 以 `HOOK("__inet_insert_ifa", inet_hook, inet_probe)` 來說 會被展開成 ```c unregister_jprobe(&inet_probe); ``` --- 那 `register_jprobe()` 到底是什麼呢? # probe 在 Kernel Source Code 中 參考: https://elixir.bootlin.com/linux/latest/source 發現新版的 Kernel,在 `/samples/kprobes` 中有 `kprobe_example.c` 而舊版 Kernel (e.g. v2.6.39.4),同樣位置還多有 `jprobe_example.c` 在 [kprobes: Abolish jprobe APIs](https://lwn.net/Articles/735667/) 一文中,知道了原來新版的 Kernel 已棄用 jprobe 了 ## probe 種類 probe 有分成兩種 - kprobes: 能在 functions 的任何位置插入探測器 - kretprobes (return probes): 在 functions return 後觸發的探測器 而新版 kernel 已棄用 jprobes 參考 [Kernel Probes](https://www.kernel.org/doc/Documentation/kprobes.txt) ## 基本觀念 kprobes 的用法大概是先寫一個 kernel module,在 init function 中註冊(register) probes,在 exit function 中 unregister 這些 probes。 而註冊探測器的函數就是 `register_kprobe()` 指定了探測器插入在哪邊以及當觸法探測器後要 call 什麼 function (handler) ## 所以剛剛的 jprobe ... ### 先看看 example 參考 [/samples/kprobes/jprobe_example.c](https://elixir.bootlin.com/linux/v2.6.39.4/source/samples/kprobes/jprobe_example.c) > Here's a sample kernel module showing the use of jprobes to dump > the arguments of do_fork(). ```c=25 /* Proxy routine having the same arguments as actual do_fork() routine */ static long jdo_fork(unsigned long clone_flags, unsigned long stack_start, struct pt_regs *regs, unsigned long stack_size, int __user *parent_tidptr, int __user *child_tidptr) { printk(KERN_INFO "jprobe: clone_flags = 0x%lx, stack_size = 0x%lx," " regs = 0x%p\n", clone_flags, stack_size, regs); /* Always end with a call to jprobe_return(). */ jprobe_return(); return 0; } static struct jprobe my_jprobe = { .entry = jdo_fork, .kp = { .symbol_name = "do_fork", }, }; ``` jprobe 是什麼呢,參考 [/include/linux/kprobes.h](https://elixir.bootlin.com/linux/v2.6.39.4/source/include/linux/kprobes.h#L158) ```c=158 struct jprobe { struct kprobe kp; void *entry; /* probe handling code to jump to */ }; ``` 大概可以猜到,jprobe 中的 kprobe kp 的 symbol_name 欄位 就是指此 jprobe 要插入在進入什麼 function 之前 而 entry 欄位則是定義 handler 是哪個 function 繼續看 [/samples/kprobes/jprobe_example.c](https://elixir.bootlin.com/linux/v2.6.39.4/source/samples/kprobes/jprobe_example.c) ```c=46 static int __init jprobe_init(void) { int ret; ret = register_jprobe(&my_jprobe); if (ret < 0) { printk(KERN_INFO "register_jprobe failed, returned %d\n", ret); return -1; } printk(KERN_INFO "Planted jprobe at %p, handler addr %p\n", my_jprobe.kp.addr, my_jprobe.entry); return 0; } ``` 定義好 jprobe 後,還要真的透過呼叫 `register_jprobe()` register jprobe 後才會作用,return 值為負表示 register 失敗。 ```c=60 static void __exit jprobe_exit(void) { unregister_jprobe(&my_jprobe); printk(KERN_INFO "jprobe at %p unregistered\n", my_jprobe.kp.addr); } ``` __exit 時要 `unregister_jprobe()` ```c=66 module_init(jprobe_init) module_exit(jprobe_exit) MODULE_LICENSE("GPL"); ``` 定義此 kernel module 進入時跟離開時分別執行的函數 function,並定義 MODULE_LICENSE 資訊。 ### 接著回頭解釋 所以 hooks.c 會有 inet_probe 的定義,前面解釋過了 ```c static struct jprobe inet_probe = { .entry = inet_hook, .kp = { .symbol_name = "__inet_insert_ifa", }, }; ``` 意思就是,在進入 `__inet_insert_ifa()` 之前,會先 call handler `inet_hook()` [hooks.c](https://github.com/firmadyne/kernel-v2.6.32/blob/cf452ab2c3673998f037bcd0a8662f724d15286b/drivers/firmadyne/hooks.c) : ```c=303 static void inet_hook(struct in_ifaddr *ifa, struct nlmsghdr *nlh, u32 pid) { if (syscall & LEVEL_NETWORK) { printk(KERN_INFO MODULE_NAME": __inet_insert_ifa[PID: %d (%s)]: device:%s ifa:0x%08x\n", task_pid_nr(current), current->comm, ifa->ifa_dev->dev->name, ifa->ifa_address); } jprobe_return(); } ``` handler 的參數跟被 hook 的 function 一樣 並且在 Line 333 的 `register_probes()` 註冊探測器 在 Line 355 的 `unregister_probes()` 取消註冊探測器 # Firmadyne 在哪註冊了探測器 前面的分析都結束後,那下個問題是,所以到底是在哪邊呼叫了 `register_probes()`? 在 [firmadyne.c](https://github.com/firmadyne/kernel-v2.6.32/blob/cf452ab2c3673998f037bcd0a8662f724d15286b/drivers/firmadyne/firmadyne.c) 的 init function ```c=55 if ((tmp = register_probes()) < 0) { printk(KERN_WARNING MODULE_NAME": register_probes() = %d\n", tmp); ret = tmp; } ``` 並在 exit function 呼叫了 `unregister_probes()` ```c=70 void __exit cleanup_module(void) { unregister_devfs_stubs(); unregister_probes(); unregister_procfs_stubs(); unregister_reboot_notifier(&reboot_cb); } ``` ```c=78 module_init(init_module); module_exit(cleanup_module); ``` 定義了此 kernel module 進入時跟離開時分別執行的函數