根据示意图,系统调用日志收集系统所做工作为:当用户进程执行系统调用,运行到内核函数do_syscall_64时,进行判断是否为我们需要记录的系统调用,如果是我们需要记录的系统调用则通过my_audit这一函数将记录内容写入内核中的buffer里;同时编写用户态测试程序调用335号系统调用(myaudit),这一系统调用调用my_sysaudit这一函数将内核buffer中数据copy到用户buffer中并显示日志内容;其中我们调用的my_audit和my_sysaudit都是钩子函数,具体实现使用内核模块完成并插入,方便调试。
3
系统调用日志收集系统实现
(1)增加系统调用表的表项
打开arch/x86/entry/syscalls/syscall_64.tbl,添加一个系统调用表项:
335 common myaudit __x64_sys_myaudit
(2)添加系统调用函数
在arch/x86/kernel/目录下添加myaudit.c文件完成系统调用函数:
#include <linux/uaccess.h>
#include <linux/proc_fs.h>
#include <linux/init.h>
#include <linux/types.h>
#include <asm/current.h>
#include <linux/sched.h>
#include <linux/syscalls.h>
#include <linux/kernel.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_sysaudit) {
return (*my_sysaudit)(type, us_buf, us_buf_size, reset);
printk("IN KERNEL: my system call sys_myaudit() working\n"); } else printk("my_sysadit is not exist\n");
return 1;
}
EXPORT_SYMBOL(my_audit);
EXPORT_SYMBOL(my_sysaudit);
这里可以看到实际上定义两个钩子函数,在我们系统调用里去调用这两个钩子函数,这样可以以模块的方式添加这两个函数的具体内容,方便调试。
(3)修改Makefile
修改arch/x86/kernel/Makefile,将myaduit.c文件加入内核编译:
obj-y += myaudit.o
(4)增加函数声明
在include/linux/syscalls.h最后的endif前添加函数声明:
asmlink long sys_myaudit(u8, u8 *, u16, u8);
extern void (*my_audit)(int, int);
(5)拦截相关系统调用
正如前面对syscall执行的分析,修改do_syscall_64()函数(在/arch/x86/entry/common.c中),对系统调用号nr进行判断,如果是我们日志收集系统需要记录的系统调用,就调用我们的记录函数进行记录:
__visible void do_syscall_64(unsigned long nr, struct pt_regs *regs)
{
...
nr = syscall_trace_enter(regs);
nr &= __SYSCALL_MASK;
if (likely(nr < NR_syscalls)) {
nr = array_index_nospec(nr, NR_syscalls);
regs->ax = sys_call_table[nr](regs);
if (nr == 57 || nr == 2 || nr == 3 || nr == 59 || nr == 39 || nr == 56) {
if (my_audit)
(*my_audit)(nr, regs->ax);
else
printk("my_audit is not exist.\n"); } } syscall_return_slowpath(regs); }
可以看到要记录的系统调用有:2:open;3:close;39:getpid;56:clone;57:fork;59:execve。
(6)重新编译内核
#提前把原来内核版本的.config拷贝到5.0内核源码根目录下
cd linux-5.0的路径
sudo cp /usr/src/内核版本/.config ./
#进入menuconfig后按照 load->OK->save->OK->exit->exit执行
sudo make menuconfig
sudo make olddefconfig
#编译内核
sudo make bzImage -j2
sudo make modules
#安装内核修改引导
sudo make modules_install sudo make install
sudo update-grub2
#重启
sudo reboot
(7)添加实现钩子函数的内核模块
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 100
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 * 8 / 10)
{ printk("IN MODULE_audit: yes, it 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 * 8 / 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;
}
static int __init audit_init(void)
{
my_sysaudit = sys_audit; my_audit = syscall_audit;
printk("Starting 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 Call Auditing\n");
return ;
} module_exit(audit_exit);
(8)实现用户空间收集日志进程程序
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];
unsigned char 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 ts_sec: %ld\n", i, p[i].serial, p[i].syscall, p[i].pid, p[i].comm, ctime(&p[i].ts_sec));
} } return 1; }
(9)测试系统
运行用户空间收集日志进程程序,随着OS系统的运行,不断从内核里记录相关系统调用日志的buffer中取出打印在屏幕上: