0x00 相关文章
0x01 概述
最近,我们发现了一系列QBot恶意活动,本篇文章是系列分析文章的第二篇。在上一篇文章中,我们分析了捕获到的Word文档是如何下载原始的QBot Payload,如何开始破坏受害者的设备,并且使用了哪些复杂的技术保护其自身逃避检测。在本篇文章中,我们将研究恶意软件的核心模块是如何从受害者的设备收集数据、如何提取子模块、如何将注入模块注入到其他进程中,同时还会分析该恶意软件的其他恶意行为。
需要强调的是,QBot恶意软件在早期专注于记录与金融相关网站有关的信息。它能够监视受感染计算机的浏览活动,同时能窃取其他重要信息。
在分析的第一部分中,我们展示了QBot如何从资源部分解密名为“307”的核心模块。然后,将该核心模块部署在“explorer.exe”进程的内存中。最后,其入口点函数由ASM指令通过调用[ebp+var_10]来实现调用,如下图所示。
调用核心模块的入口点:
在第二部分文章中,我们将重点分析QBot的恶意行为、从受害者设备获取的数据,以及如何连接到其C2服务器。
0x02 QBot核心模块在Explorer中执行
核心模块启动一个线程,恶意软件的所有工作将从主线程函数开始。下图是DllEntryPoint()的伪代码。该入口点首先被调用,然后调用API CreateThread()以启动主线程函数。
核心模块的DllEntryPoint的伪代码(资源“307”):
在主线程函数中,恶意软件创建一个名为\\.\pipe\mavrihvusp的命名管道。在不同的设备上,这个名称也会有所不同,该名称实际上是根据设备环境和用户名生成的。然后,当有人连接到这个管道时,恶意软件将启动一个线程来监视和处理数据。
为了保持持久性,恶意软件将主进程添加到系统注册表的“Auto-Run”组中。为此,它创建了一个线程,该线程的回调函数调用API RegOpenKeyExW()。随后,RegOpenKeyExW()在注册表“HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run”下添加一个字符串值。这样一来,QBot就可以在受感染设备重新启动时自动运行。
下图展示了QBot的Auto-Run项。其中的名称使用了随机字符串,字符串值是QBot在主文件夹中的完整路径。
在系统注册表中添加了Auto-Run条目:
除了将自身添加到“Auto-Run”组之外,QBot还使用函数索引为08的函数,将自身安装到系统的任务计划中。它使用命令“C:\Windows\system32\schtasks.exe”,带有参数“/create /tn {task-name} /tr \"QBot-full-path\" /sc HOURLY /mo 5 /F”,以创建一个新条目。然后,每隔5个小时执行一次QBot进程,从而在受害者的设备上实现持久性。下图展示了刚刚添加的QBot任务。
任务计划中已添加的QBot任务:
QBot的配置数据块已加密,并会从主文件夹保存在资源“308”和mavrihvu.dat文件中,该文件已被加载到主线程函数的内存中。其中包含许多键值对,实现对QBot的控制,同时辅助QBot正常工作。例如,其中包含变种ID、QBot安装时间、上一个活动的C2服务器、受害者的登录名、受害者公网IP和许多功能开关标志。QBot在运行期间经常会访问配置数据块。
除了主线程外,QBot还创建了许多工作线程,其中的线程函数执行了一系列的函数,我将在下面进行详细解释。
主线程和其他工作线程:
上图展示了主线程和其他工作线程。当工作线程启动时,它通过调用API SetThreadPriority()将线程的优先级设置为“低于正常”,因此它们的优先级都被设置为32-1。
0x03 在本地网络中广泛传播QBot
QBot能够在受害者的局域网(LAN)内进行传播。该功能在工作线程中运行,以枚举受害者设备可以连接的网络计算机。
然后,它连接到一台网络计算机,并将QBot EXE文件复制到其共享文件夹中。目标文件名是随机生成的,如下图所示,展示的是一个刚刚复制的QBot,其文件名是azamzcifut.exe。
将QBot复制到一台网络计算机的共享文件夹中:
为此,它会调用许多API,例如WNetOpenEnumW()、WNetEnumResourceW()、NetShareEnum()、OpenSCManagerW()、WNetAddConnection2W()等等。
接下来,通过调用API CreateServiceW()和StartServiceW(),在远程计算机上启动Windows服务。下图是QBot即将调用CreatServiceW()时的屏幕截图以及参数。
在远程计算机上创建Windows服务:
在调用API StartServiceW()之后,在远程计算机上创建的Windows服务开始运行,并且QBot在远程计算机的后台开始执行。
最后,QBot远程删除创建的服务和复制的QBot文件,以释放其占用的空间。它会在所有其他网络计算机上重复此过程,以实现QBot的广泛传播。
0x04 将数据包发送到C2服务器
为建立与C2服务器的连接,会使用许多线程共同工作。线程函数从核心模块中的资源“311”解密C2服务器列表。下图展示了经过RC4解密后的部分C2服务器列表。
解密资源“311”中的C2服务器列表:
在资源“311”中,共包含150个IP和对应端口,它们是以“IP;0;端口”的形式记录,例如:“72.214.55.147;0;995”。QBot从列表中选择一台C2服务器,并尝试连接,循环此过程直到建立连接为止。然后,它将活动的C2服务器记录到配置块中,并将其更新为文件mavrihvu.dat。在配置块中,键“45”保存IP地址,“46”保存端口号,当其他线程函数将数据发送到C2服务器时,会使用上述记录的键。
QBot和C2服务器之间的通信数据采用JSON格式,并使用SSL协议加密。发出的请求URL采用“https[:]//IP:Port/t3”的形式,例如“hxxps://72[.]214[.]55[.]147:995/t3”。QBot使用POST方法将数据发送到C2服务器。发送的第一个数据包类似于“{"8":9,"1":17,"2":"rvhdls712290"}”。
其中,键“8”和“1”中的值,指示的是分组类型的常量。键“2”的值是“rvhdls712290”,这是根据受害者的用户名生成的,因此可以将其视为是受害者的ID。明文数据经过RC4加密,然后进行Base64编码。
QBot将数据发送到C2服务器:
从上图中可以看出,QBot将发送Base64编码后的第一个数据包到C2服务器,数据大小为0x50。当响应数据包到达时,QBot随后执行Base64解码和解密过程,以获取纯文本数据。来自C2服务器的第一个响应数据包如下所示:
“{"8":5,"16":1613570569,"39":"M2vqgbtrjwKrllf29InHGznAwG3SnStzKegq3LTb","38":1}”
随后,QBot将键“16”的值记录在一个全局变量中,其内容为UNIX时间戳,并解析键“38”的值。
在后续分析部分,我们将忽略加密或解密过程,仅关注纯文本数据。
QBot将更多数据发送到C2服务器:
QBot接下来将另一个数据包发送到C2服务器,如上图所示。该数据包中包含QBot的基本信息,例如变种ID“spx97”、变种版本“127”以及核心模块创建时间(在键“10”中)。其中,还包括受害者设备的信息,例如键“23”中包含Windows版本,键“24”中包含Windows名称,键“31”中包含处理器信息、域名、用户名、已安装的反病毒软件,键“57”中包含QBot的完整路径,键“33”中包含QBot当前注入的explorer.exe进程以及当前运行进程的完整列表。键“14”的值是一个随机字符串,用于验证响应数据包中的数据。
QBot还会将以下数据包发送到C2服务器,以查看是否需要升级。
“{"8":1,"1":17,"2":"rvhdls712290","3":"spx97","4":804,"5":127,"10":"1586971769","6":40396,"7":99854,"14":"IZpI9ARmSpWOyXuQFppgXpuz54xXiup0JVsYu"}”
服务器解析该数据包,如果发现存在新版本,则使用键“8”为6的数据包进行回复。新的QBot经过Base64编码,并且使用键“20”中的密钥进行加密。该数据包也有一个键,是“19”,其值为19,这是一个函数索引,将会调用指定函数以处理新的QBot。
下图是数据包“8”:6的部分结构图,可以参考下图,对照上面说明过的键值。
键“15”的值是经过Base64编码后的验证数据,与请求数据包中的键“14”有关。它是键“14”经过转换后的SHA1值。
包含新版本QBot的数据包:
在经过键“15”的验证之后,恶意软件继续调用索引函数(键“19”)对新版本的QBot进行Base64编码,然后将其保存到本地文件中。随后,它使用参数“/W”来运行新版本的QBot,以检查其是否在调试中运行,通过提取资源“307”来检查核心模块(即“307”资源)的有效性,并进一步提取资源“308”已确认是否运行良好。如果通过检查,它就会退出,退出代码为0x6F。当前的QBot将检查退出代码,如果退出代码不是0x6F,则丢弃新版本QBot文件,从C2服务器请求另一个文件。否则,将会以新版本QBot文件替换当前的QBot文件,然后执行该文件。
最后,在当前QBot退出其主机进程explorer.exe之前,它将发送键“8”的值为2的数据包,将状态通知C2服务器,如下所示。
"{"8":2,"1":17,"2":"rvhdls712290","3":"spx97","18":1,"40":0}”.
0x05 从受害者的设备中收集软件信息和证书
QBot会启动三个工作线程。在第一个线程中,QBot调用大量API,从受害者的设备中读取已安装的证书和私钥。这些相关的API从Crypt32.dll中导出,包括:CertEnumSystemStore()、CertOpenStore()、CertGetCertificateContextProperty()、CertDuplicateCertificateContext()、CertGetNameStringW()、CertGetCertificateContextProperty()、CertGetEnhancedKeyUsage()、CryptFindOIDInfo()、CryptAcquireCertificatePrivateKey()、CertCreateCertificateChainEngine()、CertAddCertificateContextToStore()、PFXExportCertStore()、CertEnumCertificatesInStore()等等。
在测试设备中安装的证书:
API CertEnumSystemStore()将调用一个回调函数,以重复获取所有证书,此过程逐一进行,如上图所示。它将获取的证书放入结构块中,进行RC4加密,并将其保存到主文件夹(在我的测试环境为%AppData%\Microsoft\Vhdktrbeex\)中,文件名为“mavrihvu32.dll”。
证书执行RC4加密:
上图的数据结构中展示了一个获得的证书,其中“t=c1”是数据类型,“time=”是获得的时间,“cert_time=”是证书名称,“cert_data=”是二进制数据,其中包含十六进制表示的证书,即将进行RC4加密。然后,会将一个加密的证书块附加到文件mavrihvu32.dll中。这个文件由许多不同的加密证书块组成。
在调用这些API函数以获取证书之前,它会先对DialogBoxParamW()、MessageBoxW()这类API进行挂钩。在调用它们后,会向受害者弹出警告或信息框。在调用这些证书API时,可能会调用它们。QBot使用多个挂钩函数来接收和阻止向受害者显示警告信息框。
第二个工作线程收集受害者设备上已安装软件的信息。它会导航到注册表“HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall”,其中包含所有已安装的软件信息。QBot会收集软件名称和版本信息,并将它们放入到包含有关受害者设备一些基本信息的结构中。下面是这个数据的示例:
t=i1 time=[09:59:06-15/05/2020] ext_ip=[{public IP}] dnsname=[?] hostname=[{computer name}] user=[] domain=[] is_admin=[YES] os=[6.1.1.7601.1.0.0100] qbot_version=[0324.127] install_time=[09.28.44-14/05/2020] exe=[C:\Windows\explorer.exe] prod_id=[NULL] iface_0=[10.0.2.15/10.0.2.15] UP] soft=[Microsoft Visual Studio 2015 XAML Visual Diagnostics;14.0.25431|Windows Espc Resource Package;14.0.23107|Google Chrome;81.0.4044.138|FileZilla Client 3.38.1;3.38.1|…]
我们注意到,这个信息中包含有关收集的证书、QBot安装时间、受害者的公网IP、操作系统版本、QBot版本、当前进程名称、本地IP信息以及一些收集的软件信息(“soft=[{software name};{version}|{others} …]”)。
最后,QBot将这个数据附加到mavrihvu32.dll文件中,在该文件中它已经具有获取的证书数据。然后,它使用zlib压缩mavrihvu32.dll的全部内容,并将其保存到名为cmavrihvu32.dll的新文件中。同时,它删除了旧文件mavrihvu32.dll,并运行了第三个线程(函数索引为0x15的传输线程),将收集到的数据发送到C2服务器。
QBot发送cmavrihvu32.dll的文件内容:
上图是在传输线程中,向C2服务器发送cmavrihvu32.dll的截图。数据包类型为“8”:7,总大小为0x824C9。这次,C2服务器的URL为“hxxps://173[.]3[.]132[.]17:995/t3”。键“36”的值为cmavrihvu32.dll的文件内容,该文件内容经过RC4加密和Base64编码。
在完成上述所有工作后,也会删除cmavrihvu32.dll文件。
0x06 通过核心模块加载子模块
在这里,事情变得更加有趣。我们观察到QBot现在可以从其他C2服务器获取其他数据,然后将其保存到本地文件中。在我的测试设备中,文件名称分别为pfllperc.myt和ebofekzl.utb。下图展示了这两个本地文件。
QBot主文件夹中包含两个收到的文件:
其中,文件名是随机生成的,但对于每个设备来说,文件名都是唯一的。它们是根据设备信息和用户名生成。在pfllperc.myt中包含两个Base64编码和RC4加密的DLL文件(子模块)。一旦QBot接收到数据包,就可以在索引函数中对其进行处理。尽管如此,处理这个子模块的更常规方式是QBot从核心模块中的本地文件加载它们。这也是我分析它们的方法。
该文件在核心模块的主线程函数中进行处理,在其中解密文件,并将子模块提取到内存中,然后将其加载到新创建的进程中,例如“explorer.exe”,就如同在“explorer.exe”中加载核心模块的方式一样。最后,调用子模块的入口点。
在“explorer.exe”中运行的子模块(注入器)包含两个二进制资源,它们是用于不同平台的相同模块。“RES_DATA_1”用于32位平台,“RES_DATA_2”用于64位平台。它们都是DLL文件(注入模块),将会注入到其他进程中运行。我的分析环境使用的是32位Windows 7操作系统,因此资源“RES_DATA_1”将会被提取到内存中。
接下来,子模块枚举正在运行的进程,并尝试将“RES_DATA_1”(注入模块)注入到其中,并尽可能快地执行。实际上,它没有权限注入到高级别的进程中。
为此,它调用了几个API,例如:CreateToolhelp32Snapshot()、Process32First()和Process32Next()。在捕获到一个进程后,将调用回调函数进行注入。下图展示了这一过程的伪代码。
枚举正在运行的进程(伪代码):
在回调函数中,将复制注入模块到目标进程的新内存空间中。它还读取文件ebofekzl.utb,并将其内容一同复制到目标进程中。解密文件ebofekzl.utb后,我们可以看到其中包含大量JavaScript代码片段,可以将其注入到受害者所访问的某些网页中。
然后,会将本地函数复制到目标进程中,并调用API CreateRemoteThread在远程线程中运行它。该函数是一个加载器,用于将复制的注入模块静默加载到可执行文件中,并在目标进程中执行。其参数是一个数据块,包含一些基本信息,例如复制的注入模块的基地址、ebofekzl.utb的文件内容、QBot进程的完整路径等等。
注入器模块执行这些步骤,以将注入模块注入到进程中,然后在进程中执行,以从受害者那里窃取更多有用信息。此外,注入器模块每秒(Sleep(1000))遍历一个进程。
0x07 在受害者设备上运行的注入模块
一旦注入模块在目标进程中执行,就会对关键API进行一组挂钩,这些关键的API包括:TranslateMessage()、GetClipboardData()、GetMessage()、HttpSendRequest()、InternetReadFile()、InternetReadFileEx()、InternetQueryDataAvailable()、HttpOpenRequest()、HttpSendRequestEx()和InternetWriteFile()。同样,还包含用于Firefox的nss3.dll和nspr4.dll中使用的一些API,例如:PR_OpenTCPSocket()、PR_Read()和PR_Write(),同时也包含Chrome中使用的几个API。
下图展示了某些已挂钩的API函数的本地挂钩函数定义。
挂钩函数定义的部分列表:
通过对GetClipboardData()和TranslateMessage()本地挂钩函数,QBot能够从系统剪贴板以及击键中获取受害者的数据。
为了分析这一过程是如何实现的,我打开了一个记事本应用程序,并将内容复制粘贴到其中,现在的数据就位于系统剪贴板中。本地挂钩函数实际上会早于记事本捕获到剪贴板数据,并且注入模块会将数据放入以下结构中:
t=kb time=[11:33:54-11/06/2020] p=[C:\Windows\system32\notepad.exe] t=[Untitled - Notepad] b=[my account: 11111111111
其中,“t=kb”是数据类型,“time”是记录时间,“p”是粘贴发生的进程的完整路径,“t”是应用程序的标题,“b”是我复制粘贴到记事本中的数据。键盘记录器记录的结构与剪贴板相同,不同之处在于,“b”的值被替换为受害者键入的内容。
接下来,数据经过RC4加密,并保存到本地文件mavrihvu32.dll中。这个文件用来存储受害者设备上收集的证书和已安装的软件,我在之前已经进行过解释。在这里,也发生相同的过程,注入模块继续记录数据,并将其保存到该文件。同时,核心模块中的传输线程继续读取该文件,并使用zlib将其压缩到文件cmavrihvu32.dll中。最后,使用类型为“8”:7的数据包发送到C2服务器。在这里,传输线程(函数索引0x15)还会不定时启动,以检查mavrihvu32.dll是否存在。
QBot针对比较流行的浏览器提供了三组API,支持Google Chrome、Microsoft IE、Microsoft Edge和Mozilla Firefox。
某网站的完整数据注入块:
上图展示了针对一个特定网站的注入数据块,网站的URL位于“set_url”项中。我们来重点关注在什么时间注入的数据,以及如何处理注入的数据。当受害者访问的网站与“set_url”中定义的网站匹配时,本地挂钩函数会获取响应HTML源代码。然后,它在HTML源代码中找到定义在“data_before”和“data_end”之间的< head >标签。注入模块会修改HTML源代码,并将位于“data_inject”和“data_end”之间的JavaScript代码注入标签“< head >”和“ < / head >”之间。最后,本地挂钩函数将新的HTML源代码回复给浏览器,当浏览器显示页面时,将会在其中执行注入的恶意JavaScript代码。
注入到HTML源代码中的恶意JavaScript:
上图展示了一个示例,其中用红色方框标出了HTML源代码中注入的JavaScript代码。我在Microsoft IE浏览器中访问在线购物网站时,发现了这一问题。注入操作在API InternetQueryDataAvailable()的本地挂钩函数中执行。如我们所见,受害者ID“rvhd1s712290”用于与恶意JavaScript代码内部的C2服务器进行通信的过程中帮助C2服务器识别受害者,在这里将其称为“fakebotid”。
对于Google Chrome和Mozilla Firefox浏览器,注入操作发生在API InternetQueryDataAvailable()之外的其他挂钩函数中,因为它们实现了自身的网络通信API函数,而没有使用Windows操作系统中默认提供的IE所使用的函数。
0x08 QBot Payload的更新
正如我在前面所提到的,自从我开始分析这个变种依赖,QBot Payload文件频繁发生更新。核心模块的最新版本(资源“307”)在2020年5月28日编译,该版本增加了检测模块是否在分析环境中运行的功能。例如,它支持检测37种不同的分析工具是否正在运行,其中包括Fiddler.exe、Ollydbg.exe、lordpe.exe、regshot.exe、Autoruns.exe、dsniff.exe、VBoxTray.exe、x32dbg.exe、Tcpview.exe等。一旦其中有一个进程正在运行,则将使用我们在上篇文章中提到过的“/C”参数退出程序,并使用1作为进程的退出代码。
0x09 总结
在本篇文章的分析过程中,我们对核心模块如何从受害者设备收集数据、如何提取子模块、如何将注入模块注入到其他进程的更多详细信息进行了分析。QBot可以通过向许多进程中注入恶意模块,来实现实时从受害者设备收集系统剪贴板数据和击键数据。最后,我还举了一个示例,来说明它是如何将恶意JavaScript注入到网页的。
根据我的分析,可以肯定地说,QBot的开发人员非常谨慎。QBot在执行恶意操作之前,会执行许多检查(例如:使用“/C”、“/W”参数)。恶意软件还会不定时删除其痕迹,以逃避检测。所有的中间数据也都会被加密,并在使用后立即删除。
本文翻译自:https://www.fortinet.com/blog/threat-research/deep-analysis-qbot-campaign如若转载,请注明原文地址: