修改linux kernel对sys_call_64系统调用入口进行hook
通过内核模块替换 syscall table 在替换 nr_9或者nr_10的时候 只要替换就死机, 下面使用心得方法, 更暴力功能更强大
参考 https://blog.csdn.net/weixin_45730790/article/details/122462393
当前操作系统: centos9
当前内核版本: 5.14
替换的内核版本: 5.15
我们要实现两个功能, 对某个进程下的子进程, 所进行的系统调用, 进行hook, 所以我们需要一个系统调用my_syscall
来设置一个 父进程的ID BASE_PARENT_PID
, 在每次系统调用时可以用 current
这个宏得到当前进程的task_struct
内的 parent->pid
进行比较, 我们在比较成功后, 我们调用我们的钩子函数my_filter
,把当前系统调用号nr
和当前寄存器状态pt_regs
传入钩子函数进行校验, 这个钩子函数有我们提前声明, 在后续的内核模块进行实现, 该函数返回-1
或者0
表示禁止的操作和放行的操作
实现系统调用¶
修改系统调用表¶
我们按照格式添加一行我们自己的系统调用信息到系统调用表, 451号即我们添加的系统调用
arch/x86/entry/syscalls/syscall_64.tbl
447 common memfd_secret sys_memfd_secret
448 common process_mrelease sys_process_mrelease
449 common futex_waitv sys_futex_waitv
450 common set_mempolicy_home_node sys_set_mempolicy_home_node
451 64 my_syscall sys_my_syscall
实现系统调用代码¶
内核所给的SYSCALL_DEFINE
宏, 定义了系统调用在内核的入口, 同时使用EXPORT_SYMBOL
声明了两个符号my_filter
和BASE_PARENT_PID
和默认值, 后续等待实现, 后续我们可以在内核模块中找到这两个符号
arch/x86/kernel/my_syscall.c
#include <linux/uaccess.h>
#include <linux/proc_fs.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/sched.h>
#include <linux/syscalls.h>
#include <linux/kernel.h>
#include <asm/current.h>
// 这里定义一些符号和他们的初始值
long BASE_PARENT_PID = -10;
int(*my_filter)(unsigned long, struct pt_regs *) = -1;
// 导出一些符号到内核空间
EXPORT_SYMBOL(BASE_PARENT_PID);
EXPORT_SYMBOL(my_filter);
SYSCALL_DEFINE1(my_syscall, long, pid) {
printk("123123");
BASE_PARENT_PID=pid;
return 3;
}
修改Makefile¶
修改Makefile文件, 把my_syscall.c
添加到内核编译中去
arch/x86/kernel/Makefile
在50行后根据格式添加就行了
CFLAGS_irq.o := -I $(srctree)/$(src)/../include/asm/trace
obj-y += head_$(BITS).o
obj-y += head$(BITS).o
obj-y += ebda.o
obj-y += my_syscall.o
增加函数声明¶
include/linux/syscalls.h
在最后的endif之前添加即可
hook do_syscall_64¶
我们首先导入了一些符号到当前内核代码中, 然后再系统调用的入口点, 修改了分支
arch/x86/entry/common.c
// 导入外部.o的符号进来
extern long BASE_PARENT_PID;
extern int(*my_filter)(unsigned long, struct pt_regs *);
__visible noinstr void do_syscall_64(struct pt_regs *regs, int nr)
{
add_random_kstack_offset();
nr = syscall_enter_from_user_mode(regs, nr);
instrumentation_begin();
// 这里进行了hook, 可以直接使用vmlinux.o 中函数和变量
if(BASE_PARENT_PID==current->pid && my_filter(nr, regs) == -1) {
regs->ax = __x64_sys_ni_syscall(regs);
} else {
if (!do_syscall_x64(regs, nr) && !do_syscall_x32(regs, nr) && nr != -1) {
/* Invalid system call, but still a system call. */
regs->ax = __x64_sys_ni_syscall(regs);
}
}
instrumentation_end();
syscall_exit_to_user_mode(regs);
}
#endif
错误合集¶
建立swap分区¶
我在执行
make
命令之后, 在ld vmlinux
时会出错scripts/Makefile.vmlinux_o:61 Error 137
, 网上查的是交换分区不够,我们需要设置交换分区
查看分区情况, 如果是0就需要下面的设置了
创建用于交换分区的文件
设置用于交换分区的文件
启用交换分区文件
如果要提示设置权限之类的, 设置就行了,
我们在有交换分区之后, 再次执行make, 可能还会出错, 再多执行两次, 或者交换分区设置大一点, 或者你装内存条, 我ld这个的时候 看资源管理器 大概用了3g的内存可能就走通了(玄学)
certs/rhel.pem¶
没有规则可制作目标“certs/rhel.pem”,由“certs/x509_certificate_list”
编辑.config
文件, 注释掉下面两行
注意¶
编译出错需要重新编译或不是第一次编译, 都需要清除残留的.config 和.o 文件, 方法是进入Linux内核所在的子目录, 执行以下命令
编译安装内核¶
前置安装:
gcc
,g++
,make
,flex
,bison
,cpio
,ncurses-devel.x86_64
,elfutils-libelf-devel.x86_64
,openssl-devel.x86_64
,perl
使用现有的config文件¶
配置生效¶
然后按照load,ok,save,ok,exit,exit
的顺序
编译¶
j16
表示16个线程来编译, 一般选自己超线程的2~2.5倍来让cpu吃满
$ make bzImage -j16
$ ......
Kernel: arch/x86/boot/bzImage is ready (#1)
$ # 出现上面的表示成功了, 下面编译模块
$ make modules -j20
安装¶
更新grub¶
这一步有的可以不做,
make install
替我们做了