---
# System prepended metadata

title: Firmadyne MIPS kernel-v2.6.32 `hooks.c`
tags: [firmadyne]

---

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