当用户态发起一个系统调用时,会通过80软中断进入到syscall hander,进而进入全局的系统调用表sys_ call _table去查找具体的系统调用,那么如果我们将这个数组中的地址改成我们自己的程序地址,就可以实现系统调用劫持。但是内核为了安全,对这种操作做了一些限制:
sys_ call _table的符号没有导出,不能直接获取。 sys_ call _table所在的内存页是只读属性的,无法直接进行修改。对于以上两个问题,解决方案如下(方法不止一种):
获取sys call table的地址 :grep sys _ call _table /boot/System.map-uname -r 控制页表只读属性是由CR0寄存器的WP位控制的,只要将这个位清零就可以对只读页表进行修改。
/* make the page writable */
int make_rw(unsigned long address)
{
unsigned int level;
pte_t *pte = lookup_address(address, &level);//查找虚拟地址所在的页表地址
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;
}
开始替换系统调用
本文实现的是对 ls这个命令对应的系统调用,系统调用号是 _ NR _getdents。
static int syscall_init_module(void)
{
orig_getdents = sys_call_table[__NR_getdents];
make_rw((unsigned long)sys_call_table); //修改页属性
sys_call_table[__NR_getdents] = (unsigned long *)hacked_getdents; //设置新的系统调用地址
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_getdents] = (unsigned long *)orig_getdents;
make_ro((unsigned long)sys_call_table);
}
使用Makefile编译,insmod插入内核模块后,再执行ls时,就会进入到我们的系统调用,我们可以在hook代码中删掉某些文件,ls就不会显示这些文件,但是这些文件还是存在的。
堆栈式文件系统
Linux通过vfs虚拟文件系统来统一抽象具体的磁盘文件系统,从上到下的IO栈形成了一个堆栈式。通过对内核源码的分析,以一次读操作为例,从上到下所执行的流程如下:








