Try  HackMD Logo HackMD

2021q3 Homework1 (quiz1)

contributed by < vectorr >

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

static void init_hook(void)
{
    real_find_ge_pid = (find_ge_pid_func) kallsyms_lookup_name("find_ge_pid");
    hook.name = "find_ge_pid";
    hook.func = hook_find_ge_pid;
    hook.orig = &real_find_ge_pid;
    hook_install(&hook);
}

從程式碼看,是 find_ge_pid 被自訂的 hook_find_ge_pid 所取代。

TODO: 了解使用 ftrace hook function 的機制。

以下是推導如何使用修改 find_ge_pid 來隱藏 cron 的 pid 的過程:

  1. strace 觀察 pidof cron 使用了哪些系統呼叫。

    ​​​​chdir("/proc")                          = 0
    ​​​​openat(AT_FDCWD, ".", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3
    ​​​​fstat(3, {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
    ​​​​brk(NULL)                               = 0x55be244e8000
    ​​​​brk(0x55be24509000)                     = 0x55be24509000
    ​​​​getdents64(3, /* 364 entries */, 32768) = 9568
    ​​​​openat(AT_FDCWD, "1/stat", O_RDONLY)    = 4
    ​​​​fstat(4, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
    ​​​​read(4, "1 (systemd) S 0 1 1 0 -1 4194560"..., 4096) = 195
    ​​​​read(4, "", 3072)                       = 0
    ​​​​close(4)                                = 0
    ​​​​openat(AT_FDCWD, "1/cmdline", O_RDONLY) = 4
    ​​​​fstat(4, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
    ​​​​read(4, "/sbin/init\0splash\0", 1024)   = 18
    ​​​​close(4)                                = 0
    ​​​​stat("/proc/1/exe", 0x7ffead440920)     = -1 EACCES (Permission     denied)
    ​​​​readlink("/proc/1/exe", 0x7ffead4419d0, 4096) = -1 EACCES (Permission denied)
    ​​​​openat(AT_FDCWD, "2/stat", O_RDONLY)    = 4
    ​​​​fstat(4, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
    ​​​​read(4, "2 (kthreadd) S 0 0 0 0 -1 212998"..., 4096) = 150
    ​​​​read(4, "", 3072)                       = 0
    ​​​​close(4)                                = 0
    ​​​​openat(AT_FDCWD, "2/cmdline", O_RDONLY) = 4
    

    在進入 /proc ( chdir("/proc") ) 後,呼叫 getdents64,然後就開始依序地進入每個 pid 的目錄,讀取 stat, cmdline, exe 的內容,這邊推測應該是在比對有沒有 cron 字串。
    getdents64 系統呼叫,通常是透過 C 執行時期函式庫的 readdir 去呼叫,是要取得這個目錄內的子目錄列表。

    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →
    留意用語:

    • directory 的翻譯是「目錄」,這從 1960 年代就使用
    • folder 的翻譯才是「檔案夾」,這是 MS-Windows 引入的詞彙

    Linux 沿襲 UNIX 傳統,使用 "directory"

    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →
    jserv

    好的,已更改,謝謝~Vectorr

    推測 pidof 是取得 /proc 下的所有子目錄名稱,然後分別輪詢每個目錄內的相關檔案是否有 cron 的字串,因為有相關檔案的子目錄的名稱即是此 process 的 pid,藉此取得 cron 的 pid。

    從完整的 strace 輸出看出,當使用這個 driver 隱藏 cron 的 pid 後,不會去輪詢到 cron pid 的那個子目錄,推測 getdents64 在當下不會回傳 cron 的 pid 那個子目錄。

  2. 接下來要尋找 find_ge_pidgetdents64 有什麼關係, trace 一下 kernel source code 得到下面的呼叫關係。

    ​​​​find_ge_pid <- next_tgid <- proc_pid_readdir <- proc_root_readdir
    

    再用 ftrace 來追蹤 pidof cron 使用到的 function call,也可以看到 proc_root_readdirproc_pid_readdir

    ​​​​pidof-54198   [002] .... 184843.742896: ksys_getdents64 <-__x64_sys_getdents64
    ​​​​pidof-54198   [002] .... 184843.742896: __fdget_pos <-ksys_getdents64
    ​​​​pidof-54198   [002] .... 184843.742896: __fget_light <-__fdget_pos
    ​​​​pidof-54198   [002] .... 184843.742896: iterate_dir <-ksys_getdents64
    ​​​​pidof-54198   [002] .... 184843.742896: security_file_permission <-iterate_dir
    ​​​​pidof-54198   [002] .... 184843.742896: apparmor_file_permission <-security_file_permission
    ​​​​pidof-54198   [002] .... 184843.742896: common_file_perm <-apparmor_file_permission
    ​​​​pidof-54198   [002] .... 184843.742896: aa_file_perm <-common_file_perm
    ​​​​pidof-54198   [002] .... 184843.742897: __fsnotify_parent <-security_file_permission
    ​​​​pidof-54198   [002] .... 184843.742897: fsnotify <-security_file_permission
    ​​​​pidof-54198   [002] .... 184843.742897: down_read_killable <-iterate_dir
    ​​​​pidof-54198   [002] .... 184843.742897: _cond_resched <-down_read_killable
    ​​​​pidof-54198   [002] .... 184843.742897: rcu_all_qs <-_cond_resched
    ​​​​pidof-54198   [002] .... 184843.742897: proc_root_readdir <-iterate_dir
    ​​​​pidof-54198   [002] .... 184843.742897: proc_pid_readdir <-proc_root_readdir
    

    kernel裡的 x86_64 的 system call table arch/x86/entry/syscalls/syscall_64.tbl 可以看到 217 common getdents64 __x64_sys_getdents64 ,即 getdents64 會觸發 __x64_sys_getdents64 被呼叫。
    再搭配上面 ftrace pidof cron 的紀錄可以看到,__x64_sys_getdents64 會一路呼叫到 proc_pid_readdir
    再根據更早之前得到的呼叫關係可以得到 find_ge_pid 是一路從 user space 呼叫 getdents64 system call,然後跳到 kernel space 後觸發 __x64_sys_getdents64 被呼叫,然後一路被呼叫到的。

    TODO: 不知道為什麼有的 function 不會出現在 ftrace 的輸出,像 find_ge_pid,應該是有什麼規則,需要再去了解。

  3. 從上面的追蹤推論,pidof cron 會去取得 /proc 的所有子目錄列表,根據檢查 cron 是在哪個 pid 的子目錄中,來判斷 cron 的 pid 為何。在取得 /proc 的子目錄的路徑中,有一個地方會呼叫到 find_ge_pid ,我們透過 ftrace hook function 的機制, 用 hook_find_ge_pid 去取代原本 find_ge_pid 的行為。透過回傳 pid 時,把我們要隱藏的pid跳掉,使得回傳的子目錄列表缺少了被我們隱藏的 pid 所屬的目錄。

    ​​​​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;
    ​​​​}
    

擴充允許其 PPID 也跟著隱藏,或允許給定一組 PID 列表,而非僅有單一 PID

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

  1. 資源釋放,包含下面各項目 (commit 1df7d1d)
    1. character device 相關資源。
    2. class (/sys) 相關資源。
    3. ftrace hook function 的恢復。
    4. driver 內建 hidden pid list 的釋放。
  2. 修正 unhide_process 會把目前所有放在 list 中要被隱藏的所有 pid 都刪除的問題。(commit 847450a)
  3. 將 global variables (hidden_proc, hook, cdev, hideproc_class, dev) 包裝在一個屬於這個 module 的自訂 struct 裡。(commit 6387153)