跳转至

修改linux kernel对sys_call_64系统调用入口进行hook

通过内核模块替换 syscall table 在替换 nr_9或者nr_10的时候 只要替换就死机, 下面使用心得方法, 更暴力功能更强大

参考 https://blog.csdn.net/weixin_45730790/article/details/122462393

https://zhuanlan.zhihu.com/p/487648323

当前操作系统: 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_filterBASE_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之前添加即可

asmlinkage long sys_my_syscall(long pid);
#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就需要下面的设置了

$ free -m

创建用于交换分区的文件

$ dd if=/dev/zero of=/swapfile bs=1M count=2048

设置用于交换分区的文件

$ mkswap /swapfile

启用交换分区文件

$ swapon /swapfile

如果要提示设置权限之类的, 设置就行了,

我们在有交换分区之后, 再次执行make, 可能还会出错, 再多执行两次, 或者交换分区设置大一点, 或者你装内存条, 我ld这个的时候 看资源管理器 大概用了3g的内存可能就走通了(玄学)

certs/rhel.pem

没有规则可制作目标“certs/rhel.pem”,由“certs/x509_certificate_list”

编辑.config文件, 注释掉下面两行

#CONFIG_SYSTEM_TRUSTED_KEYS="certs/rhel.pem"
#CONFIG_DEBUG_INFO_BTF=y

注意

编译出错需要重新编译或不是第一次编译, 都需要清除残留的.config 和.o 文件, 方法是进入Linux内核所在的子目录, 执行以下命令

$ make clean
$ make mrproper  # 清理配置

编译安装内核

前置安装: gcc, g++, make, flex, bison, cpio, ncurses-devel.x86_64, elfutils-libelf-devel.x86_64, openssl-devel.x86_64, perl

使用现有的config文件

$ cp /boot/config-5.14.0-283.el9.x86_64 .config

配置生效

$ make menuconfig

然后按照load,ok,save,ok,exit,exit的顺序

$ make oldconfig

编译

j16表示16个线程来编译, 一般选自己超线程的2~2.5倍来让cpu吃满

$ make bzImage -j16

$ ......

Kernel: arch/x86/boot/bzImage is ready  (#1)
$ # 出现上面的表示成功了, 下面编译模块
$ make modules -j20

安装

$ make modules_install
$ make install

更新grub

这一步有的可以不做, make install替我们做了

$ update-grub2