在Linux下,一切皆文件。所有的进程信息,都存放在/proc/目录下,每一个pid一个目录。一般来说,遍历检查进程的方法,就是遍历检查/proc目录,检查所有名称是数字的子目录,检查其中的status等文件的内容,可以获得进程的相关信息:命令行、运行状态、程序路径、父进程pid等等。
那么,如何进行进程隐藏呢?
让别的进程看不到“/proc/[需要隐藏的进程pid]”这个目录就可以了。
制作一个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等系统命令都无法发现隐藏的进程了。
缺点:
这个方法更直接,直接把ls/ps等直接替换掉,想给调用者显示什么就显示什么,就是这么任性。
缺点:
关于fanotify的写得很全面的介绍:fanotify 监控文件系统
fanotify在Linux内核2.6.37引入,相对inotify来说,其最大的特点是可以对文件的OPEN/ACCESS操作进行审核,换句话说:任何进程尝试打开/访问你关注的文件,你可以获得通知,并且知道是哪个进程在做这个事,然后你可以选择放行或者拒绝。
这个听起来很牛的功能,按照一些文章的介绍,是发明者给杀毒软件留下的好东东,在用户尝试打开某个文件时,杀软先收到通知,进行检查,然后做出动作。(和Windows下的杀软行为也确实很像)。
结合第1节和第3节,相信已经有人猜出来我们要干什么了。
没错,我们要用fanotify来监控“/proc/[需要隐藏的进程pid]”这个目录,针对其下的所有访问,不是授权进程统统拒绝。
先来看看测试程序的效果:
编译和运行:
示例程序stealth.cpp编译为可执行程序stealth,他启动后,会打印自己的pid,然后使用fanotify对/proc/下自己的目录进行监视,拒绝所有访问请求。
从上图中可以看出stealth进程的pid是2596。
下面我们来进行测试:
可以看到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;
}