上一章,我们获取了系统调用表的地址,这里我们来搞点所谓“截获”的事情。所谓“截获”即是将系统调用表里的地址指向我们自己写的一个函数,系统调用先执行我们自己写的函数,处理完后,再返回原来系统调用的执行函数。
还是先贴代码吧。
modu.c
#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){ 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)); kprintk(" address of idtr: 0x%x\n",(unsigned int)&idtr); idt=(struct _idt_descriptor *)(idtr.base+8*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); 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){ 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){ 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){ 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);
Makefile文件和上一节是一样的,这里就不贴了。同样按照上一节的方法,将modu.c编译,然后加载到内核中。
加载成功后,执行“dmesg”,看到的系统日志如图。
如果稍微晚一点执行“dmesg”,系统日志就可能被截获open调用打印的信息刷屏了,因为open系统调用实在是被调用的太多了。
可以执行“lsmod”,查看当前系统中含有的模块,可以找到我们刚刚加载的modu。
ok,接下来解释一下原理。
之前也有解释过,“截获”的过程即是:修改系统调用表中调用函数的地址,将其执行我们自己实现的函数,再在我们自己的函数中完成我们想做的事情后,在返回到原来的系统调用执行流程中。
代码里面的这个函数asmlinkage long my_sys_open(char * filename, int flags, int mode),就是我们自己实现的调用函数,注意这里的形参是参考系统原有的open调用函数的原型来了,必须是这个样子。每种系统调用的原型是什么样子,可以自行百度。
在my_sys_open()中,我们打印了当前是哪个进程在访问(进程名和进程号的信息),访问的是哪个文件(文件的绝对路径),打印完后跳转到原来的系统调用函数。
在模块初始化的过程中,执行start_hook()函数。在start_hook()函数中,先获得系统调用表的地址,将系统调用表中的原有地址保存下来,再将my_sys_open()函数的地址赋到系统调用表中。
注意修改系统调用表时,由于内核中的很多东西,比如这里的系统调用表sys_call_table是只读的,我们需要修改一下权限才能修改。由于控制寄存器CR0的第16位若置位,则表示禁止系统进程写那些只有只读权限的文件,所以我们在修改系统调用表sys_call_table之前先将CR0的第16位清零,在修改完后再恢复置位就好了。如代码里的close_cr()函数,即是将CR0第16位清零,open_cr()函数是将CR0第16位恢复。
最后在卸载modu模块的时候,将系统调用表的内容还原就OK了。