今天在ubuntu中玩了下“拦截系统调用”,记录下自己对整个实现的理解。
在linux kernel中,系统调用都放在一个叫做“sys_call_table”的分配表里面,在进入一个系统调用的最后一步,会调用与eax中包含的系统调用号对应的特定服务例程:
call *sys_call_table(,%eax,4)
网上介绍了很多种方法得到sys_call_table的地址,我使用了相对简单的一种方法——从内核导出的符号表中获取。
图中,十六进制数c15b3000即为sys_call_table的地址。同时,我们也得到了一个重要的信息,该符号对应的内存区域是只读的!
因为sys_call_table分配表的内存属性为只读,因此,我们要先清除对应地址的写保护。暂时使用了两种方法实现该目的:
第一种方法,修改cr0读写保护位:
/* 清除写保护 */ unsigned int clear_and_return_cr0(void) { unsigned int cr0 = 0; unsigned int ret; asm volatile ("movl %%cr0, %%eax" : "=a"(cr0) ); ret = cr0; /* clear the 16 bit of CR0, a.k.a WP bit */ cr0 &= 0xfffeffff; asm volatile ("movl %%eax, %%cr0" : : "a"(cr0) ); return ret; } /* 设置cr0,--本程序用来恢复写保护 */ void setback_cr0(unsigned int val) { asm volatile ("movl %%eax, %%cr0" : : "a"(val) ); }
/* make the page writable */ int make_rw(unsigned long address) { unsigned int level; pte_t *pte = lookup_address(address, &level); if (pte->pte & ~_PAGE_RW) pte->pte |= _PAGE_RW; return 0; } /* make the page write protected */ int make_ro(unsigned long address) { unsigned int level; pte_t *pte = lookup_address(address, &level); pte->pte &= ~_PAGE_RW; return 0; }
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/sched.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/file.h> #include <linux/fs_struct.h> #include <linux/fdtable.h> #include <linux/string.h> #include <linux/mm.h> #include <linux/syscalls.h> #include <linux/list.h> #include <linux/jiffies.h> #include <linux/cdev.h> #include <asm/unistd.h> #include <asm/uaccess.h> #include <linux/path.h> #include <linux/time.h> #include <linux/stat.h> #include <net/sock.h> #include <net/inet_sock.h> #include <asm/cpufeature.h> /* grep sys_call_table /boot/System.map-`uname -r` */ unsigned long **sys_call_table = (unsigned long **)0xc15b3000; unsigned long *orig_mkdir = NULL; /* make the page writable */ int make_rw(unsigned long address) { unsigned int level; pte_t *pte = lookup_address(address, &level); if (pte->pte & ~_PAGE_RW) pte->pte |= _PAGE_RW; return 0; } /* make the page write protected */ int make_ro(unsigned long address) { unsigned int level; pte_t *pte = lookup_address(address, &level); pte->pte &= ~_PAGE_RW; return 0; } asmlinkage long hacked_mkdir(const char __user *pathname, int mode) { printk("mkdir pathname: %s\n", pathname); printk(KERN_ALERT "mkdir do nothing!\n"); return 0; /*everything is ok, but he new systemcall does nothing*/ } static int syscall_init_module(void) { printk(KERN_ALERT "sys_call_table: 0x%lx\n", sys_call_table); orig_mkdir = (unsigned long *)(sys_call_table[__NR_mkdir]); printk(KERN_ALERT "orig_mkdir: 0x%lx\n", orig_mkdir); make_rw((unsigned long)sys_call_table); sys_call_table[__NR_mkdir] = (unsigned long *)hacked_mkdir; make_ro((unsigned long)sys_call_table); return 0; } static void syscall_cleanup_module(void) { printk(KERN_ALERT "Module syscall unloaded.\n"); make_rw((unsigned long)sys_call_table); sys_call_table[__NR_mkdir] = (unsigned long *)orig_mkdir; /*set mkdir syscall to the origal one*/ make_ro((unsigned long)sys_call_table); } module_init(syscall_init_module); module_exit(syscall_cleanup_module); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("hack syscall");
参考:
1、《深入理解linux内核(第三版)》
2、Hijack Linux System Calls: Part III. System Call Table