Try   HackMD

2021q3 Homework1 (quiz1)

contributed by <93i7xo2>

Source: quiz1

解釋上述程式碼運作原理,包含 ftrace 的使用

ftrace 的功用是在目標函式前附加上 callback 來追蹤在 kernel 內的流程,使用這項功能必須用 struct ftrace_ops 來告知 ftrace 何者為 callback。

struct ftrace_hook {
  ...
  struct ftrace_ops ops;
};

啟用/停用方法如下:

register_ftrace_function(&ops);
unregister_ftrace_function(&ops);

ftrace_set_filter() 對特定名稱的函式進行 hook,若函式名稱重複可以指定 ip 的方式進行,hook_install() 即採用後者:

ftrace.c

/**
 * ftrace_set_filter_ip - set a function to filter on in ftrace by address
 * @ops - the ops to set the filter with
 * @ip - the address to add to or remove from the filter.
 * @remove - non zero to remove the ip from the filter
 * @reset - non zero to reset all filters before applying this filter.
 *
 * Filters denote which functions should be enabled when tracing is enabled
 * If @ip is NULL, it fails to update filter.
 */
int ftrace_set_filter_ip(struct ftrace_ops *ops, unsigned long ip,
			 int remove, int reset)
ret = ftrace_set_filter(&ops, "schedule", strlen("schedule"), 0);

ret = ftrace_set_filter_ip(&ops, ip, 0, 0);

在 callback 換掉 regs->ip 達到劫持的作用:

static void notrace hook_ftrace_thunk(unsigned long ip, unsigned long parent_ip,
                                      struct ftrace_ops *ops,
                                      struct pt_regs *regs) {
  ...
    regs->ip = (unsigned long)hook->func;
}

hook_install() 行為

  1. kallsyms_lookup_name 找尋名稱為 find_ge_pid 的 address
  2. 設定 func & flags,呼叫 ftrace API 進行 hook

Problem:

  1. hook_find_ge_pid 行為不明,明明有簡單的 find_pid_ns 可用。
static struct pid *hook_find_ge_pid(int nr, struct pid_namespace *ns) {
  struct pid *pid = real_find_ge_pid(nr, ns);
  while (pid && is_hidden_proc(pid->numbers->nr))
    pid = real_find_ge_pid(pid->numbers->nr + 1, ns);
  return pid;
}
  1. 使用hook.func = hook_find_ge_pid; 而非 hook.func = &hook_find_ge_pid;

本程式僅在 Linux v5.4 測試,若你用的核心較新,請試著找出替代方案

$ uname -r
5.4.0-80-generic

本核心模組只能隱藏單一 PID,請擴充為允許其 PPID 也跟著隱藏,或允許給定一組 PID 列表,而非僅有單一 PID

隱藏 ppid

To-do

多重 pid 輸入

device_write() 內以 strsep 分離字串(核心內無法使用 strtok),達到多個 pid 同時輸入的可能,最後去除輸入時重複 pid 的 entry。

static ssize_t device_write(struct file *filep, const char *buffer, size_t len,
                            loff_t *offset) {
...
  while ((found = strsep(&start, " ")) != NULL) {
    if ((ret = kstrtol(found, 10, &pid)) == 0) {
      switch (input_mode) {
      case _ADD:
        hide_process(pid);
        break;
      case _DEL:
        unhide_process(pid);
        break;
      }
    }
  }

  /* remove duplicate entries */
  list_sort(priv, &hidden_proc, &list_cmp);
  list_for_each_entry_safe(proc, tmp_proc, &hidden_proc, list_node) {
    if (&tmp_proc->list_node != (&hidden_proc) && tmp_proc->id == proc->id) {
      list_del(&proc->list_node);
      kfree(proc);
    }
  }
...
}

假設 pid = {1, 2, 7, 8, 6} 的 process 存在,批次隱藏/復原 pid 的步驟如下:

$ sudo insmod hideproc.ko
$ echo "add 8 7 6" | sudo tee /dev/hideproc
add 8 7 6
$ echo "del 1 2 8" | sudo tee /dev/hideproc
del 1 2 8
$ sudo cat /dev/hideproc
pid 6
pid 7
$ echo "del 6" | sudo tee /dev/hideproc
del 6
$ sudo cat /dev/hideproc
pid 7
$ sudo rmmod hideproc

指出程式碼可改進的地方,並動手實作

a. module init/exit

適當的初始化及釋放資源

static int _hideproc_init(void) {
  int dev_major;
  printk(KERN_INFO "@ %s\n", __func__);
  if (alloc_chrdev_region(&dev, 0, MINOR_VERSION, DEVICE_NAME) < 0) {
    return -1;
  }
  dev_major = MAJOR(dev);

  cdev_init(&cdev, &fops);
  if (cdev_add(&cdev, MKDEV(dev_major, MINOR_VERSION), 1) == -1) {
    unregister_chrdev_region(dev, 1);
    return -1;
  }

  if (IS_ERR(hideproc_class = class_create(THIS_MODULE, DEVICE_NAME))) {
    cdev_del(&cdev);
    unregister_chrdev_region(dev, 1);
    return -1;
  }

  if (IS_ERR(device_create(hideproc_class, NULL,
                           MKDEV(dev_major, MINOR_VERSION), NULL,
                           DEVICE_NAME))) {
    class_destroy(hideproc_class);
    cdev_del(&cdev);
    unregister_chrdev_region(dev, 1);
    return -1;
  }

  init_hook();

  return 0;
}
static void _hideproc_exit(void) {
  hook_remove(&hook);
  device_destroy(hideproc_class, MKDEV(MAJOR(dev), MINOR_VERSION));
  class_destroy(hideproc_class);
  cdev_del(&cdev);
  unregister_chrdev_region(dev, 1);
  printk(KERN_INFO "@ %s\n", __func__);
}

卸載模組前告知 ftrace 並釋放 list 上所有元素

void hook_remove(struct ftrace_hook *hook) {
  pid_node_t *proc, *tmp_proc;
  list_for_each_entry_safe(proc, tmp_proc, &hidden_proc, list_node) {
    list_del(&proc->list_node);
    kfree(proc);
  }
  int err = unregister_ftrace_function(&hook->ops);
  if (err)
    printk("unregister_ftrace_function() failed: %d\n", err);
  err = ftrace_set_filter_ip(&hook->ops, hook->address, 1, 0);
  if (err)
    printk("ftrace_set_filter_ip() failed: %d\n", err);
}