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 進入時跟離開時分別執行的函數