本博客用到的linux内核版本为4.19.163,不同版本需要对代码进行部分修改。我的代码已经开源到了github仓库。
1.确定需要hook的函数
2.对hook函数进行填充,添加自己的逻辑(安全检查)
3.添加到在security_hook_list的数据结构里
4.对这个有注册逻辑的函数进行注册
这里我们希望在创建进程时进行**“额外的安全检查”,所以需要对task_alloc()**进行hook
1.用户创建进程需要调用系统调用 sys_fork()/sys_vfork()/sys_clone()
2.sys_fork()等函数中调用 do_fork()
3.do_fork()调用copy_process()
4.copy_process()调用security_task_alloc(),这里便是hook点了,可以进行自己的逻辑
5.security_task_alloc()的主体就是security_hook_list的task_alloc()
需要注意的是,这里选择的函数,必须与include/lsm_hook.h中security_list_options的一个成员相匹配(只能选里面有的)。
LSM的核心是Hook技术,struct security_operations是实现Hook技术的核心,即LSM核心中的核心!security_operations结构体中除了一个名字,成员全是函数指针(hook)
union security_list_options {
int (*binder_set_context_mgr)(struct task_struct *mgr);
int (*binder_transaction)(struct task_struct *from,
struct task_struct *to);
int (*binder_transfer_binder)(struct task_struct *from,
struct task_struct *to);
int (*binder_transfer_file)(struct task_struct *from,
struct task_struct *to,
struct file *file);
int (*ptrace_access_check)(struct task_struct *child,
unsigned int mode);
int (*ptrace_traceme)(struct task_struct *parent);
int (*capget)(struct task_struct *target, kernel_cap_t *effective,
kernel_cap_t *inheritable, kernel_cap_t *permitted);
int (*capset)(struct cred *new, const struct cred *old,
const kernel_cap_t *effective,
const kernel_cap_t *inheritable,
const kernel_cap_t *permitted);
int (*capable)(const struct cred *cred, struct user_namespace *ns,
int cap, int audit);
int (*quotactl)(int cmds, int type, int id, struct super_block *sb);
int (*quota_on)(struct dentry *dentry);
int (*syslog)(int type);
int (*settime)(const struct timespec *ts, const struct timezone *tz);
int (*vm_enough_memory)(struct mm_struct *mm, long pages);
....}
在这里,只是为了简单的检测自己的代码是否能够正常运行,所以,只用了简单的printk,详情请看demo_task_create_alloc函数。
int demo_task_alloc(struct task_struct *task,unsigned long clone_flags)
{
printk("[+geek] call task_create(). count=%llu\n", ++count);
return 0;
}
在LSM框架下,每个安全模块需要实现一个"security_hook_list"结构体数组,每个数组项表示一个实现了的hook函数。
security_hook_list 记录单个接口(一个hook点)的信息,然后模块初始化函数中会调用 security_add_hooks 将 security_hook_list 数组链接到上面的 security_hook_heads 上。
struct security_hook_list {
struct hlist_node list;(用于侵入式链表)
struct hlist_head *head;
union security_list_options hook;
char *lsm;
}
security_list_options以union的方式存在(里面定义了很多不同的可hook函数,实现的hook函数必须与里面的一个定义相符合),然后通过侵入式链表连接(成员指向成员),可以有效的节省空间。
static struct security_hook_list demo_hooks[] = {
LSM_HOOK_INIT(task_alloc,demo_task_alloc),
};
void __init demo_add_hooks(void)
{
pr_info("Demo: becoming mindful.\n"); //打印相关信息,可以通过dmesg | grep XXX:查看
security_add_hooks(demo_hooks, ARRAY_SIZE(demo_hooks)); //添加安全模块函数
}
所谓侵入式链表:
用一个仅含指针的结构体表示连接关系,用一个不含指针的结构体表示数据。(原理:结构体中第一个元素的地址与结构体的首地址相同)
如:
struct list_s {
struct list_s *next;
};
struct foo_s {
int data;
struct list_s link;
};
这里使用侵入式链表,长链的节点仅为一个指针,不需要把所有数据都存到链上。
这里的next理解成security_hook_list.list,data为其他三个成员变量。
在这一点上,简单的security_initcall(demo_init);就可以了
static __init int demo_init(void){
demo_add_hooks();
return 0;
}
security_initcall(demo_init);
1.hook点只能从security_list_options里面选,我参考的一根稻草大佬的博客里选取的是task_create,但是这个函数在4.19.163中不能用做hook点。
2.自己补充实现的函数,其定义必须与hook点的函数一模一样,包括返回值和参数。
3.每次编译都需要make clean,清除之前编译产生的文件。
4.将输出重定向到文件,固然可以方便事后查看一些信息,但是会造成编译时间成倍增加。
5.出现缺少pem文件的报错,直接在config文件中把他删掉。
6.重新编译后直接重启,不需要再修改grub。