參考的資料:
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和文件名,於是實現了文件監控