参考的资料:

hello world   https://www.cnblogs.com/bitor/p/9608725.html

linux内核监控模块——系统调用的截获  https://www.cnblogs.com/lxw315/p/4773566.html

实现:

实验目的:

内核模块的编写:完成一个Linux/Windows内核/驱动模块的编写,

能够实现对文件访问的监控、或者对键盘设备、USB设备、网络设备、

蓝牙设备等的监控。

实验内容:

通过linux内核模块编程,写一个模块使得进程访问文件时会把进程的进程名,进程id,文件名打印到系统日志,然后通过dmesg查看相关信息。

实验步骤:

1.思路

“截获”的过程是:修改系统调用表中调用函数的地址,将其执行我们自己实现的函数,再在我们自己的函数中完成我们想做的事情后,在返回到原来的系统调用执行流程中。

实现文件访问监控的思路为:截获sys_open系统调用。当进程打开文件时会调用系统中断sys_open,通过修改中断向量表使得先执行我们编写的函数my_sys_open,功能为打印当前访问的进程名,进程号,文件名,然后再执行sys_open。这样就相当于监控到了信息,信息通过printk打印到系统日志,通过dmesg查看即可。

2.代码实现

函数asmlinkage long my_sys_open(char * filename, int flags, int mode),就是自己实现的调用函数,这里的形参是参考系统原有的open调用函数的原型。

在my_sys_open()中,打印了当前是哪个进程在访问(进程名和进程号的信息),访问的是哪个文件(文件的绝对路径),打印完后跳转到原来的系统调用函数。

在模块初始化的过程中,执行start_hook()函数。在start_hook()函数中,先获得系统调用表的地址,将系统调用表中的原有open函数的地址保存下来,再将my_sys_open()函数的地址赋到系统调用表中。

修改系统调用表时,由于内核中的很多东西,比如系统调用表sys_call_table是只读的,需要修改一下权限才能修改。由于控制寄存器CR0的第16位若置位1,则表示禁止系统进程写只有只读权限的文件,所以在修改系统调用表sys_call_table之前先将CR0的第16位清零,在修改完后再恢复置位。

代码的close_cr()函数,是将CR0第16位清零,open_cr()函数是将CR0第16位恢复。

最后在卸载modu模块的时候,将系统调用表的内容还原。

#include<linux/init.h>
#include<linux/module.h>
#include<linux/moduleparam.h>
#include<linux/unistd.h>
#include<linux/sched.h>
#include<linux/syscalls.h>
#include<linux/string.h>
#include<linux/fs.h>
#include<linux/fdtable.h>
#include<linux/uaccess.h> #include<linux/rtc.h> MODULE_LICENSE("Dual BSD/GPL"); #define _DEBUG
#ifdef _DEBUG
#define kprintk(fmt,args...) printk(KERN_ALERT fmt,##args)
#define kprintf(fmt,args...) printf(fmt,##args)
#define kperror(str) perror(str)
#else
#define kprintk
#define kprintf
#define kperror
#endif /*Function declaration*/
long * get_sys_call_table(void);
unsigned int close_cr(void);
void open_cr(unsigned int oldval);
void start_hook(void);
asmlinkage long (*orig_open)(char __user *filename, int flags, int mode); long * g_sys_call_table = NULL; //save address of sys_call_table
long g_old_sys_open = 0; //save old address of sys_open
long g_oldcr0 = 0; //save address of cr0 struct _idtr{ //中断描述符表寄存器
unsigned short limit;
unsigned int base;
}__attribute__((packed)); struct _idt_descriptor{
unsigned short offset_low;
unsigned short sel;
unsigned char none,flags;
unsigned short offset_high;
}__attribute__((packed)); unsigned int close_cr(void){
unsigned int cr0 = 0;
unsigned int ret;
asm volatile("movl %%cr0,%%eax":"=a"(cr0));
ret = cr0;
cr0 &= 0xfffeffff;
asm volatile("movl %%eax,%%cr0"::"a"(cr0));
return ret;
} void open_cr(unsigned int oldval){
asm volatile("movl %%eax,%%cr0"::"a"(oldval));
} /*Get the address of sys_call_table*/
long * get_sys_call_table(void){ //在idtr寄存器 struct _idt_descriptor * idt;
struct _idtr idtr;
unsigned int sys_call_off;
int sys_call_table=0;
unsigned char* p;
int i;
asm("sidt %0":"=m"(idtr)); //汇编,sidt指令获得中断描述符表基地址
kprintk(" address of idtr: 0x%x\n",(unsigned int)&idtr);
idt=(struct _idt_descriptor *)(idtr.base+8*0x80); //0x80中断为系统调用中断 这是一个描述符,下面的操作得到描述符指向的具体地址
sys_call_off=((unsigned int)(idt->offset_high<<16)|(unsigned int)idt->offset_low);
kprintk(" address of idt 0x80: 0x%x\n",sys_call_off); //0x80位
p=(unsigned char *)sys_call_off;
for(i=0;i<100;i++){
if(p[i]==0xff&&p[i+1]==0x14&&p[i+2]==0x85){
sys_call_table=*(int*)((int)p+i+3);
kprintk(" address of sys_call_table: 0x%x\n",sys_call_table); return (long*)sys_call_table;
}
} return 0;
} //My own sys_open
asmlinkage long my_sys_open(char * filename, int flags, int mode){ //打印当前使用sys-open函数的进程信息和文件名
kprintk("The process is \"%s\"(pid is %i)\n",current->comm,current->pid);
kprintk("The file is being accessed is \"%s\"\n",filename);
return orig_open(filename,flags,mode);
} void start_hook(void){ //得到系统调用表地址,寻找sys-open项,替换为my_sys-open,cr0寄存器16位置0,可以写只读项
g_sys_call_table = get_sys_call_table();
if(!g_sys_call_table){
kprintk("Get sys_call_table error!\n");
return;
}
if(g_sys_call_table[__NR_close] != (unsigned long)sys_close){
kprintk("Incorrect sys_call_table address!\n");
return;
} g_old_sys_open = g_sys_call_table[__NR_open];
orig_open = (long(*)(char *, int, int))g_sys_call_table[__NR_open]; g_oldcr0=close_cr();
g_sys_call_table[__NR_open] = my_sys_open;
open_cr(g_oldcr0);
} int monitor_init(void){ //启动模块
kprintk("Monitor init\n");
start_hook();
return 0;
} void monitor_exit(void){ //退出模块 恢复系统调用表sys_open函数所在项地址为原sys_open,cr0寄存器16位置1,禁止写只读文件
if(g_sys_call_table && g_old_sys_open){
g_oldcr0 = close_cr();
g_sys_call_table[__NR_open] = g_old_sys_open;
open_cr(g_oldcr0);
}
kprintk("Monitor exit\n");
} module_init(monitor_init);
module_exit(monitor_exit);

3. Makefile

obj-m += hello.o
#generate the path
CURRENT_PATH:=$(shell pwd)
#the current kernel version number
LINUX_KERNEL:=$(shell uname -r)
#the absolute path
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)
#complie object
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
#clean
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
obj-m := hello.o
PWD := $(shell pwd)
KVER := $(shell uname -r)
KDIR :=/lib/modules/$(KVER)/build/ all:
$(MAKE) -C $(KDIR) M=$(PWD) clean:
rm -rf *.o *.mod.c *.mod.o *.ko *.symvers *.order *.a

4.加载内核模块,卸载内核模块

通过insmod XX.ko加载模块,rmmod xx.ko卸载模块。

cat /proc/modules查看模块信息,lsmod查看所有的模块。

遇到的问题及解决:

1.编写完makefile 执行Make时报错 nothing to be done for all

原因:这是因为空格和tab的转换问题

比如下面两个make就不一样。

解决:删掉前面的空格,改成tab

2.insmod后ubuntu系统卡死

原因:VMware的问题,换成virtual box就可以正常运行。

实验结果记录:

insmod 后dmesg,查看系统日志,可见记录了一些访问文件的进程名,id和文件名,于是实现了文件监控

linux内核编程入门--系统调用监控文件访问的更多相关文章

  1. linux内核编程入门 hello world

    注意: Makefile 文件的命名注意M需要大写,否则会报错. 在Makefile文件中make命令前应为tab制表符. 下文转载至:https://blog.csdn.net/bingqing07 ...

  2. Win64 驱动内核编程-14.回调监控文件

    回调监控文件 使用 ObRegisterCallbacks 实现保护进程,其实稍微 PATCH 下内核,这个函数还能实现文件操作监视.但可惜只能在 WIN7X64 上用.因为在 WIN7X64 上 P ...

  3. Linux内核编程规范与代码风格

    source: https://www.kernel.org/doc/html/latest/process/coding-style.html translated by trav, travmym ...

  4. 初探linux内核编程,参数传递以及模块间函数调用

    一.前言                                  我们一起从3个小例子来体验一下linux内核编程.如下: 1.内核编程之hello world 2.模块参数传递 3.模块间 ...

  5. 内核编程实例,多文件的Makefile

    内核编程实例,多文件的Makefile 经典的hello word测试 ////# cat hello.c #include <linux/module.h> #include <l ...

  6. 给Linux内核增加一个系统调用的方法(转)

    作者:chenjieb520 给Linux内核增加一个系统调用的方法    为了更加好地调试linux内核,笔者的实验均在mini6410的arm板上运行的.这样做的原因,第一是因为本人是学嵌入式的, ...

  7. Linux内核编程-0:来自内核的 HelloWorld

    Linux内核编程一直是我很想掌握的一个技能.如果问我为什么,我也说不上来. 也许是希望有一天自己的ID也出现在内核开发组的邮件列表里?或是内核发行文件的CREDITS文件上? 也许是吧.其实更多的, ...

  8. Linux系统编程(2)——文件与IO之系统调用与文件IO操作

    系统调用是指操作系统提供给用户程序的一组"特殊"接口,用户程序可以通过这组"特殊"接口来获得得操作系统内核提供的特殊服务.在linux中用户程序不能直接访部内核 ...

  9. Linux系统编程(1)——文件与I/O之C标准I/O函数与系统调用I/O

    Linux系统的I/O也就是一般所说的低级I/O--操作系统提供的基本IO服务,与os绑定,特定于Linux平台.而标准I/O是ANSI C建立的一个标准I/O模型,是一个标准函数包和stdio.h头 ...

随机推荐

  1. oracle编译表上失效USERDBY脚本

    对表进行DLL操作之后,依赖这个表的一些存储过程,触发器等会失效,可以用下边的脚本进行重编译 /* Formatted on 2020/7/8 上午 09:31:31 (QP5 v5.163.1008 ...

  2. VBScript调用winscp,实现sftp操作

    最新有一个需求,需要在ssis中调用sftp下载文件,由于服务器上只有framework2.0,并且需要用sqlserver代理调用作业,限制了很多. 首先用的是脚本任务,进程调用winscp.com ...

  3. 如何在C#中使用MSMQ

    MSMQ (Microsoft消息队列)是Windows中默认可用的消息队列.作为跨计算机系统发送和接收消息的可靠方法,MSMQ提供了一个可伸缩.线程安全.简单和使用方便的队列,同时为你提供了在Win ...

  4. Oracle 0至6级锁的通俗解释及实验案例_ITPUB博客 http://blog.itpub.net/30126024/viewspace-2156232/

    Oracle 0至6级锁的通俗解释及实验案例_ITPUB博客 http://blog.itpub.net/30126024/viewspace-2156232/

  5. JAD 反编译

    自动拆装箱 对于基本类型和包装类型之间的转换,通过xxxValue()和valueOf()两个方法完成自动拆装箱,使用jad进行反编译可以看到该过程: public class Demo { publ ...

  6. LOJ10159旅游规划

    题目描述 W 市的交通规划出现了重大问题,市政府下定决心在全市各大交通路口安排疏导员来疏导密集的车流.但由于人员不足,W 市市长决定只在最需要安排人员的路口安排人员. 具体来说,W 市的交通网络十分简 ...

  7. 前端调用微信小程序的支付流程

    目录 1,前言 2,流程 3,参数说明 4,具体代码 1,前言 分享一个完整的微信小程序支付流程中,前端要做的模块. 2,流程 在调用wx.requestPayment之前,需要准备一些参数,流程如下 ...

  8. Grafana+Prometheus通过node_exporter监控Linux服务器信息

    Grafana+Prometheus通过node_exporter监控Linux服务器信息 一.Grafana+Prometheus通过node_exporter监控Linux服务器信息 1.1nod ...

  9. JVM 线上故障排查

    JVM 线上故障排查 Linux 1.1 CPU 1.2 内存 1.3 存储 1.4 网络 一.CPU 飚高 寻找原因 二.内存问题排查 三.一般排查问题的方法 四.应用场景举例 4.1 怎么查看某个 ...

  10. Stream API处理集合

    使用流来遍历集合 简介 如何工作 总结 从集合或数组创建流 简介 如何工作 结论 聚合流的值 简介 如何工作 结论 转载 使用流来遍历集合 简介: Java的集合框架,如List和Map接口及Arra ...