type
Post
status
Published
slug
2023/07/06/Add-monitoring-system-call-based-on-kernel-6.4.2-version-on-Ubuntu-22.04.2
summary
基于 Linux 6.4 内核,添加一个系统监控的系统调用,把系统调用,修改内核,内核编译,还有内核模块的编写插入,用户态测试函数等等结合起来,做一个全面的讲解。
tags
工具
Linux
category
Linux
icon
password
new update day
Property
Oct 22, 2023 01:31 PM
created days
Last edited time
Oct 22, 2023 01:31 PM

0 前言

今天我们基于 Linux 6.4 内核,添加一个系统监控的系统调用,但是单纯添加一个系统调用会显得有些单调,所以我们把系统调用,修改内核,内核编译,还有内核模块的编写插入,用户态测试函数等等结合起来,做一个全面的讲解。

简介

我们要添加的是一个系统调用日志收集系统,系统调用是用户程序与系统打交道的入口,系统调用的安全直接关系到系统的安全,假设一个用户他恶意的不断调用fork,将会导致系统负载增加,所以如果能收集到是谁,调用了一些有危险的系统调用,以及系统调用的时间和其他信息,将有助于系统管理员进行事后的追踪,从而提高系统的安全性,我们看到的这张图就是本次要添加的系统调用日志收集系统示意图。
notion image
我们可以看到当用户进程进行系统调用的时候,当执行到do_syscall_64,我们判断是否是我们需要记录的系统调用,如果是则拦截需要记录的系统调用,通过my_audit这一函数将相关信息写入到内核中的buffer里,同时我们编写用户测试程序,在用户测试程序中我们通过本次添加的335号系统调用,执行my_sysaudit这一函数,该函数把内核buffer里的信息,拿到我们的用户buffer当中。
其中 my_auditmy_sysaudit 这两个函数是钩子函数,在我们的 my_audit 内核模块当中进行具体的实现。

下载对应的内核源码

可以使用wget命令进行下载,解压下载好的压缩包。
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.4.2.tar.xz tar xvf linux-6.4.2.tar.xz

1 添加系统调用

第一步

打开系统调用表表项文件,增加系统调用表表项,
vim arch/x86/entry/syscalls/syscall_64.tbl
notion image

第二步 添加系统调用函数

vim arch/x86/kernel/myaudit.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> void (*my_audit) (int, int) = 0; int (*my_sysaudit) (u8, u8 *, u16, u8) = 0; SYSCALL_DEFINE4(myaudit, u8, type, u8 *, us_buf, u16, us_buf_size, u8, reset) { if (my_audit){ printk("IN KERNEL: my system call sys_myaudit() working\n"); return (*my_sysaudit)(type, us_buf, us_buf_size, reset); } else printk("my_audit is not exist\n"); return 1; } EXPORT_SYMBOL(my_audit); EXPORT_SYMBOL(my_sysaudit);

第三步 修改 Makefile

myaudit.c 添加到内核编译中去。
vim arch/x86/kernel/Makefile
notion image

第四步 增加函数声明

vim include/linux/syscalls.h
notion image

第五步 拦截相关系统调用

vim arch/x86/entry/common.c
  • 找到 do_syscall_x64 函数,将对应的想要拦截的系统调用添加到函数中。如果捕捉到对应的要监控的系统函数,调用 my_audit 内核函数进行处理。
notion image
notion image

第六步 添加实现钩子函数的内核模块

  • my_audit.c
#include <linux/init.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/sched.h> #include <linux/uaccess.h> #define COMM_SIZE 16 #define AUDIT_BUF_SIZE 20 MODULE_LICENSE("GPL v2"); struct syscall_buf{ u32 serial; u64 ts_sec; u64 ts_micro; u32 syscall; u32 status; pid_t pid; uid_t uid; u8 comm[COMM_SIZE]; }; DECLARE_WAIT_QUEUE_HEAD(buffer_wait); static struct syscall_buf audit_buf[AUDIT_BUF_SIZE]; static int current_pos = 0; static u32 serial = 0; void syscall_audit(int syscall, int return_status) { struct syscall_buf *ppb_temp; struct timespec64 nowtime; ktime_get_real_ts64(&nowtime); if (current_pos < AUDIT_BUF_SIZE ){ ppb_temp = &audit_buf[current_pos]; ppb_temp->serial = serial++; ppb_temp->ts_sec = nowtime.tv_sec; ppb_temp->ts_micro = nowtime.tv_nsec; ppb_temp->syscall = syscall; ppb_temp->status = return_status; ppb_temp->pid = current->pid; ppb_temp->uid = current->tgid; memcpy(ppb_temp->comm, current->comm, COMM_SIZE); if (++current_pos == AUDIT_BUF_SIZE * 6/10){ printk("IN MODULE_AUDIT: notice, audit_buf near full\n"); wake_up_interruptible(&buffer_wait); } } } int sys_audit(u8 type, u8 *us_buf, u16 us_buf_size, u8 reset) { int ret = 0; if (!type){ if (clear_user(us_buf, us_buf_size)){ printk("Error: clear_user\n"); return 0; } printk("IN MODULE_systemcall: starting...\n"); ret = wait_event_interruptible(buffer_wait, current_pos >= AUDIT_BUF_SIZE * 6/10); printk("IN MODULE_systemcall: over, current_pos is %d\n", current_pos); if (copy_to_user(us_buf, audit_buf, (current_pos)*sizeof(struct syscall_buf))){ printk("Error: copy error\n"); return 0; } ret = current_pos; current_pos = 0; } return ret; } extern void (*my_audit)(int, int); extern int (*my_sysaudit)(u8, u8 *, u16, u8); static int __init audit_init(void) { my_sysaudit = sys_audit; my_audit = syscall_audit; printk("Initing System Call Auditing\n"); return 0; } module_init(audit_init); static void __exit audit_exit(void) { my_audit = NULL; my_sysaudit = NULL; printk("Exiting System Calling Auditing\n"); return; } module_exit(audit_exit);

第七步 编写 Makefile

obj-m += my_audit.o LINUX_KERNEL_PATH := ../linux-6.4.2 # 根据需要自己修改 all: make -C $(LINUX_KERNEL_PATH) M=$(CURDIR) modules clean: make -C $(LINUX_KERNEL_PATH) M=$(CURDIR) clean

2 编译修改后的内核

2.1 开启 ccache 加速编译

因为内核数据量很大,所以在编译的时候每次都需要非常多的时间,基本上就是修改两分钟,编译两小时,一点都没有夸张。可以跟据我下面的文章开启 ccache 。

2.2 拷贝原系统 .config 文件

cp /lib/modules/5.19.0-32-generic/build/.config . # 5.19.0-32-generic 是现阶段的内核版本

2.3 配置内核

  • 打开内核配置页,加载 .config 文件
make menuconfig # load > ok > exit > save > ok > exit

2.4 配置内核编译关闭证书

如果出现 debian/canonical-certs.pem error 等错误,参考下面的文章与下面的操作。
scripts/config --disable SYSTEM_TRUSTED_KEYS scripts/config --disable SYSTEM_REVOCATION_KEYS

2.5 编译内核

make bzImage -jn # n 为 你的 CPU 核心数 make modules -jn

2.6 安装内核

sudo make modules_install sudo make install

2.7 重启查看修改

  • 重启之后查看内核
uname -r 6.4.2
  • 查看 dmseg
[ 5076.738802] my_audit is not exist! [ 5076.738804] my_audit is not exist! [ 5076.738805] my_audit is not exist! [ 5076.738812] my_audit is not exist! [ 5076.739142] my_audit is not exist! [ 5076.739207] my_audit is not exist! [ 5076.739305] my_audit is not exist! [ 5076.739381] my_audit is not exist! [ 5076.739435] my_audit is not exist! [ 5076.739577] my_audit is not exist!

2.8 编译内核模块

make

2.9 加载内核模块

  • sudo insmod my_audit.ko
[ 5076.740485] my_audit: loading out-of-tree module taints kernel. [ 5076.740488] my_audit: module verification failed: signature and/or required key missing - tainting kernel [ 5076.741889] Initing System Call Auditing [ 5076.742284] IN MODULE_AUDIT: notice, audit_buf near full

2.10 编写用户态测试函数

  • test_syscall.c
#include <stdlib.h> #include <stdio.h> #include <errno.h> #include <signal.h> #include <time.h> #include <sys/resource.h> #include <sys/syscall.h> #include <sys/types.h> #define COMM_SIZE 16 typedef unsigned char u8; typedef unsigned int u32; typedef unsigned long long u64; struct syscall_buf{ u32 serial; u64 ts_sec; u64 ts_micro; u32 syscall; u32 status; pid_t pid; uid_t uid; u8 comm[COMM_SIZE]; }; #define AUDIT_BUF_SIZE ( 20 * sizeof(struct syscall_buf)) int main(int argc, char *argv[]) { u8 col_buf[AUDIT_BUF_SIZE]; u8 reset = 1; int num = 0; int i, j; struct syscall_buf *p; while(1){ num = syscall(335, 0, col_buf, AUDIT_BUF_SIZE, reset); printf("num: %d\n", num); p = (struct syscall_buf *) col_buf; for(i=0; i < num; i++){ printf("num[%d], serial:[%d]\t syscall:%d\t pid:%d \t comm:%s\t time:%s", i, p[i].serial, p[i].syscall, p[i].pid, p[i].comm, ctime(&p[i].ts_sec)); } } return 0; }

2.11 编译并运行

gcc test_syscall.c -o test_syscall ./test_syscall
  • 运行结果
num: 12 num[0], serial:[122300] syscall:39 pid:18025 comm:sshd time:Fri Jul 7 12:32:07 2023 num[1], serial:[122301] syscall:3 pid:588 comm:systemd-oomd time:Fri Jul 7 12:32:07 2023 num[2], serial:[122302] syscall:39 pid:18025 comm:sshd time:Fri Jul 7 12:32:07 2023 num[3], serial:[122303] syscall:39 pid:18025 comm:sshd time:Fri Jul 7 12:32:07 2023 num[4], serial:[122304] syscall:39 pid:18025 comm:sshd time:Fri Jul 7 12:32:07 2023 num[5], serial:[122305] syscall:39 pid:18025 comm:sshd time:Fri Jul 7 12:32:07 2023 num[6], serial:[122306] syscall:39 pid:18025 comm:sshd time:Fri Jul 7 12:32:07 2023 num[7], serial:[122307] syscall:39 pid:18025 comm:sshd time:Fri Jul 7 12:32:07 2023 num[8], serial:[122308] syscall:3 pid:588 comm:systemd-oomd time:Fri Jul 7 12:32:07 2023 num[9], serial:[122309] syscall:3 pid:263 comm:systemd-udevd time:Fri Jul 7 12:32:08 2023 num[10], serial:[122310] syscall:3 pid:263 comm:systemd-udevd time:Fri Jul 7 12:32:08 2023 num[11], serial:[122311] syscall:3 pid:263 comm:systemd-udevd time:Fri Jul 7 12:32:08 2023
 
 
欢迎加入喵星计算机技术研究院,原创技术文章第一时间推送。
notion image
 
使用 ccache 加速内核编译QEMU 调试 Linux 内核环境搭建