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

Linux内核--hook系统调用

2018-01-19 00:00 197 查看
Linux系统调用劫持:其实就是修改内核符号表,来达到一个劫持的作用。因为系统调用实际上是触发了一个0x80的软中断,然后转到了系统调用处理程序的入口system_call()。system_call()会检查系统调用号来得出到底是调用哪种服务,然后会根据内核符号表跳转到所需要调用的内核函数的入口地址,所以,如果我们这个时候修改了内核符号表,使其跳转到我们自己的函数上,就可以完成劫持。不过2.6之后,内核符号表便不再导出了,所以需要我们在内存中找到它的位置。具体查找的方式为:查找到中断向量表中的0x80中断服务函数的位置,然后在这个中断服务函数代码中查找根据内核符号表跳转的指令,从指令中可以分析出内核符号表的位置。还是先贴代码吧。 #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_tablelong g_old_sys_open = 0; //save old address of sys_openlong 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",(unsignedint)&idtr);    idt=(struct _idt_descriptor*)(idtr.base+8*0x80);    sys_call_off=((unsignedint)(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_openasmlinkage 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 beingaccessed 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("Getsys_call_table error!\n");        return;    }    if(g_sys_call_table[__NR_close]!= (unsigned long)sys_close){        kprintk("Incorrect sys_call_tableaddress!\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("Monitorinit\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("Monitorexit\n");} module_init(monitor_init);module_exit(monitor_exit);将modu.c编译,然后加载到内核中。加载成功后,执行“dmesg”,看到的系统日志如图。

如果稍微晚一点执行“dmesg”,系统日志就可能被截获open调用打印的信息刷屏了,因为open系统调用实在是被调用的太多了。可以执行“lsmod”,查看当前系统中含有的模块,可以找到我们刚刚加载的modu。
接下来解释一下原理: “截获”的过程即是:修改系统调用表中调用函数的地址,将其执行我们自己实现的函数,再在我们自己的函数中完成我们想做的事情后,在返回到原来的系统调用执行流程中。代码里面的这个函数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了。 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: