前言
? ? ? ? 近期需要實現linux系統文件防護功能,故此調研了些許知識,如何實現文件防護功能從而實現針對文件目錄防護功能。當被保護的目錄,禁止增刪改操作。通過內核層面實現相關功能,另外在通過跟應用層面交互從而實現具體的業務功能。好了,話不多說,直接開始本文的主題,詳情請見下文(下文實現介紹為ubuntu18.x版本)。
一.查看系統內核函數定義
如上所示,在系統中,每個系統函數都定義一個函數對應的原型號,如上mkdir方法定義的類型宏號表示。紅框中,當時終端使用mkdir,內核中最終通過_SYSCALL調用sys_mkdir方法。
sys_mkdir方法的定義:
如上為函數原型的定義,可通過上述的函數定義進行攔截從而實現相關的業務功能。(注:在新的系統架構中,現在都通過寄存器來實現相關的函數定義,例如:
asmlinkage long hook_sys_mkdir(struct pt_regs *regs) {char __user *pathname = (const char __user *)regs->di; // x86_64 的第一個參數umode_t mode = (umode_t)regs->si; // 第二個參數// 鉤子邏輯printk("mkdir: path=%s, mode=%o\n", pathname, mode);// 調用原始系統調用return orig_sys_mkdir(regs);
}
上述的方法定義,省略了函數的個數定義,從而使系統的攔截調用更加的方便靈活,所需的參數直接從pt_regs結構中直接獲取,靈活方便,不用再定義每個方法各自的參數個數以及相關的定義方法。?
二.系統攔截調用實現
????????如下通過自定義鉤子函數來實現系統函數mkdir的攔截調用步驟
? ? ? ? 1.定義鉤子函數
asmlinkage long hook_sys_mkdir(struct pt_regs *regs);
typedef asmlinkage long (*sys_mkdir_ptr)(struct pt_regs *regs);
static sys_mkdir_ptr origin_mkdir;
? ? ? ? 2.獲取系統函數符號表? ?
? ? ? ? 注:不同系統可能獲取符號表方式存在偏差,另外如果和應用層交互時,也可以通過應用層通過netlink將對應的符號表地址傳入內核當中。??
kallsyms_lookup_name_t get_kallsyms_lookup_name(void)
{int ret;kallsyms_lookup_name_t pfun;static struct kprobe kp ={.symbol_name = "kallsyms_lookup_name",};ret = register_kprobe(&kp);if (ret < 0){printk(KERN_INFO "register_kprobe failed, returned %d\n", ret);return NULL;}pfun = (kallsyms_lookup_name_t)kp.addr;unregister_kprobe(&kp);return pfun;
}static int obtain_sys_call_table_addr(unsigned long *sys_call_table_addr)
{unsigned long temp_sys_call_table_addr;kallsyms_lookup_name_t fn_kallsyms_lookup_name = 0;fn_kallsyms_lookup_name = get_kallsyms_lookup_name();if (fn_kallsyms_lookup_name == NULL){printk("Fail to get_allsyms_lookup_name\n");return -1;}temp_sys_call_table_addr = fn_kallsyms_lookup_name("sys_call_table");/* Return error if the symbol doesn't exist */if (0 == temp_sys_call_table_addr){printk("Can not found sys_call_table\n");return -1;}printk("Found sys_call_table: %p", (void *)temp_sys_call_table_addr);*sys_call_table_addr = temp_sys_call_table_addr;return 0;
}
? ? ? ? 3.進行鉤子操作
????????因系統具備寫保護,進行hook時需要先關閉寫保護,鉤子完成之后再將寫保護恢復(函數的hook放在如下disable_cr0和enable_cr0之間)。
寫保護和恢復保護函數處理:? ?
unsigned int disable_cr0(void)
{unsigned int cr0 = 0;unsigned int ret;asm volatile ("movq %%cr0, %%rax": "=a"(cr0));ret = cr0;//清理第16位的標志位cr0 &= 0xfffeffff;asm volatile ("movq %%rax, %%cr0"::"a"(cr0));return ret;
}void enable_cr0(unsigned int val)
{asm volatile ("movq %%rax, %%cr0": : "a"(val));
}
? ? ? ? 函數鉤子更換
// sys_call_table_addr為上述步驟獲取的函數符號表地址void set_hook(){disable_cr0();origin_mkdir = ((unsigned long *)(sys_call_table_addr))[__NR_mkdir];enable_cr0();
}
? ? ? ? 4.mkdir鉤子運行流程
? ? ? ? 自定義函數進行對mkdir函數的攔截,進行自己的業務處理,正常情況再調用原始內核函數
? ? ? ? 注:在某些系統上,可能在內核中調用了其他的函數,例如麒麟系統,mkdir命令,內核中調用的不是sys_mkdir,雖然可以通過上述查詢到,但是實際上調用的是sys_mkdirat.所以在ko中直接進行sys_mkdirat的hook即可。如何查看某個命令內核真正調用哪個內核函數,可以通過strace命令進行查看,例如mkdir命令查看,如下:(ubuntu18.04)
root@ubuntu:~# strace -e trace=mkdir,mkdirat mkdir Test
mkdir("Test", 0777) = 0
+++ exited with 0 +++
root@ubuntu:~#
如上,輸出執行輸出結果,mkdir,那么說明調用內核的sys_mkdir方法。?
? ? ?麒麟v10,aarch64架構,跟上述不一致,詳情見:
[root@localhost]# strace -e trace=mkdir,mkdirat mkdir Test
mkdirat(AT_FDCWD, "Test", 0777) = 0
+++ exited with 0 +++
[root@localhost]#
上述結果輸出mkdirat,跟上述ubuntu差別很大,表示mkdir命令對應調用的是系統內核的sys_mkdirat函數。
綜上兩種情況,再編寫系統攔截方法時,一定需要注意不同結構系統存在的偏差,依據實際情況而定再行決定處理相關hook方式函數。?
asmlinkage long origin_mkdir (struct pt_regs *regs)
{int ret;char *filename = NULL;char *regs_filename = (char *)(regs->di);int file_len = strnlen_user(regs_filename, MAX_PATH);filename = kmalloc(file_len, GFP_KERNEL);if (filename == NULL){printk("Fail to kmalloc\n");goto end;}ret = strncpy_from_user(filename, regs_filename, file_len);if (ret < 0){printk("Fail to strncpy_from_user\n");goto end;}printk("current opetation filename: %s\n", filename);
end:if (filename){kfree(filename);}return old_sys_mkdir(regs);
}
? ? ? ? 5.恢復系統原始函數? ?
? ? ? ? 卸載該驅動文件記得恢復系統原始函數,避免發生崩潰問題。
// sys_call_table_addr為上述步驟獲取的函數符號表地址void restore_hook()
{disable_cr0();((unsigned long *)(sys_call_table_addr))[__NR_mkdir] = origin_mkdir ;enable_cr0();
}
如上就是一個mkdir函數的完成hook過程的步驟,上述的分布代碼將在如下第三標題中完美整合從而實現一個完整的調用并且實現生成一個ko文件可以進行使用。