65.9K
CodeProject 正在变化。 阅读更多。
Home

Linux OS 中的隐藏文件驱动程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.44/5 (13投票s)

2012年8月22日

CPOL

7分钟阅读

viewsIcon

46618

downloadIcon

800

用于 Linux OS 的简单驱动程序,可从系统中隐藏选定的文件

下载 DriverToHideFiles.zip

目录

引言

一般信息

为文件父目录新增 filldir() 函数

新增文件操作结构

用于挂钩 inode 操作、文件操作并获取指定文件 inode 号的函数

用于备份指定文件 inode 指针的函数

驱动的设备文件

隐藏驱动构建系统 

正在运行的隐藏文件驱动

参考文献列表

介绍 

在本文中,我将描述在 Linux 操作系统中开发一个隐藏文件驱动模块的过程(您可以 此处 阅读关于 Windows 操作系统隐藏驱动的文章)。此外,我还将涉及以下问题

  • 虚拟文件系统 (VFS)
  • inode 和 dentry 结构的工作

本文涉及 Linux 内核版本 2.6.32,因为其他内核版本可能具有修改过的 API,这与示例或构建系统中使用的 API 不同。本文档适合已有 Linux 驱动开发经验的人员。简单的 Linux 驱动的创建 此处 已有描述。

一般信息

虚拟文件系统是内核中的软件层,它为用户空间程序提供文件系统接口。它还在内核中提供了一个抽象,允许不同的文件系统实现共存。[1]

VFS 实现 openstatchmod 和类似的系统调用。传递给它们的 pathname 参数由 VFS 用于在目录项缓存(也称为 dentry 缓存或 dcache)中进行搜索。这提供了一种非常快速查找机制,可以将 pathnamefilename)转换为特定的 dentry。Dentry 存储在 RAM 中,永不保存到磁盘:它们仅出于性能目的而存在。[1]

单个 dentry 通常指向一个 inode。Inode 是文件系统对象,例如普通文件、目录。它们要么存储在磁盘上(对于块设备文件系统),要么存储在内存中(对于伪文件系统)。当需要时,存储在磁盘上的 inode 会被复制到内存中,并且对 inode 的更改会写回磁盘。多个 dentry 可以指向同一个 inode(例如,硬链接就是这样做的)。每个文件都由其自己的 inode 结构表示。[1]

 

每个 inode 结构都有一个对于当前挂载的文件系统唯一的 inode 号。

我们的驱动程序将挂钩指定文件及其父目录的 inode 和文件操作。为此,我们将修改 inode 和文件操作结构中的指针,指向我们自己的函数。这将使我们能够从系统中隐藏文件。

为文件父目录新增 filldir() 函数

要从系统中隐藏文件,我们将挂钩父目录的 filldir() 函数。该函数由显示目录中文件的 readdir() 函数调用。该函数的第二个和第三个参数是显示文件的名称和名称长度。为了改变 filldir() 的调用,我们将创建我们自己的 readdir() 函数,并从它调用我们的 filldir() 函数。

int parent_readdir (struct file * file, void * dirent, filldir_t filldir)
{       
    g_parent_dentry = file->f_dentry;

    real_filldir = filldir;

    return file->f_dentry->d_sb->s_root->d_inode->i_fop->readdir(file, dirent, new_filldir);
} 

g_parent_dentry 是一个指向父目录 dentry 的指针,我们将在我们的 filldir() 函数中使用它来搜索指定文件的 inode。

由于我们只需要覆盖父目录 file_operations 结构中的 readdir() 函数,因此我们将创建一个静态的 file_operations 结构。

/***********************File OPERATIONS of the parent directory*****************************/
static struct file_operations new_parent_fop =
{
.owner = THIS_MODULE,
.readdir = parent_readdir,
};

此代码创建了一个 file_operations 结构,其中我们将使用自定义的 parent_readdir() 函数来覆盖 readdir() 函数。

这种语法告诉我们,结构中未显式赋值的任何成员都将被 gcc 初始化为 NULL。[3, 第 4.1.1 章]。

我们可以使用 d_lookup() 函数,它返回当前目录中文件的 dentry。d_lookup() 接收父目录 dentry 和文件,以及包含文件名和名称长度的 qstr 结构。我们将在 readdir() 函数中保存父目录的 dentry。我们可以在 filldir() 函数中创建 qstr 结构并填充它。

因此,有了当前显示文件的 dentry,我们就可以将其 inode 号与我们要隐藏的文件(我们稍后会获取它们)的 inode 号进行比较。 如果文件被隐藏,filldir() 函数返回 0。如果文件未被隐藏,则调用原始的 filldir() 函数。

static int new_filldir (void *buf, const char *name, int namelen, loff_t offset,u64 ux64, unsigned ino)
{
    unsigned int i = 0;
    struct dentry* pDentry;
    struct qstr current_name;

    current_name.name = name;
    current_name.len = namelen;
    current_name.hash = full_name_hash (name, namelen);

    pDentry = d_lookup(g_parent_dentry, &current_name);
        
    if (pDentry != NULL)
    {
        for(i = 0; i <= g_inode_count - 1; i++)
        {
            if (g_inode_numbers[i] == pDentry->d_inode->i_ino) 
            {    
                return 0;
            }
        }
    }
    return real_filldir (buf, name, namelen, offset, ux64, ino);
}

g_inode_numbers – 要隐藏文件的 inode 号数组。

新增文件操作结构

改变 filldir() 函数只能让我们从目录中隐藏文件。但是,对于该文件,诸如 readwrite 操作之类的系统调用仍然可以工作。为了防止这种情况,我们将更改文件的 inode 和文件操作。我们的驱动程序将为所有操作返回 -2 值,这意味着:“指定的文件不存在。”

/********************************FILE OPERATIONS*************************/
static struct file_operations new_fop  =
{
    .owner = THIS_MODULE,
    .readdir = new_readdir,
    .release = new_release,
    .open = new_open,
    .read = new_read, 
    .write = new_write,
    .mmap = new_mmap,
};
…
ssize_t new_read (struct file * file1, char __user  * u, size_t t, loff_t * ll)
{
    return -2;
}
ssize_t new_write (struct file * file1, const char __user * u, size_t t, loff_t *ll)
{
    return -2;
}
…
int new_open (struct inode * old_inode, struct file * old_file)
{
    return -2;
}
/********************************INODE OPERATIONS*************************/
static struct inode_operations new_iop =
{
    .getattr = new_getattr,
    .rmdir = new_rmdir,
};
int new_rmdir (struct inode *new_inode,struct dentry *new_dentry)
{
    return -2;
}
int new_getattr (struct vfsmount *mnt, struct dentry * new_dentry, struct kstat * ks)
{
    return -2;
}

用于挂钩 inode 操作、文件操作并获取指定文件 inode 号的函数

我们将使用 path_lookup() 函数来挂钩 inode 和文件操作。该函数将为作为第一个参数指定的文件的 nameidata 结构进行填充。

unsigned long hook_functions(const char * file_path) 
{
    int error = 0;
    struct nameidata nd;

    error = path_lookup (file_path, 0, &nd);
    if (error) 
    {
        printk( KERN_ALERT "Can't access file\n");

        return -1;
    }

在获取了填充好的 nameidata 结构后,我们为包含旧 inode 指针和 inode 号的数组分配内存。

void reallocate_memmory()
{
    /*Realloc memmory for inode number*/
    g_inode_numbers = (unsigned long*)krealloc(g_inode_numbers,sizeof(unsigned long*)*(g_inode_count+1), GFP_KERNEL);
	
    /*Realloc memmory for old pointers*/
    g_old_inode_pointer  = (void *)krealloc(g_old_inode_pointer , sizeof(void*) * (g_inode_count + 1), GFP_KERNEL);
    g_old_fop_pointer = (void *)krealloc(g_old_fop_pointer, sizeof(void*) * (g_inode_count + 1), GFP_KERNEL);
    g_old_iop_pointer = (void *)krealloc(g_old_iop_pointer, sizeof(void*) * (g_inode_count + 1), GFP_KERNEL);

    g_old_parent_inode_pointer = (void *)krealloc(g_old_parent_inode_pointer, sizeof(void*) * (g_inode_count + 1), GFP_KERNEL);
    g_old_parent_fop_pointer = (void *)krealloc(g_old_parent_fop_pointer, sizeof(void*) * (g_inode_count + 1), GFP_KERNEL);
}

g_old_inode_pointer – 指向隐藏文件原始 inode 结构的指针数组。

g_old_fop_pointer – 指向隐藏文件原始 file_operations 结构的指针数组。

g_old_iop_pointer – 指向隐藏文件原始 inode_opearations 结构的指针数组。

g_old_parent_inode_pointer – 指向包含隐藏文件的父目录原始 inode 结构的指针数组。

g_old_parent_fop_pointer – 指向包含隐藏文件的父目录原始 file_operations 结构的指针数组。

Nameidata 结构包含指定路径的 dentry。使用接收到的 dentry,我们可以操作指向文件和 inode 操作的指针。在我们获得指定路径的 dentry 和 inode 结构后,我们保存旧指针并将其更改为指向我们结构的指针。

       unsigned long hook_functions(const char * file_path) 
{

…

    /************************Old pointers**********************************/
    /*Save old pointers*/
    g_old_inode_pointer [g_inode_count] = nd.path.dentry->d_inode;
    g_old_fop_pointer[g_inode_count] = (void *)nd.path.dentry->d_inode->i_fop;
    g_old_iop_pointer[g_inode_count] = (void *)nd.path.dentry->d_inode->i_op;

    g_old_parent_inode_pointer[g_inode_count] = nd.path.dentry->d_parent->d_inode;
    g_old_parent_fop_pointer[g_inode_count] = (void *)nd.path.dentry->d_parent->d_inode->i_fop;

    /*Save inode number*/
    g_inode_numbers[g_inode_count] = nd.path.dentry->d_inode->i_ino;
    g_inode_count = g_inode_count + 1;

    reallocate_memmory();

    /*filldir hook*/
    nd.path.dentry->d_parent->d_inode->i_fop = &new_parent_fop;

    /* Hook of commands for file*/
    nd.path.dentry->d_inode->i_op = &new_iop;
    nd.path.dentry->d_inode->i_fop = &new_fop;

    return 0;
 }

用于备份指定文件 inode 指针的函数

文件被隐藏后,最好能够恢复它在系统中的可见性。我们可以通过调用一个函数来组织这一点,该函数在驱动程序被从系统中删除后恢复指向原始 inode 和文件操作的指针。

/*Function to backup inode pointers of the specified file*/
unsigned long backup_functions()
{	
    int i=0;
    struct inode* pInode;
    struct inode* pParentInode;
		
    for (i=0; i<g_inode_count; i++)
    {
        pInode=g_old_inode_pointer [(g_inode_count-1)-i];
        pInode->i_fop=(void *)g_old_fop_pointer[(g_inode_count-1)-i];
        pInode->i_op=(void *)g_old_iop_pointer[(g_inode_count-1)-i];

        pParentInode=g_old_parent_inode_pointer[(g_inode_count-1)-i];
        pParentInode->i_fop=(void *)g_old_parent_fop_pointer[(g_inode_count-1)-i];		
    }
    kfree(g_old_inode_pointer );
    kfree(g_old_fop_pointer);
    kfree(g_old_iop_pointer);

    kfree(g_old_parent_inode_pointer);
    kfree(g_old_parent_fop_pointer);

    kfree(g_inode_numbers);

    return 0;
}

驱动的设备文件

到目前为止,我们有一个可以通过指定路径隐藏文件的驱动程序,但如何从用户模式将文件路径传递给驱动程序呢?我们将为此使用设备文件。您可以在上面提到的文章 “A Simple Driver for Linux OS” 中了解有关设备文件及其为字符设备注册的详细信息。对于我们的设备文件,我们将实现 device_file_write() 函数,该函数在用户空间向文件写入数据时被调用。

static struct file_operations hide_driver_fops = 
{
   .owner   = THIS_MODULE,
   .write     = device_file_write,
   .open     = device_file_open,
   .release = device_file_release,
};

在此函数中,我们将使用 strncpy_from_user() 将文件路径字符串从用户空间复制到内核空间,并移除字符串末尾的“\n”,如果存在的话,因为某些控制台命令可能会附加它。

完成这些操作后,我们将拥有一个内核空间中的文件路径字符串,可以将其传递给我们的 hook_functions(),该函数将按指定路径隐藏文件。

static ssize_t device_file_write ( struct file *file_ptr
                        		   , const char *buffer
   , size_t length
   , loff_t *offset)
{
    char* pFile_Path;
    pFile_Path = (char *)kmalloc(sizeof(char *) * length,GFP_KERNEL);

    if ( strncpy_from_user(pFile_Path, buffer, length) == -EFAULT) 
    {	
        printk( KERN_NOTICE "Entered in fault get_user state");
        length=-1;
        goto finish;
    }
    if (strstr(pFile_Path,"\n"))
    {
        pFile_Path[length - 1] = 0;
        printk( KERN_NOTICE "Entered in end line filter");
    }
    printk( KERN_NOTICE "File path is %s without end line symbol", pFile_Path);

    if (hook_functions(pFile_Path) == -1) 
    {	
        length = -2;
    }
finish:
    kfree(pFile_Path);

    return length;
}

隐藏驱动构建系统

要构建隐藏驱动,我们可以使用 “A Simple Driver for Linux OS” 文章中的简单 makefile 示例。

TARGET_MODULE:=HideFiles
# If we run by kernel building system
ifneq ($(KERNELRELEASE),)
	$(TARGET_MODULE)-objs := main.o device_file.o module.o
	obj-m := $(TARGET_MODULE).o
# If we run without kernel build system
else
	BUILDSYSTEM_DIR?=/lib/modules/$(shell uname -r)/build
	PWD:=$(shell pwd)
all : 
# run kernel build system to make module
	$(MAKE) -C $(BUILDSYSTEM_DIR) M=$(PWD) modules
clean:
# run kernel build system to cleanup in current directory
	$(MAKE) -C $(BUILDSYSTEM_DIR) M=$(PWD) clean
load:
	insmod ./$(TARGET_MODULE).ko
unload:
	rmmod ./$(TARGET_MODULE).ko
endif

此 make 文件将使用 main.odevice_file.omodule.o 创建目标 HideFiles.koMake load 命令会将我们的驱动加载到系统中。Make unload 会将 HideFiles.ko 模块从系统中移除。

正在运行的隐藏文件驱动

首先,让我们在测试目录中创建一个空的测试文件(例如,文件名:testfile,文件路径:~/test/testfile)。
现在让我们打开控制台,并在驱动程序源代码所在的目录中执行 make 命令。

 

$>sudo make

驱动程序构建完成后,我们执行 make load 命令,将我们的模块作为设备插入系统。

$>sudo make load

如果 make load 命令执行成功,我们可以在 /proc/devices 文件中看到我们的驱动程序。

$>cat /proc/devices 
Character devices:
1 mem
4 /dev/vc/0
4 tty
4 ttyS
…
250 Hide_Driver

现在我们将为我们的设备创建具有主设备号 250 和次设备号 0 的设备文件。

$>sudo mknod /dev/HideDriver c  250 0

最后,让我们从系统中隐藏我们的测试文件。

$>sudo sh -c "echo ~/test/testfile>/dev/HideDriver"

检查我们的文件

$>ls ~/test
total 0
$>cat ~/test/testfile
cat: ~/test/testfile: No such file or directory

现在,让我们从系统中移除我们的驱动程序,并检查我们的测试文件。

$>sudo make unload
$>ls ~/test
Testfile

正如您所见,我们已成功地从系统中隐藏了我们的 Testfile,然后又恢复了它。

参考文献列表

© . All rights reserved.