您的位置:首页 > 运维架构 > Linux

Linux内核之旅/张凯捷—系统调用分析(3) (基于最新Linux-5.0版本系统调用日志收集系统)...

2019-01-28 07:10 666 查看

    在上一篇文章《系统调用分析(2)》中介绍和分析了32位和64位的快速系统调用指令——sysenter/sysexit和syscall/sysret,以及内核对快速系统调用部分的相关代码,并追踪了一个用户态下的系统调用程序运行过程。    本篇中将基于最新的Linux-5.0内核,添加一个系统调用,完成一个“系统调用日志收集系统”; 并对这三篇文章进行一个总结。

 


加前两篇文章在这里:

https://mp.weixin.qq.com/s/3Dvd2dy0l6OYFVGzfEvOcghttps://mp.weixin.qq.com/s/7uXVXXqzN8wMqgxqrN_5og

加一个系统调用

//


  单纯添加一个系统调用会显得有些单调,出于既是作业又是学习角度,将系统调用、工作队列、修改内核、内核编译和内核模块编写插入等结合起来,通过完成一个系统调用日志收集系统。
1系统调用日志收集系统的目的      系统调用是用户程序与系统打交道的入口,系统调用的安全直接关系到系统的安全,如果一个用户恶意地不断调用fork()将导致系统负载增加,所以如果能收集到是谁调用了一些有危险的系统调用,以及系统调用的时间和其他信息,将有助于系统管理员进行事后追踪,从而提高系统的安全性。
2系统调用日志收集系统概述
图3-1 系统调用日志收集系统示意图
  根据示意图,系统调用日志收集系统所做工作为:当用户进程执行系统调用,运行到内核函数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中取出打印在屏幕上:


//

图3-2 系统测试截图
总结

图 4-1 系统调用总结图

《系统调用分析》一共三篇文章,先从最早的系统调用方法——(int 80)开始,基于Linux-2.6.39内核开始分析,对用软中断系统调用的初始化、处理流程和系统调用表进行了学习探究。随后,基于Linux-4.20内核分析学习了从机制上对系统调用进行优化的方法——vsyscalls和vDSO。之后对32位下的快速系统调用指令——sysenter/sysexit进行指令学习和对相关Linux源码分析。然后在Linux-4.20内核下编写调用系统调用的程序,使用gdb进行调试跟踪并分析出最后使用syscall指令执行系统调用,再对64位下的快速系统调用指令syscall/sysret进行指令学习和对相关Linux源码分析。最后在Linux-5.0内核上完成一个系统调用日志收集系统,其中包含着添加系统调用,编译内核,修改内核代码,添加内核模块,编写用户态程序测试。
参考文献
[1] 英特尔®64和IA-32架构软件开发人员手册合并卷. https://software.intel.com/sites/default/files/managed/39/c5/325462-sdm-vol-1-2abcd-3abcd.pdf[2] The Definitive Guide to Linux System Calls. https://blog.packagecloud.io/eng/2016/04/05/the-definitive-guide-to-linux-system-calls/#32-bit-fast-system-calls[3] Linux 2.6 对新型 CPU 快速系统调用的支持. https://www.ibm.com/developerworks/cn/linux/kernel/l-k26ncpu/index.html[4] vsyscalls and vDSO. https://0xax.gitbooks.io/linux-insides/content/SysCall/linux-syscall-3.html[5] Linux系统调用过程分析. https://www.binss.me/blog/the-analysis-of-linux-system-call/[6] Fix-Mapped Addresses and ioremap. https://0xax.gitbooks.io/linux-insides/content/MM/linux-mm-2.html[7] 王宗涛. Linux快速系统调用实现机制分析. TP316.81[8] linux下系统调用的实现. https://www.pagefault.info/2010/10/09/implementation-of-system-call-under-linux.html
查看我们精华技术文章请移步:
Linux阅码场原创精华文章汇总

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: