Try   HackMD

2021q3 Homework1 (quiz1)

contributed by < fdgkhdkgh >

tags: linux2021 kernel

第一週隨堂測驗題

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

在 kernel module 初始化的地方,呼叫 init_hook

static int _hideproc_init(void)
{
.
.
.
    init_hook();

    return 0;
}

struct ftrace_hook {
    const char *name;
    void *func, *orig;
    unsigned long address;
    struct ftrace_ops ops;
};
.
.
.
static void init_hook(void)
{
    // 使用 kallsyms_lookup_name 去找到 linux kernel 中 "find_ge_pid" 這個 function 的位址
    real_find_ge_pid = (find_ge_pid_func) kallsyms_lookup_name("find_ge_pid");
    hook.name = "find_ge_pid";
    
    // 儲存我們想執行的 find_ge_pid 的位址
    hook.func = hook_find_ge_pid;
    
    // 儲存真正的 find_ge_pid 的位址
    hook.orig = &real_find_ge_pid;
    
    // 開始安裝我們在這裡設定的 ftrace hook
    hook_install(&hook);
}

static int hook_install(struct ftrace_hook *hook)
{

    // 再抓取一次我們想 hook 的 kernel function 的位址
    //   若抓取失敗則回傳錯誤碼
    int err = hook_resolve_addr(hook);
    if (err)
        return err;

    // 註冊 ftrace 的 call back function
    hook->ops.func = hook_ftrace_thunk;
    
    
    hook->ops.flags = FTRACE_OPS_FL_SAVE_REGS | FTRACE_OPS_FL_RECURSION_SAFE |
                      FTRACE_OPS_FL_IPMODIFY;

    // hook->address : 被我們 hook 的 kernel function
    // ftrace_set_filter_ip : 哪些 ip address 可以使用這個 ftrace 的 call back function。 
    //   e.g. 這邊使用的是真正的 find_ge_pid 的位址,表示當我們呼叫 find_ge_pid 時,
    //   會進入此 ftrace 設定的 call back function ( 也就是 hook_ftrace_thunk )
    //   而呼叫其他的 function 則不會進入此 ftrace 的 call back function
    err = ftrace_set_filter_ip(&hook->ops, hook->address, 0, 0);
    if (err) {
        printk("ftrace_set_filter_ip() failed: %d\n", err);
        return err;
    }

    // 註冊這個 ftrace,並開始啟用
    err = register_ftrace_function(&hook->ops);
    if (err) {
        printk("register_ftrace_function() failed: %d\n", err);
        
        // 註冊失敗的話,要把這個 filter 關掉
        ftrace_set_filter_ip(&hook->ops, hook->address, 1, 0);
        
        return err;
    }
    return 0;
}

設定好後,每當 linux kernel 裡除了我們的 kernel module 以外的任何地方呼叫 "find_ge_pid" ,就會執行我們所設定的 frace 的 call back function "hook_ftrace_thunk"。

static void notrace hook_ftrace_thunk(unsigned long ip,
                                      unsigned long parent_ip,
                                      struct ftrace_ops *ops,
                                      struct pt_regs *regs)
{
    struct ftrace_hook *hook = container_of(ops, struct ftrace_hook, ops);

    // 假如 caller 的 instruction pointer 並不在我們這個 kernel module 的範疇內,
    //   就將其 instruction pointer 指向我們這個 kernel module 版本的 hook_find_ge_pid
    if (!within_module(parent_ip, THIS_MODULE))
        regs->ip = (unsigned long) hook->func;
}

從 ftrace 的 call back function "hook_ftrace_thunk" 會跳轉到我們的 kernel module 所實作的 hook_find_ge_pid。

static struct pid *hook_find_ge_pid(int nr, struct pid_namespace *ns)
{
    struct pid *pid = real_find_ge_pid(nr, ns);
    
    // 只要想要搜尋的 pid_t 位於 hidden_list 內,就讓 "real_find_ge_pid" 去搜尋 pid_t + 1 ,藉此隱藏 pid_t
    while (pid && is_hidden_proc(pid->numbers->nr))
        pid = real_find_ge_pid(pid->numbers->nr + 1, ns);
    return pid;
}

若你用的核心較新,請試著找出替代方案

使用的是 linux-5.4.134 ,剛好不會碰上這個問題。

$ uname -a
Linux tommy-X450VP 5.4.134 #1 SMP Sun Jul 25 17:18:47 CST 2021 x86_64 x86_64 x86_64 GNU/Linux

本核心模組只能隱藏單一 PID,請擴充為允許其 PPID 也跟著隱藏

  1. 使用 find_vpid 從 pid_t 拿到 struct pid
  2. 再用 pid_task 從 struct pid 拿到 struct task_struct
  3. 使用 task_ppid_nr 從 struct task_struct 拿到 ppid
  4. 把 pid 以及 ppid 都用 hide_process 來隱藏起來
static ssize_t device_write(struct file *filep, const char *buffer, size_t len, loff_t *offset) { long pid; char *message; char add_message[] = "add", del_message[] = "del"; if (len < sizeof(add_message) - 1 && len < sizeof(del_message) - 1) return -EAGAIN; message = kmalloc(len + 1, GFP_KERNEL); memset(message, 0, len + 1); copy_from_user(message, buffer, len); if (!memcmp(message, add_message, sizeof(add_message) - 1)) { kstrtol(message + sizeof(add_message), 10, &pid); struct task_struct *task = pid_task(find_vpid(pid), PIDTYPE_PID); struct pid_t *ppid = task_ppid_nr(task); hide_process(pid); hide_process(ppid); } else if (!memcmp(message, del_message, sizeof(del_message) - 1)) { kstrtol(message + sizeof(del_message), 10, &pid); unhide_process(pid); } else { kfree(message); return -EAGAIN; }

允許給定一組 PID 列表,而非僅有單一 PID

使用簡易的 parser 將 PID 列表裡的所有 PID 用 hide_process 隱藏起來

static ssize_t device_write(struct file *filep, const char *buffer, size_t len, loff_t *offset) { long pid; char *message; char add_message[] = "add", del_message[] = "del"; if (len < sizeof(add_message) - 1 && len < sizeof(del_message) - 1) return -EAGAIN; message = kmalloc(len + 1, GFP_KERNEL); memset(message, 0, len + 1); copy_from_user(message, buffer, len); if (!memcmp(message, add_message, sizeof(add_message) - 1)) { hide_processes((char *) message + sizeof(add_message)); } else if (!memcmp(message, del_message, sizeof(del_message) - 1)) { unhide_processes((char *) message + sizeof(del_message)); } else { kfree(message); return -EAGAIN; } *offset = len; kfree(message); return len; } . . . void hide_processes(char *pids) { char num[32]; memset(num, 0, sizeof(num)); int stat = 0; int i = 0; int numi = 0; long pid = 0; while(pids[i] != 0) { if(stat == 0) { if(pids[i] >= '0' && pids[i] <= '9') { num[numi++] = pids[i]; stat = 1; } } else if(stat == 1) { if(pids[i] >= '0' && pids[i] <= '9') { num[numi++] = pids[i]; } else { kstrtol(num, 10, &pid); printk("%d\n", pid); hide_process(pid); memset(num, 0, sizeof(num)); numi = 0; stat = 0; } } i++; } if(num[0] != 0) { kstrtol(num, 10, &pid); printk("%d\n", pid); hide_process(pid); } }

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

釋放 ftrace 的 hook

在釋放 kernel module 前使用老師寫好的 hook_remove 將 hook 釋放。

static void _hideproc_exit(void) { printk(KERN_INFO "@ %s\n", __func__); hook_remove(&hook); }

處理重複加兩次相同 pid 的情形

避免同一個 pid 重複出現在 linked list 裡。

static int hide_process(pid_t pid) { if(!is_hidden_proc(pid)) { pid_node_t *proc = kmalloc(sizeof(pid_node_t), GFP_KERNEL); proc->id = pid; // TODO list_add(&(proc->list_node), &hidden_proc); } return SUCCESS; }

使用 rbtree 實作 hideproc

插入節點由 O(1) 變慢成 O(logn)
刪除節點由 O(n) 加快成 O(logn)
查詢節點由 O(n) 加快成 O(logn)

https://github.com/fdgkhdkgh/linux2021-summer-q1/tree/main/main_rb