权限维持之加载动态链接库隐藏进程 tcp连接

之前聊过rootkit的使用和原理

初探rookit(另一种角度看维权) (qq.com)

也聊过用LKM隐藏进程 tcp的原理

从linux内核初窥LKM(抛砖引玉之rootkit隐藏进程 or tcp连接原理) (qq.com)

今天继续这个话题---如何隐藏进程  or tcp链接,不过今日手法为应用级--劫持预加载动态链接库,一些简单的rootkit会用到该办法。

本文目录

何为预加载动态链接库以及工作原理

预加载动态链接库(Dynamic Link Library,DLL)是一种在Linux和Unix-like系统中常用的技术,它允许用户在程序启动前指定额外的共享库(shared libraries),这些共享库中的函数可以覆盖或包装(wrap)程序原本的函数调用。这种技术通常用于调试、修改程序行为、或监控API调用等目的。

预加载的基本原理是在程序启动时,由动态链接器(dynamic linker)先于其他库加载指定的共享库。当程序调用某个函数时,动态链接器会首先在预加载的库中查找是否存在该函数的定义。如果找到了,就会调用预加载库中的该函数;如果没有找到,才会继续在其他动态库或程序自身中寻找并调用该函数。

ps pgrep命令原理

strace ps看一下ps所有的系统调用

不够直观,换成strace -e open,openat ps

ok,现在清楚多了,ps pgrep命令就是去遍历/proc/pid下的文件,然后读取 关闭,得到进程信息,在此过程中,ps命令调用的函数为opendir和readdir(readdir64),readdir函数就是读目录,遍历目录里的文件和子目录。opendir作用为打开并读取目录。

对了,再说一下/proc是什么

/proc是一个虚拟文件系统,要用于提供内核和进程信息的接口,里面会包含系统详细信息。比如/proc/pid(这里的pid就是具体的数字,对应该进程,如/proc/1)里包含该进程详细信息,cpuinfo meninfo version这些包含cpu 内存 内核版本信息,以及硬件网络这些,这里我们只需关注/pid。

那么我们自定义一个readdir函数,在执行过程中跳过读取我们想要隐藏的进程信息,然后把hook 原来的readdir函数,把地址定位到自定义函数。那么用户在使用ps pgrep这些命令去查看进程时,是不是就会跳过我们指定的进程,也就是说读不到指定进程的信息了,输出结果里自然就没有该进程的信息,是不是就达到隐藏的目的了?

下面开始实验之前,再解释一些其他原理。

LD_PRELOAD

何为LD_PRELOAD以及原理

LD_PRELOAD是Linux系统的一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。一方面,我们可以以此功能来使用自己的或是更好的函数(无需别人的源码),而另一方面,我们也可以以向别人的程序注入程序,从而达到特定的目的。

如何设置

例如

export LD_PRELOAD=/libmyhooks.so

开始实验

先生成一个叫milu.elf的马子并连接

靶机里运行milu.elf以后看一下靶机相关进程

生成so文件并修改LD_PRELOAD

有前人写好的工具,项目地址

GitHub - gianlucaborello/libprocesshider: Hide a process under Linux using the ld preloader (https://sysdig.com/blog/hiding-linux-processes-for-fun-and-profit/)

修改processhider.c里第12行为

static const char* process_to_filter = "milu.elf";

为什么要修改这里会在下面的原理分析里讲到,读者莫急

编译 and 导入

gianluca@sid:~/libprocesshider$ make
gcc -Wall -fPIC -shared -o libprocesshider.so processhider.c -ldl
gianluca@sid:~/libprocesshider$ sudo mv libprocesshider.so /usr/local/lib/
export LD_PRELOAD=/usr/local/lib/libprocesshider.so

再次查看进程

隐藏了

那如果是我们要隐藏多个进程呢,所以需要对该工具进行改进

工具改进和原理

先看效果

多创了几个进程,下图是没隐藏前

隐藏后

简简单单,下面讲

工具原理

前面已经说过了,LD_PRELOAD是一个环境变量,允许用户定义在程序运行前优先加载的动态链接库。我们只需要hook掉readdir就行了,其实,这就是这款工具的原理,下面看一下具体实现的代码。

先看processhider.c。

先是定义了要过滤的进程名。

static const char* process_to_filter = "evil_script.py";

get_dir_name获取指定的目录名,get_process_name获取指定进程名,鉴于文章篇幅不做解读。

DECLARE_READDIR(dirent, readdir)DECLARE_READDIR(dirent64, readdir64)两个宏分别定义了自定义版本的readdir  readdir64 函数,这里是关键,先说大致流程--获取原始函数地址,然后对这两个函数进行重定义。

具体如下

查找原始函数地址

if(original_##readdir == NULL) {
  original_##readdir = dlsym(RTLD_NEXT, #readdir);
  if(original_##readdir == NULL)
  {
      fprintf(stderr, "Error in dlsym: %s
", dlerror());
  }
}

循环遍历并过滤指定目录

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;
}

替换

DECLARE_READDIR(dirent64, readdir64);
DECLARE_READDIR(dirent, readdir);
改进

很简单,定义一个字符串数组用于存放多个要隐藏的进程名称,然后在在过滤的时候循环一下就行了,代码太长了,而且比较简单,读者动下手就行,不放完整代码了,只贴一些关键代码得了,等麋鹿的知识星球上线以后再把完整代码放进去。

排查

看环境变量的值

echo $LD_PRELOAD

/proc下文件并没有被隐藏,可以用脚本去遍历一下/proc所有的目录,也就是获取到所有进程pid,再和ps pgrep的结果比对一下,遇到ps里没有的进程,可以kill -20一下,如果pid不存在会报错,存在的化也不会真的kill掉该进程。然后用上一篇说到的办法去排查该进程

别当初级猴子了,五分钟教你linux维权和排查思路,助你圆梦4k! (qq.com)

清除

还原LD_PRELOAD就行了

本文到此结束,下次麋鹿有空的时候继续谈谈其他隐藏进程的手法,留个彩蛋,大家可以猜一下下期主题