Fancy Bear 是一名伐木工人,没关系 - 深入了解 Drovorub 的内核组件

Xcellerator

密码学Linux其他逆向工程

Fancy Bear 是一名伐木工人,没关系 - 深入了解 Drovorub 的内核组件

2020-10-29 :: TheXcellerator

# linux # rootkit # drovorub #花式熊

名字里有什么?

早在 8 月,美国国家安全局 (NSA) 和联邦调查局 (FBI) 就俄罗斯 GRU 开发的一款此前未公开的恶意软件联合发布了网络安全咨询,该恶意软件名为“Drovorub”,该名称源自俄语单词“дрово”和“руб”,合起来为“Drovorub”。 “樵夫”,或者按照我的说法,“伐木工人”。

这个特定的恶意软件比平常更有趣的是它包含一个内核模块 Rootkit!在这篇文章中,我想介绍一下该内核模块使用的一些技术,以及它与我们在其他文章中已经介绍过的技术的关系。

有点令人沮丧的是,该报告的细节相当稀疏。它告诉我们恶意软件有什么能力,但没有详细说明它是*如何实现的。*例如,我们被告知Drovorub“通过直接修补函数或通过覆盖指向函数的函数指针”来挂钩内核函数。使用什么方法来做到这一点?短语“覆盖指向函数的函数指针”可能指的是修改系统调用表(存储在sys_call_table内核对象中)——这是一种相当古老且混乱的技术,仅适用于挂钩系统调用。然而,该报告似乎表明 Drovorub 作者倾向于挂钩常规内核函数,因为没有提到系统调用。

至于“直接修补功能”——我不确定。我非常怀疑 Drovorub 直接在内核内存中修补函数代码。虽然这通常是一种可行的攻击(看看FreeBSD 12 的示例),但我看不出它在 Linux 下运行良好。相反,这些词可能指的是我们在本系列中一直使用的 Ftrace 方法。可悲的是,这只是我的猜测。如果有人有更好的想法或见解,我很想听听!

当我们研究报告中提到的一些技术时,我将链接到本系列中任何相关的早期帖子,但如果您错过了任何内容,这里是完整列表。

我写的特权容器转义也将是相关的:

如果您正在阅读官方报告,那么有趣的部分是“基于主机的通信”“规避”部分。

快速说明:我不会尝试重新创建 Drovorub 恶意软件中的任何代码。如果您想这样做,本系列中包含的信息绰绰有余,但我既不赞同也不鼓励这样做。请尽可能了解,以便更好地了解此类 APT 所带来的威胁。

与用户空间通信

首先,让我们看看 Drovorub 内核模块与用户空间通信的方式。该报告概述的方法非常聪明,涉及我们已经研究过的两种技术的组合。

主要技术是我们在特权 docker escape中使用的方法的变体,但 Drovorub 不是通过 procfs 文件,而是通过 下的伪设备之一进行通信/dev//proc/escape在我们的示例中,我们通过编写自定义读/写处理程序并向内核注册 procfile 来实现一个全新的“文件”。

Drovorub 使用的方法之所以如此聪明,是因为他们劫持了现有的设备文件,而不是创建新的文件。报告指出这 /dev/zero就是所使用的设备,但稍后我们会看到为什么这是一个奇怪的选择。

您可能还记得第 4 部分,当我们劫持内核函数以在读取时random_read()返回序列0x00而不是随机字节时,我们已经自己实现了此功能。/dev/random

通过结合这两种方法,Drovorub 能够通过以下方式在其内核空间和用户空间组件之间进行通信:

用户态 -> 内核:
  • 用户态进程写入命令/dev/zero
  • 内核/dev/zero通过挂钩相应的写入处理程序来拦截写入
  • 内核执行与发送的命令相关的任何功能
内核 -> 用户层:
  • 内核SIGUSR1向用户态进程发送一个信号,表明有一些东西可以从中读回/dev/zero
  • 用户态进程读取内容/dev/zero
  • 内核通过挂钩读取处理程序将数据缓冲区返回到用户态进程(在此事务之后,后续读取将/dev/zero正常运行)

没有解释 rootkit 究竟如何确定向哪个 PID 发送信号。报告的表 XIII详细介绍了 Drovorub 使用的命令格式。用户态进程的 PID 很可能作为附加数据的一部分传递给任何需要某种输出的命令(至少在我看来这是有意义的,但我在这里完全猜测)。

通过查看drivers/char/mem.c,我们找到了存储伪设备处理程序的结构。特别是,我们可以看到读处理程序写处理程序分别设置为和。zero_fops file_operations``/dev/zero``read_zero``write_zero

读处理程序可以在第 729 行进一步找到。这将是一个非常容易编写钩子的函数 - 一个简单的if语句来检查某些条件将决定我们是否应该用用户的一些秘密数据填充提供的缓冲区,或者只是0x0通过调用真实的数据来填充它功能。

写处理程序有点不同。搜索 为我们提供了第 902 行write_zero的定义,该定义以标识。这个函数就像你想象的一样简单:它只是返回参数来向用户表明缓冲区已被写入(即使它被丢弃)。问题在于,这也是写入处理程序,其下是 的结构。劫持似乎有点混乱,因为当只需要一个设备时,它会同时干扰两个不同的设备。write_zerowrite_nullcount``write_nullnull_fopsfile_operations``/dev/null``write_null

/dev看看世界可读可写的所有条目,我首先认为此功能最合乎逻辑的候选者是/dev/random/dev/urandom。然后我意识到它们也共享一个写入处理程序(请参阅此处此处),因此无论您喜欢与否,无论哪种方式,您最终都会同时劫持对两个设备的写入。

进程隐藏

该报告谈到的第一个基于内核的功能是隐藏进程。我们在第 7 部分中对此进行了介绍,其中我们只是屏蔽了目录列表,/proc/以不显示我们想要隐藏的 PID。

Drovorub 加倍努力并结合使用另一种方法。根据内核版本,它会挂钩find_pid_ns()find_pid()find_task_by_pid_type()。最后两个不再存在于内核中 - 它们在 2.6.27 周期的某个时间被删除 - 早在 2008 年 10 月!(请参阅末尾的一些想法来了解为什么会出现这种情况)。

让我们看一下find_pid_ns()

struct pid *find_pid_ns(int nr, struct pid_namespace *ns)
{
    return idr_find(&ns->idr, nr);
}
EXPORT_SYMBOL_GPL(find_ns_pid);

复制

这是主要的函数挂钩的东西。的描述idr_find()让我们更好地了解这个函数正在做什么,但更好的解释在内核文档

本质上,IDR是内核中的通用 ID 分配系统 - 无论这些 ID 是文件描述符、PID、设备号,还是 SCSI 标签等更神秘的东西。该idr_find()函数接受一个 ID 号(在我们的例子中是 PID)和 a pid_namespace,并在该命名空间内查找与该 ID 号相对应的指针。

对于顽固分子来说,这听起来有点复杂。idr_find()是一个包装器radix_tree_lookup(),而它又是一个包装器__radix_tree_lookup()。这就是奇迹发生的地方。基数树是计算机科学中大量借鉴图论的概念之一。这里重要的是我们使用一个数据结构__radix_tree_lookup()来获取条目。

关于如何find_pid_ns()编写函数钩子的线索可以在 的描述中找到idr_find()在这里,我们看到NULL返回的指针表明 ID 未分配(或者指针NULL本身与 ID 关联 - 这将是相当奇怪的)。

这一定是 Drovorub 正在做的事!nr对参数进行简单检查,find_pid_ns()看看它是否与我们想要隐藏的 PID 之一匹配,并且它可以返回来自 的指针idr_find(),或者只是返回NULL以指示该 PID 不与内存中的任何进程关联。

这里的巧妙之处在于,通过挂钩find_pid_ns()而不是idr_find()直接,PID 分配完全不受影响!内核将使用各种idr_radix_tree_函数来检查分配了哪些 PID,以及在分配新的 PID 之前未分配的最低 PID 是多少。这很重要,因为 Drovorub 通过首先向进程发送SIGUSR1信号(此处描述)来与用户区进行通信。事实上,如果我们遵循函数调用链,sys_kill()我们最终会得到pid_nr_ns()

pid_t pid_nr_ns(struct pid *pid, struct pid_namespace *ns)
{
    struct upid *upid;
    pid_t nr = 0;

    if (pid && ns->level <= pid->level) {
        upid = &pid->numbers[ns->level];
        if (upid->ns == ns)
            nr = upid->nr;
    }
    return nr;
}
EXPORT_SYMBOL_GPL(pid_nr_ns);

复制

对于感兴趣的人来说,链条是:sys_kill(), prepare_kill_siginfo(), task_tgid_vnr(), __task_pid_nr_ns(), pid_nr_ns()

无论出于何种原因(可能有一个很好的理由),sys_kill()都不会靠近 IDR 子系统。如果确实如此,那么 Drovorub 可能会遇到很多问题,无法以这种方式隐藏进程并仍然向它们发送信号。我认为这是一个广泛的、内核范围的决定,如果采取不同的做法,将会产生很多影响(如果您知道确切的原因,请告诉我!)。

文件隐藏

尽管采用了上述技术,系统上的所有活动 PID 仍将显示在 下/proc/。除此之外,Drovorub 恶意软件还隐藏了其自身的用户态可执行组件,如报告中所述。如前所述,它采用与第 6 部分中所做的类似方法,除了我们sys_getdents64()直接挂钩时,使用 Drovorub 代替 hooks d_lookup()iterate_dir()并且对于 4.1 之前的内核版本,vfs_readdir().

确切地说,Drovorub 作者决定使用 hookiterate_dir()而不是的原因sys_getdents64()尚不清楚,特别是看到 assys_getdents64()使用,正如您在第 366 行iterate_dir()看到的那样。也许他们选择节省开销 - 如果您不挂钩系统调用,那么您不必担心内核版本 4.17 中的整个更改带来的多个调用约定(有关更多信息,请参阅第 2 部分)。pt_regs

查看sys_getdents64(),我们看到系统调用是通过使用提供的文件描述符进行调用开始的。fdget_pos()这将返回一个fd结构体,其中包含一个file结构体作为子字段。该file结构现在传递iterate_dir(). 仔细观察该file结构体,我们发现它有一个path名为 的结构体字段f_path。继续沿着兔子洞往下走,我们看到了一个结构体,我们在第 6 部分dentry中就已经了解了它!结构体包含一个对象,该对象是文件的名称!dentry``d_name

很可能,Drovorub 的钩子首先与预先配置的字符串或用户空间组件指示的字符串iterate_dir()进行比较(见上文)。如果它获得匹配,它可能只返回,否则它可以调用真实的.file->f_path.dentry->d_name``0``iterate_dir()

对于所有这一切,还有一个最后的警告,如此处所解释。在每个结构体 ( )lookup()的深处都有一个称为函数指针的函数指针。Drovorub 也设法挂钩这个函数,但我不确定这个边缘情况到底是为了什么以及如何出现的。path``f_path.dentry->d_inode->i_op->lookup

插座隐藏

我想讨论的最后一项技术可能是最容易引起猜测的技术。官方报告指出,“Drovorub-kernel 模块挂钩适当的内核函数并过滤掉隐藏的套接字。/proc/net它通过在 proc 文件系统中的目录中打开适当的接口来确定要挂钩的函数”。接下来解释了tpctcp6和下udpudp6“文件”之间的区别/proc/net/

这没有多大意义。我们/proc/net/tcp之前在第 8 部分中已经研究过该文件,它不包含函数或函数指针。我怀疑 NSA/FBI 上述声明的意思是 Drovorub 挂钩、、 和(我们只在第 6 部分挂钩)。显然,没有可以从目录中打开的“适当的界面”。tcp4_seq_show() tcp6_seq_show()udp4_seq_show()udp6_seq_show()tcp4_seq_show()``/proc/net

在我看来,我认为 Drovorub 很可能使用与第 6 部分中使用的几乎完全相同的技术- 除了它可能已将其扩展为包括其他 3 个“文件” tcp6、、udp4以及udp6- “Yara 规则 #4”似乎以表明情况确实如此。一个重要的区别是,该报告指出,Drovorub 不仅能够根据源端口(如我们所做的那样)过滤连接,还能够根据目标端口(即,它根据 以及 进行过滤skc_dport)来过滤skc_num连接。

另一个有趣的功能是 Drovorub 可以隐藏隐藏进程拥有的任何连接。使用我们信任的strace,我们可以(再一次)看看netstat。如果我们netstat以 root 身份运行,我们可以看到分配给每个连接的所有进程(否则我们只能看到我们用户拥有的进程)。检查 的输出sudo strace -u root netstat -tunelp,我们发现它循环遍历/proc/x/fd/y每个 PIDx和文件描述符y,以便识别哪个进程拥有tcptcp6udp和中每个条目的哪个条目udp6。这意味着隐藏进程拥有的连接的能力已经通过将其 PID 隐藏在/proc/!

事情还没说完

我想了解更多但报告未能提供的最后一件事是 Drovorub 隐藏内核模块的方法。表 XIV确认可以指示它隐藏模块(我假设是通过名称),但这就是我们得到的全部。鉴于我们已经知道的情况,我认为我们可以对他们做了什么做出合理的猜测。

仔细查看表 XIV的措辞,我们了解到该hm命令将“隐藏模块”。它是一个模块而不是模块这一事实表明,Drovorub 能够从模块列表中隐藏的不仅仅是它本身。

第 5 部分中,我们开发了一种通过对象修改链表来隐藏 rootkit 模块的方法THIS_MODULE。实际上,没有理由不能扩展它来隐藏其他模块 - 所有模块需要做的就是循环加载的内核模块(感谢链接列表,这很简单!)并调用list_del()符合某些条件的模块- 据称是成功strcmp()对抗.name模块领域的。

唯一稍微复杂的一点是跟踪指向已隐藏模块的指针,因为如表 XIV所示,该um命令将取消隐藏模块。无论 Drovorub 使用什么内部簿记设备,它都需要跟踪与已保存指针关联的模块名称,以便正确的模块根据需要返回到正确的位置。我感到惊讶(也令人沮丧!)Drovorub 功能的如此重要部分以及一些可能非常有趣的设计选择没有进入报告。

结束语

通读该报告后,我印象最深刻的一件事是对兼容性的关注——甚至超出了我们在本系列其他文章中所做的尝试。Drovorub 甚至可以挂钩仅存在于内核版本 2.6 及更低版本中的内核函数(此时已经超过 12 年了!)。这是否告诉我们一些关于预期目标的信息?

我们在哪里还能看到如此早期的内核版本?集体智慧告诉我们,物联网和嵌入式世界仍然经常使用这种过时的内核。在研究这篇文章时,我发现了 Fraunhofer 于 2020 年 6 月发布的一份题为“家庭路由器安全报告”的报告。第 8 页上的饼图表明,他们调查的路由器中有 31.4% 正在运行 Linux 内核 2.6.36!尤其令人担忧的是,内核模块签名直到内核 3.7 才实现 - 这将使针对 Drovorub 的缓解变得极其困难。

内核模块组件的传递机制可能表明路由器不是唯一的预期目标。报告第 5 页的底部解释了 Debian 和 Red Hat 系统(/etc/modules.conf等)的常用内核模块加载方法。我觉得这很有趣,因为这意味着内核模块本身必须作为.ko文件存在于文件系统的某个位置(即使在加载模块后它从目录列表中隐藏)。另一种方法是将内核模块直接加载到内存中(正如我的特权 docker escape示例中所做的那样) - 尽管我将内核对象硬编码为可执行文件中的数组,但没有理由不能通过相反,这样就不会在文件系统上的任何地方留下内核模块的残余。Drovorub 作者采用的方法无疑使法医分析师的工作变得更加容易。

为什么这表明嵌入式设备不是唯一的目标?一般来说,这些类型的设备上的持久性是不必要的(最后一次重新启动路由器是什么时候?)。这仍然使服务器成为一种可能性(遗憾的是桌面 Linux 的人口统计数据仍然太小,无法认真作为目标),我认为这是最有意义的。也许两者都是目标,或者也许作者只是对冲他们的赌注。

我希望你喜欢这篇文章,就像我喜欢写它一样。我很惊讶 Drovorub 使用的许多技术与本系列之前的文章中探讨的技术如此相似。看来,在许多情况下,能够加载内核模块使得攻击者可以在 Linux 系统上猖獗,而几乎无法阻止他们。

直到下一次…

免责声明

这篇文章完全是有根据的猜测。我与 NSA、FBI 和 GRU 没有任何关系,也没有机会检查 Drovorub 恶意软件。我非常谨慎地没有尝试重新创建任何我怀疑 GRU 可能在 Drovorub 开发中使用过的源代码。请不要自己尝试这样做。我希望阅读这篇文章(或此博客上的任何其他文章)的任何人都可以使用获得的信息来更好地保护自己和他人免受此类恶意软件的侵害。

阅读其他帖子


←Linux Rootkit:内核 5.7+ 的新方法牙齿出血深潜→

哈维菲利普斯 2020 - 伦敦, 英国:: panr制作的主题

该网站是闹鬼网络的一部分

](https://xcellerator.github.io/posts/bleeding_tooth/)

哈维菲利普斯 2020 - 伦敦, 英国:: panr制作的主题

该网站是闹鬼网络的一部分

<<< 随机 >>>

  • 12
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

丁金金

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值