如何创建自己的Tracker

 

0x00 前言

某一天,我正在浏览abuse.ch。这个网站会收集用户提交的恶意/可疑URL,我偶然发现了一些非常有趣的东西。我看到某推特用户Gandylyan1每日上传大量名为Mozi的恶意软件样本(可参阅此文)。此僵尸网络是一IoT P2P僵尸网络,似乎正在疯狂地传播着。当我撰写本文时,Gandy依然在上传样本,目前有24,709个IP上传到abuse.ch。

该恶意软件非常有趣,并且不是很复杂,同时不是很难理解。它会使用已知exp及暴力破解方式在IoT设备中传播,若它已连接到某IoT设备,则会在该设备上启动http服务,之后将自身上传到随机端口,并将其托管在该IoT设备的IP地址上。接下来该设备开始扫描并攻击网络,当它成功接管另一台设备时,新感染的设备将从先前感染的设备接收Mozi。于是我决定为此僵尸网络构建一个Tracker。

可惜我的Linux相关知识仅限于我知道ls -la命令功能。但是为僵尸网络构建一个Tracker的想法使我夜不能寐。经过短暂的搜索,我发现了Intezer创建的工具。此工具是一个小型的Python项目,可以让研究人员通过模拟OS环境来伪造出受恶意软件感染的客户端。研究人员所要关注的就是恶意软件通信协议。不需要蜜罐,不需要虚拟机,什么也不需要。之后我在GitHub上搜索了所有开源的恶意软件,发现了Quasar,一款开源RAT。研究该RAT是了解恶意软件,逆向开源恶意软件以及了解其网络相关工作原理的好方法。我们的小实验的绝佳候选人!

  • 必备知识:
  1. Wireshark相关的基础知识
  2. 编程相关知识
  3. Python
  4. C#
  • 所需工具:
  1. VMWare
  2. Visual Studio Community
  3. Python 3.8
  4. Sublime Text Editor 3
  5. Dnspy
  6. De4dot
  • 目标:
  1. 我们想知道Quasar客户端是如何连接到服务器的
  2. 我们想知道Quasar是如何构造发送给服务器的消息的
  3. 我们想知道是否存在用于处理消息的加密/解密过程
  4. 我们想知道服务器如何处理客户端消息的(因为我们有Quasar的源码,所以这是可能的)

 

0x01 如何阅读Quasar源码

我们将下载的Quasar源代码加载到Visual Studio 2019 Community(下载地址)中:

img

我们对客户端代码,以及如何与其他对象协作都很感兴趣——Common包含各个公用程序,Server包含Server端代码。据我所知,所有C#程序都以Program.cs开始,所以我们将从此开始,让我们打开Quasar.Client并找到Program.CS文件:

img

如果我们右击QuasarClient,然后单击”转到实现”,我们将在此处看到一些有趣的东西:

img

首先解释从Client类继承的QuasarClient类。其工作是管理客户端中发生的所有事件。它具有专门的函数来处理bot的注册(OnClientState),并读取事件(OnClientRead)以及处理失败事件(OnClientFail)。

img

OnClientState函数尝试发送数据包到服务器。若想了解如何创建该消息,我们可以查看ClientIdentification类的构造函数:

img

让我们回到Program.cs ,并查看ConnectClient.Connect

img

这使我们回到了Client类:

img

这应该是我们第一个目标的答案!若要启动连接,客户端首先会建立一个SSL流,然后可能使用ValidateServerCertificate回调函数和AuthnticateAsClient正在进行某种形式的验证。让我们暂时先不管这些,因为我们只是在探索代码的工作方式。接下来会发生什么?如果我们通过Client基类访问OnClientState,这将导致我们进入事件处理程序本身,那么若要找到触发该事件的函数,我们必须转到QuasarClient.cs并通过其查看该函数的实现。如我们之前所见,OnClientState函数触发了client.Send函数:

img

说实话,我不懂C#,但是我在这里通过我的直觉看到唯一有价值的东西是ProcessSendBuffers,所以让我们查看下,看看是否能有所收获:

img

使用与之前相同的策略,让我们查看下SafeSendMessage

img

现在我们暂且不访问OnClientWrite,因为我担心它不会带我们到我们想要的地方去。我们可以查看位于PayloadWriter类中的WriteMessage

img

该函数会将序列化消息(我稍后会解释什么是序列化)写入SSL数据流!因此,让我们通过一张流程图来说明我们的发现:

img

上图是非常浅显且不完整的,随着我们进行动态分析,我们可以在该图上进行扩充,因此让我们在Visual Studio中编译Quasar项目,并将编译后文件移至虚拟机并开始研究它。

 

0x02 分析Quasar样本

编译Quasar项目并将其移至虚拟机后,即可启动Quasar:

img

此时屏幕应该会弹出一消息框,该消息用于构建X509证书(非常重要)。该证书负责在客户端和服务器之间创建有效的SSL数据流。Quasar会生成一个X509证书并将该证书绑定到所有生成的客户端。可以在此处了解有关SSL的更多信息。

生成证书后,就可以构建样本了,单击Builder然后使用builder菜单进行相关配置,其中最重要的部分如下所示:

img

这里有2个IP:一个是本机回送地址,另一个是该虚拟机IP。我建议将客户端绑定到当前虚拟机IP,因为这样可以模拟从当前虚拟机与外部主机服务器的连接(主机也是VMWare本地网络中的一员)。您可以使用任何您喜欢的端口,这里我使用了27015端口。生成客户端后,您应该可以在当前目录看到它。我们将其在dnspy中打开(dnspy.NET反编译器):

img

但是我们遇到了代码混淆,不用担心,我们可以使用de4dot,它是个.NET反混淆工具,运行它之后,我们可以得到一个去混淆的Quasar客户端:

img

尽管我们的Quasar客户端代码是去混淆后的,但符号已经消失了,不用担心,因为我们拥有完整的源代码,所以接下来让我们开始调试。我只想验证我的流程图是否正确,所以让我们单击开始调试,然后在入口点设置一个断点(我强烈建议根据源代码重命名这些函数和类名,但是由于调试了许多次,我对此已经非常熟悉了)。请确保Quasar服务端正在运行中。我们将在Class0.smethod_3()遇到第一个问题,这是第二个初始化方法:

img

它不会返回True,从而导致客户端无法正确执行和退出。但是为什么会这样?让我们看一下源码:

img

上图中标红的if语句,会在初始化后通过返回true来安装并将客户端连接到服务器,但由于此时客户端正在运行的current path不等于install path,所以它不会执行。要理解上述说明,需要回到Builder

img

因此,此代码块会检查客户端当前是否正在AppdataRomaing目录下运行(于本文的情况下),如果没有,它将执行以下代码:

img

此代码会处理两个问题:其一是客户端检测到另一个Quasar实例正在运行,通过检测互斥体;其二是客户端已安装到受害主机中,但实现了持久化,此时会终止进程并删除该文件,然后将其移至我们指定的install文件夹后重新启动。您可以查看Install方法自行研究,因为源码已经给出。它使得研究人员可以真正了解如何开发恶意软件。下面让我们做两件事:

  1. 更新我们的流程图
  2. 将客户端移动到指定的安装目录并启动

img

让我们从预设的安装目录中调试客户端,看看会发生什么,请确保Quasar服务端正在运行。此外,我启动了Wireshark来监视流量(下图是我自己的设置,IP地址及端口在您的计算机上将会有所不同):

img

我将从AppdataRoaming目录重新启动客户端,然后直接跳到Client.Connect函数:

img

这次我们恰好到达了想要的位置(专家提示:您可以右键单击dnspy对象并更改其名称) 。

img

这里有三处重点,首先是RemoteCertificationValidationCallBack——它将验证从服务器接收到的证书,数据流读取函数和OnClientState函数。

img

所以socket.Connect函数应该将客户端成功连接到服务器并进行第一次TCP握手:

img

接下来,我想研究执行到第287行时发生了什么:

img

这是一个SSL握手,但是发生的是服务器将X509证书传给了客户端,并且客户端认证了该证书,并且这是在RemoteCertificationValidationCallBack内部发生的。让我们查看一下它的源码:

img

正如您在else语句中看到的那样(在关闭调试模式的情况下编译会进入此语句),该函数会检查客户端证书和服务器证书是否匹配。但是,看看在调试模式下发生了什么——它只会返回true,并且发生在客户端……我们的客户端可以执行同样的操作以启动与服务器的有效SSL通信。让我们记住这一点并继续。接下来发生的事情有些棘手,在Client类的第290行中,将调用OnClientState,但是因为从Client类中调用了OnClientState,所以事件注册功能会生效,而事件处理功能将不会生效:

img

我们必须手动找到QuasarClient类,然后从那里进入到OnClientState 函数(我建议读者多读几次源码并充分理解其含义,这对于理解其工作原理有相当大的帮助)。但我们应该如何找到它?答案很简单,让我们回到Class0,即Program.cs

img

img

因此,Gclass27QuasarClient,为其重命名以便之后找到它更方便,接下来我们将通过双击该类并尝试手动查找OnClientState来访问该类:

img

让我们在第79行设置一个断点,并在之前的PayloadWriter WriteBytes函数内设置一个断点,该函数位于Stream1method02。在第79 行,创建Class18,然后将其传递给send函数中。Class18在Quasar源码中为ClientIdentification

img

这是消息构造函数,如果我们继续执行到 payload writer,可以看到该消息的内容:

img

img

我们仅是截取了整个消息。但是我注意到一些奇怪的事情——ClientIdentification类中只有14个成员,为什么这里却有28个成员?

img

另外,在第38行中,会将消息复制到数据流中并进行序列化,然后发送消息的长度,以及由序列化函数返回的原始字节。我尝试查看序列化后消息的内容。首先,让我们调试该程序到第40行,右键单击该变量,然后选择show memory window,以查看该数组变量的内容:

img

除了一些文本内容,其它的部分没有任何意义。

 

0x03 消息序列化与Google Protocol Buffer

为了节省读者时间,我简单解释下什么是序列化。序列化是一种压缩消息大小并提高处理效率的方法。序列化有诸多类型——其中一种是将消息压缩为json格式或XML格式,然后将其发送,接收方会按照预先约定好的协议对消息进行反序列化。可以在此处了解有关序列化的信息。

Quasar使用了由Google开发的Protobuf,Protobuf是一消息序列化程序。(希望您在看完上述视频后明白其原理)

这就是我们之前看到的这些ProtoMember

img

该C#类是基于ProtoContract定义的,使得编译器在生成这14个成员时会同时生成google protobuf消息。这就是为什么每个类成员都有一个原型成员。现在,protobuf协议会以不同的方式压缩每种类型(int32,int64,字符串),所以我们会在消息中看到很多奇怪的字节。到这里基本上已经可以回答我们之前设定的目标1-3。

很明显,下一步我们要做的是:

  1. 创建一个启动SSL连接的python脚本
  2. 验证客户端和服务器之间的连接
  3. 生成一个protobuf消息,并且可以与生成客户端发送的消息匹配。

关于第3点,我们可以假设消息的重要部分是TagSignatureEncryptionKey成员,根据我进行的测试,它们必须保持不变并与生成的客户端完全匹配。因为SignatureEncrpytionKey是包含X509证书信息的成员。

 

0x04 如何使用Python生成Protobuf消息

幸运的是,Google提供了一个使用Python生成protobuf消息的编程接口以及教程

我精心设计了一条消息来启动与服务器的SSL连接:

img

首先,消息内容很简单,它包含14个成员,这些成员与Quasar客户端发送的消息完全匹配。

img

该脚本会创建SSL socket,并通过始终在每个证书上返回1来来处理验证问题。尽管这是作弊,但我们是Hackers。

img

接下来让我们生成一条消息并将其序列化。请记住,必须在其后附加4个little endian字节——代表消息的总长度。

img

img

因此,我创建了一个简单的函数,它将返回该值并将其添加到序列化消息前缀中。现在我们可以生成消息,进行序列化并输出内容,看看它是否与Quasar客户端生成的内容匹配:

img

img

如您所见,生成消息有问题。Python生成的消息与客户端生成消息(除0x0A0xCF0x05之外)完全匹配(忽略前缀0xdf 0x02 0x00 0x00)。这些字节是什么?我不知道,所以我在stack overflow上提出了相关问题

经过些许研究,我确定该前缀只能是消息的长度。Python生成的消息其余部分与客户端生成的消息完全匹配。另外,如果我们编辑Quasar客户端生成的消息,则随着我们增加消息的长度,该前缀字段将会改变。让我们再次调试生成的Quasar客户端,并在发送消息之前于第36行中断Payload Writer函数,于任意一个字段中添加任意数量的字符”A”来改变消息内容:

img

img

如上所示,消息已更改,第二个字节从0xcf变为0xf5。0xF5与0xCF之间差38,这正是我添加的字符”A”的数量。若要准确计算出长度,我们必须了解对该字节序列进行编码的工作原理,幸运的是,Google并未对此保密(参考),另外这里也给出了答案。

我不会详细解释整个过程的工作原理,但是理解它对于我们目标的实现是有益的。Google Protobuf是开源项目,故我们无须从头开始重新实现。

img

img

在这两个代码块中,我将序列化一条消息并将其长度传递给我从protobuf项目中”借用”的函数。我对其进行了一些修改,使其始终位于最终结果的0x0A字节之前,最终结果表示字段编号和字段类型。由于该消息是我们生成的消息的前缀,因此其类型始终为2(length prefix)以及编号为1:

img

现在,我们使用python生成的消息与Quasar生成的消息完全匹配。

接下来转到连接部分:

img

以上代码块将连接到我们的服务器并与其进行握手。服务器将对其进行X509认证,然后我们的验证功能将会被触发:

img

它始终返回True。然后,我们将生成序列化的消息,并以消息的长度加序列化后消息的长度作为前缀:

img

首先在第97行,我将消息序列化。然后,我在消息中附加一个代表消息长度的前缀。最终,我计算了包括前缀在内的消息总长度,转换为小端序格式,并将其附加在序列化消息中。发送该消息到服务器,我们可以在Wireshark中查看此消息:

img

如果检查我们的Quasar服务端,可以看到:

img

我已经发送了一条自定义消息,没有问题!现在,我开始学习Intezers工具的工作原理,以便将我的Python脚本完善成为完整的Quasar RAT Tracker。

脚本完整代码可以在这里找到。

 

0x05 为Intezers Puppets创建Quasar插件

由于Intezer并未在该工具上投入过多心血,所以这一部分会比较棘手,但我会尽力解释该工具的工作原理,为您节省时间,因为我相信该工具对于跟踪恶意软件具有巨大价值。

Puppets是一组Python脚本,可以模拟出一个完整的OS环境,从而节省了研究人员构建蜜罐或虚拟机环境所需要的时间和资源。现在,我们不需要构建完整的OS与内核以及引导项,因为大多数恶意软件都希望与特定的用户模式应用程序和文件系统进行交互,而这些程序很容易模拟!Puppets附带了许多实用脚本与处理程序,这些脚本和处理程序将模拟基本功能以处理恶意软件——例如连接,注册,发送,接收等等。研究人员唯一要做的工作就是为特定恶意软件创建插件,其它工作都可以交由该工具来处理。我想引导读者阅读代码,并共同创建一个图表,以帮助开发人员了解该工具的工作原理。首次下载MoP以及从这里下载我的自定义插件quasar.pytargets.yamlutils.py

接下来,请按照此处的安装指南进行操作。

img

打开orchestrator.py,删除第一行#!/usr/bin/env python3.6,因为它会使该工具无法在Python 3.6以外的其他环境中运行。这是该工具的核心部分。来到第46行,您将看到一个command parser,在本例中,我们将使用第53行的选项–targets-config,该选项允许此工具连接到多个客户端,因此请打开targets.yaml并删除其中的相关hash。

img

我已经将其设置为与我的Quasar实例一起运行,但是由于尚未设置插件,因此您可以使其保持原样。该工具会通过指定目标IP,端口和插件来设置多个目标。

让我们回到核心脚本:

img

在第54行,对targets.yaml文件进行了解析,并提取其内容。然后调用函数connect_targets()

img

对于targets.yaml文件中的每个目标执行connect()函数:

img

connect()函数位于第22行,它使用_connect()回调函数启动线程,并将ipportplugin传递给该函数。_connect在第27行被调用。首先,它从plugin文件夹中导入一个插件,然后在第29行,使用插件构造函数连接到RAT,接下来调用插件的connectregisterloop函数。创建的所有插件扩展了puppet_rat.py中类的所有属性。因此,让我们查看下:

img

于第16行,我们可以看到该类的构造函数,就像之前在核心脚本中看到的那样,于第29行被触发。第16行的构造函数设置了客户端属性——ip,端口,伪进程ID,记录所有事件的记录器和用于表示套接字的conn变量。最重要的功能是:

  1. connect用来实现从客户端到服务器的连接
  2. register用来实现注册功能
  3. loop用来等待服务端发送命令

接下来我们对该脚本进行测试。首先我们从最简单的事情开始,让我们修改target.yaml文件以满足我们的需求,可以在上文找到示例。接下来,请下载quasar.pyclientidentity_pb2.py,将它们放到plugins文件夹中。

现在,我会逐行回顾我的插件代码:

img

首先,让我们来看下构造函数——它扩展了PuppetRat类,从而继承了其所有属性。我添加了message成员,以便可以随时对其进行编辑。用户必须自己设置的是TagEncryptionKeySignature,它们在客户端之间传输。另外,我添加了一个名为message的protobuf消息成员,在第70行可以看到。它创建了未初始化的Quasar消息。

img

同时我增加了4个自定义函数,这些函数使研究人员可以更改tagidkeysignature,并且可以生成和设置protobuf消息。函数__del__是一标准的Python函数,当对象被销毁时执行——它会关闭为Quasar创建的连接。

img

重新实现的connect函数——该函数与测试脚本中的非常相近。在我们阅读其源码 之前,请先从我的Github上下载utils.py文件,并将其放置在stage props文件夹中。在第112行,创建一个tcp套接字,然后将该套接字绑定到SSL套接字。create_ssl_sock是一个添加到utils脚本中的自定义函数。就像在我们的测试脚本,它所做的只是创建一个SSL套接字并将其绑定,以便我们可以验证Quasar证书。然后,连接到Quasar服务器并尝试进行握手。如果一切顺利,logger会显示一正确的消息。

img

接下来,我们来看loop函数,该函数功能非常简单,它所做的只是从服务器接收消息并显示它们。

img

我们继续看register函数,这是最后一个函数,它所做的只是模仿测试脚本。它会构造一个Quasar消息并发送到服务器。正如我之前说过的,我不会对此做过多介绍。

最后,让我们看看脚本是如何运作的!

开启您的虚拟机并启动Quasar服务器,然后于MoP中打开一个Shell并输入下列命令:

py orchestrator.py –targets-configtargets.yaml

img

让我们看看如果服务端命令客户端打开一消息框会发生什么:

img

img

如您所见,我们收到了一条需要反序列化的新消息。这将需要更多的努力,但根据您目前所学知识,应该不会太难。

(完)