Linux内核之文件驱动随笔
前言
近期需要实现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@ubuntu:~# strace -e trace=mkdir,mkdirat mkdir Test
mkdirat(AT_FDCWD,"Test", 0777) = 0
+++ exited with 0 +++
上述结果输出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文件可以进行使用。