用fanotify来实现Linux下的进程隐藏

Index

  • 1.原理
  • 2.目前比较常见的Linux进程隐藏方法
    • 1.1.注入preload挂钩readdir
    • 1.2.直接替换ls/ps等系统程序
  • 3.fanotify是什么?
  • 4.使用fanotify进行进程隐藏

1.原理

在Linux下,一切皆文件。所有的进程信息,都存放在/proc/目录下,每一个pid一个目录。一般来说,遍历检查进程的方法,就是遍历检查/proc目录,检查所有名称是数字的子目录,检查其中的status等文件的内容,可以获得进程的相关信息:命令行、运行状态、程序路径、父进程pid等等。

那么,如何进行进程隐藏呢?

让别的进程看不到“/proc/[需要隐藏的进程pid]”这个目录就可以了。

2.目前比较常见的Linux进程隐藏方法

1.1.注入preload挂钩readdir

制作一个libxxx.so文件,在里面仿照系统接口制作一个readdir函数,然后修改系统的配置/etc/ld.so.preload,将这个libxxx.so文件的绝对路径配置上去。

这样,所有动态编译的程序在启动时,会优先加载这个libxxx.so库文件,然后再加载libc相关的so,这样一来,先入为主,libxxx.so中的readdir就顶替了libc中的readdir。

在我们仿冒的readdir中,调用libc的readdir获取结果并进行过滤,把需要隐藏的pid目录过滤掉然后返回。这样一来,ps/ls/top等系统命令都无法发现隐藏的进程了。

缺点:

  • 需要修改系统配置/etc/ld.so.preload,比较容易被发现。
  • 对静态编译的程序无效。
  • 全局挂钩,稳定性需要考虑好。

1.2.直接替换ls/ps等系统程序

这个方法更直接,直接把ls/ps等直接替换掉,想给调用者显示什么就显示什么,就是这么任性。

缺点:

  • 替换系统程序很敏感,很容易被发现。

3.fanotify是什么?

关于fanotify的写得很全面的介绍:fanotify 监控文件系统

fanotify在Linux内核2.6.37引入,相对inotify来说,其最大的特点是可以对文件的OPEN/ACCESS操作进行审核,换句话说:任何进程尝试打开/访问你关注的文件,你可以获得通知,并且知道是哪个进程在做这个事,然后你可以选择放行或者拒绝。

这个听起来很牛的功能,按照一些文章的介绍,是发明者给杀毒软件留下的好东东,在用户尝试打开某个文件时,杀软先收到通知,进行检查,然后做出动作。(和Windows下的杀软行为也确实很像)。

4.使用fanotify进行进程隐藏

结合第1节和第3节,相信已经有人猜出来我们要干什么了。

没错,我们要用fanotify来监控“/proc/[需要隐藏的进程pid]”这个目录,针对其下的所有访问,不是授权进程统统拒绝。

先来看看测试程序的效果:

编译和运行:
用fanotify来实现Linux下的进程隐藏_第1张图片
示例程序stealth.cpp编译为可执行程序stealth,他启动后,会打印自己的pid,然后使用fanotify对/proc/下自己的目录进行监视,拒绝所有访问请求。

从上图中可以看出stealth进程的pid是2596。

下面我们来进行测试:
用fanotify来实现Linux下的进程隐藏_第2张图片
可以看到ps/lsof都无法发现这个进程,我们列取/proc目录,可以看到有2596这个目录,但是尝试列取其中的内容或尝试查看其中的文件内容,皆提示:Operation not permitted。

最后,上代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
//#include "fanotify-syscalllib.h"
//#include "fanotify.h"

static int _fafd = 0;
static bool _working = true;
static bool _thread_created = false;
static pthread_t _reader_thread;

static void fanotify_callback(struct fanotify_event_metadata* metadata)
{
    if ((FAN_OPEN_PERM & metadata->mask) != 0 || (FAN_ACCESS_PERM & metadata->mask) != 0)
    {
        bool allow = true;
        do
        {
            if (getpid() == metadata->pid)
            {
                break;
            }

            //get filepath of program
            char exe_filepath[64] = {0};
            snprintf(exe_filepath, sizeof(exe_filepath), "/proc/%d/exe", metadata->pid);
            char process_filepath[PATH_MAX] = {0};
            realpath(exe_filepath, process_filepath);

            //get filepath of target file
            char fd_path[64] = {0};
            snprintf(fd_path, sizeof(fd_path), "/proc/self/fd/%d", metadata->fd);
            char filepath[PATH_MAX] = {0};
            realpath(fd_path, filepath);

            printf("PROCDENY [%s] access [%s]\n", process_filepath, filepath);

            allow = false;
        } while (false);

        struct fanotify_response resp;
        resp.fd = metadata->fd;
        resp.response = allow ? FAN_ALLOW : FAN_DENY;
        int write_ret = write(_fafd, &resp, sizeof(resp));
        if (write_ret < 0)
        {
            printf("write resp failed %d\n", write_ret);
        }
    }
}

static bool read_data()
{
    struct pollfd fds;
    memset(&fds, 0, sizeof(fds));
    fds.fd = _fafd;
    fds.events = POLLIN;

    int res = poll(&fds, 1, 1000);
    if (-1 == res)
    {
        printf("poll from fanotify failed.\n");
        return false;
    }
    else if (0 == res)
    {
        //timeout
        return true;
    }
    else if ((fds.revents & POLLIN) == 0)
    {
        //not poll in
        return true;
    }

    ssize_t len = 0;
    char buf[4096] = {0};
    bool error = false;
    while (_working && !error && (len = read(_fafd, buf, sizeof(buf))) > 0)
    {
        struct fanotify_event_metadata* metadata = (struct fanotify_event_metadata*) buf;

        while (FAN_EVENT_OK(metadata, len))
        {
            if ((FAN_Q_OVERFLOW & metadata->mask) != 0)
            {
                printf("fanotify queue overflow\n");
                break;
            }

            if (metadata->vers < 2)
            {
                printf("Kernel fanotify version too old\n");
                error = true;
                break;
            }

            if (metadata->fd >= 0)
            {
                fanotify_callback(metadata);
                ::close(metadata->fd);
            }

            metadata = FAN_EVENT_NEXT(metadata, len);
        }
    }

    return (!error);
}

static void* fawatch_reader(void* param)
{
    while (_working)
    {
        if (! read_data())
        {
            printf("read fa data failed.\n");
            break;
        }
    }

    printf("fa watcher reader exit.\n");

    return NULL;
}

static bool start()
{
    _fafd = fanotify_init(FAN_CLASS_CONTENT | FAN_NONBLOCK, O_RDONLY | O_LARGEFILE);
    if (-1 == _fafd)
    {
        printf("init fanotify failed.\n");
        return false;
    }

    if (0 != pthread_create(&_reader_thread, NULL, fawatch_reader, NULL))
    {
        printf("start reader thread failed.\n");
        return false;
    }
    _thread_created = true;

    pid_t self = getpid();
    printf("My PID is %d\n", self);

    char proc_filepath[32] = {0};
    snprintf(proc_filepath, sizeof(proc_filepath), "/proc/%d", self);
    if (0 != fanotify_mark(_fafd, FAN_MARK_ADD,
                FAN_OPEN_PERM | FAN_ACCESS_PERM | FAN_EVENT_ON_CHILD | FAN_ONDIR,
                AT_FDCWD, proc_filepath))
    {
        printf("add fawatch for %s failed.\n", proc_filepath);
        return false;
    }

    printf("You can try to find me\n");

    return true;
}

static void stop()
{
    if (_thread_created)
    {
        _working = false;
        pthread_join(_reader_thread, NULL);
    }

    if (0 != _fafd)
    {
        close(_fafd);
    }
}

int main(int argc, char* argv[])
{
    if (start())
    {
        printf("press ENTER to end.\n");
        getchar();
    }

    stop();

    return 0;
}

你可能感兴趣的:(安全)