首页
社区
课程
招聘
[翻译]老木马新玩法:Qbot最新攻击手法探究
2020-9-10 18:29 15271

[翻译]老木马新玩法:Qbot最新攻击手法探究

2020-9-10 18:29
15271

老木马新玩法:Qbot最新攻击手法探究

简介

臭名昭著的银行木马Qbot已经活跃了十多年。该恶意软件也被称为QakbotPinkslipbot,于2008年被发现,以收集浏览器数据、窃取受害者银行凭证和其他财务信息而出名。该木马代码具有高度结构化和分层清晰的特点,并且团伙不断开发扩展新功能。这些新的“手法”意味着Qbot尽管已经存在了很久,但仍具有持续性的威胁。该木马好比恶意软件界的瑞士军刀,主要功能包括:

  • 窃取受感染机器的信息,包括密码、电子邮件、信用卡详细信息等。
  • 在受感染的机器上安装其他恶意软件,如勒索软件。
  • 允许僵尸网络团伙连接到受害者机器(即使在受害者登录的状态下),通过受害者的机器和IP地址进行银行交易。
  • 劫持Outlook客户端的邮件会话,并尝试利用邮件会话感染其他用户的机器。

从今年3月到6月底,出现了一起利用Qbot的攻击活动。我们判断攻击活动在6月份停止,是由于QBot团伙需要时间进行下一步的恶意软件开发,我们没有想到在这么短的时间内攻击活动又重新启动。

 

7月底,作为当今最严重的网络威胁之一的Emotet木马团伙,全面恢复了攻击活动并发起了多次垃圾邮件攻击,影响了全球5%的组织,其中一些攻击活动在受害者的机器上安装了Qbot的更新版本。几天后,我们在一次最近的Emotet攻击活动中,发现了最新版本的Qbot样本,由此我们进一步找到了最新的C&C基础设施,以及在Emotet的攻击过程中分发恶意软件的全新技术。

 

另外我们观察到,Qbot恶意软件在8月上旬又开始在全球范围内广泛分发蔓延,感染新的机器。 Qbot的一项新手法特别恶心,一旦感染机器,木马就会激活一个特殊的“邮件收集模块”,该模块从受害者的Outlook客户端中提取所有邮件会话,并上传到硬编码地址的远程服务器上。这些被窃取的邮件数据将用于之后的恶意软件分发,由于垃圾邮件延续了已有的邮件会话,用户更容易被诱骗点击木马附件。Check Point的研究人员看到了具有针对性的劫持邮件会话的示例,其中涉及Covid-19、纳税提醒和工作招聘相关的主题。

 

基于我们的数据,大多数攻击的目标是欧美组织和机构,如下图所示:
攻击目标按国家分布

 

其中,被攻击最多的行业是政府、军工和制造业:
攻击目标按行业分布

 

在对这些新的QBot样本进行深度分析之后,我们将在下文分享以下主题和见解:

  • Qbot的感染过程–劫持电子邮件会话和利用VBS下载者。
  • Payload功能和版本分解。
  • C&C通信协议和模块获取。
  • 受害者机器如何变成僵尸网络代理,以及代理模块中包含的各种技术。

感染过程

Qbot感染过程图示如下,细节详见下文。
感染流程图

恶意邮件

上述流程始于向目标组织发送特制的邮件。该方法不如鱼叉式网络钓鱼技术复杂,但具备可信度高等其他特性,其中之一称为“劫持邮件会话”–获取已有的邮件会话并用恶意内容回复发件人。Qbot的邮件收集模块负责获取这些会话,我们将在后面具体描述。

 

@malware_traffic在其博客中上传了一些关于此方法的示例,如下面的3张图所示:
Covid-19相关的邮件会话

 

税务相关的邮件会话

 

招聘相关的邮件会话

 

每封邮件包含下载ZIP文件的URL,ZIP文件中包含恶意VBS文件。

 

通过我们对恶意软件分发的监控,我们看到数百个不同的下载ZIP文件的URL,并且大部分都是属于被黑的WordPress站点。

VBS感染

VBS感染对于该恶意软件而言是一种比较新的感染方式,从2020年4月开始使用。以前的攻击活动中用的是包含恶意宏的Word文档。

 

之前的宏经过简单的混淆和字符串编码,而VBS文件包含了一些更高级的手法:

 

文件大小–文件大小超过35MB,并用NULL字节填充。由于性能限制,体积大的文件通常会被各种沙箱选择性忽略。

 

延迟执行–脚本通过调用Sleep API延迟执行。这是避免沙箱检测的另一种方法。

 

混淆-脚本包含多种混淆方法,如图7中所示:
VBS混淆方法

 

加密-VBS文件从6个硬编码且加密的URL中,选择有效的URL下载Qbot payload。这些URL经过了3次自定义的异或加密,且每次加密密钥是动态生成的。我们编写了解密和提取URL的脚本,见附录B

 

为了支持恶意VBS文件的检测和威胁狩猎,我们编写了YARA规则,见附录A

 

与之前的感染方式相似,VBS文件下载并执行Qbot payload。这些URL经过了3次自定义的异或加密,且每次加密密钥是动态生成的。我们编写了解密和提取URL的脚本,见附录B

Qbot payload

版本分析

在我们分析的过程中,Qbot团伙频繁升级版本,我们跟踪并分析了每个版本。实际上,Qbot开发者在每个版本中都标明了版本标签,简化了我们的分析工作。

 

例如,我们看一下脱壳样本中的版本标签,如下图所示:
样本版本标签

 

我们推测初始payload版本是325/5,主payload版本是325/7(16进制)。

 

过去几个月中,我们追踪了不同版本的Qbot,并且分析出版本间的一些区别,总结如下:

主版本 次版本 Payload次版本 版本时间标签 描述
324 44 8 2020年1月22日 主版本324的第一个样本
324 353 53 2020年3月3日
324 375 65 2020年3月13日
324 379 70 2020年3月20日 增加命令35-支持hVNC模块。
324 383 74 2020年4月1日
324 385 75 2020年4月1日
324 388 79 2020年4月8日 增加命令10-终止指定名称的进程。
324 390 127 2020年4月10日
324 393 136 2020年4月29日 移除JS Updater资源,调整JS Update命令。
324 399 141 2020年5月7日 增加分析程序黑名单,增强反虚拟机能力。
324 401 142 2020年5月28日
325 5 7 2020年7月29日 增加新的反分析技术,在服务端增加反虚拟机机制。
325 7 13 2020年7月31日
325 8 14 2020年8月3日
325 35 42 2020年8月7日
325 37 43 2020年8月11日 此文发布前的最新版本。
 

上述日期是每个样本文件的编译日期。编译日期字段可以人为更改,但我们认为这些样本文件未被更改。

 

我们也跟踪了主payload的时间戳,看到编译时间与初始paylaod只相差几分钟。
比较payload的编译时间

解密机制

该恶意软件采用了多种加密方案,以对受害者和杀毒厂商隐藏其功能和数据。为了成功分析恶意软件及其组件,我们必须自动化解密所有变量。

 

下表展示了不同的解密和解码方法:

加密数据 算法 Key
网络数据 Base64 + RC4 KEY = SHA1(ENCRYPTED[0:16] + "jHxastDcds)oMc=jvh7wdUhxcsdt2")
Payload(资源307) RC4 + Custom Compression KEY = ENCRYPTED[0:20]
JavaScript Updater文件 RC4 + Custom Compression KEY = ENCRYPTED[0:20]
僵尸网络列表(资源311)和初始配置(资源308) RC4 KEY = ENCRYPTED[0:20]
“.dat”配置文件、网页注入文件和hook模块 RC4 KEY = SHA1(EXE_NAME)
“.dll”窃密文件 RC4 srand(CRC32(BOT_ID)),KEY = RANDOM_STRING_32

初始payload

Qbot的初始payload已被安全研究员分析得很透彻。最新版本的Qbot使用了几个经典的恶意软件相关组件和技术,以缩小其暴露面并加大分析难度:

 

Packer–EXE加壳。

 

随机目录名称–使用随机目录和文件名创建工作目录,以弱化特征。目录位置是%APPDATA%\ Microsoft,在下文一张图片中可以看到。

 

字符串加密–使用异或加密字符串(也适用于其他模块)。

 

动态导入表–基于加密字符串动态构建导入表(也适用于其他模块)。

 

反虚拟机和反调试技术:

  • 最新版本在服务器端检测VM相关特征。枚举受害者机器配置,并发送到C&C。服务器判断将模块“下发”至受害者机器是否安全。
  • 检测“ VMWare”相关端口是否开放。
  • 检测与VM和文件分析程序相关的进程。最新版本还添加了一长串分析程序黑名单:

    Fiddler.exe;samp1e.exe;sample.exe;runsample.exe;lordpe.exe;regshot.exe;Autoruns.exe;dsniff.exe;VBoxTray.exe;HashMyFiles.exe;ProcessHacker.exe;Procmon.exe;Procmon64.exe;netmon.exe;vmtoolsd.exe;vm3dservice.exe;VGAuthService.exe;pr0c3xp.exe;ProcessHacker.exe; CFF Explorer.exe;dumpcap.exe;Wireshark.exe;idaq.exe;idaq64.exe;TPAutoConnect.exe;ResourceHacker.exe;vmacthlp.exe;OLLYDBG.EXE;windbg.exe;bds-vision-agent-nai.exe;bds-vision-apis.exe; bds-vision-agent-app.exe;MultiAnalysis_v1.0.294.exe;x32dbg.exe;VBoxTray.exe;VBoxService.exe;Tcpview.exe
    
  • 检测VM相关的设备驱动,如下所示:
    VM设备驱动

  • 通过CPUID指令检测VM。

  • 通过强制抛出异常检测调试器是否运行。
  • 检测沙箱签名。

持久性-通过修改注册表和任务调度器实现持久性。

 

每当恶意软件确定可以在目标系统上安全运行时,它都会如上所述解密其资源“ 307”,将其注入到新创建的进程explorer.exe中,调用一个加载程序来加载DLL,并调用主payload的DllEntryPoint。

主payload

主payload有多个角色:

  • 创建和维护恶意软件配置项。
  • 创建和维护消息传递机制–管道、事件和自定义Windows消息。
  • 安装和管理新模块-新功能。
  • 创建并维护与C&C服务器的通信。
  • 通过自定义线程队列机制执行命令。

Payload还有其他一些内部模块,我们在本文中暂且不提,例如横向移动功能、证书收集、垃圾邮件机器人等。

 

该恶意软件使用几个嵌入式资源构建配置,这些嵌入式资源在运行时会被解压并解密。相关资源为:

  • “308”-初始配置数据。
  • “311”-150个肉鸡的IP和端口,用于构建通信隧道。

下图所示的工作目录,是Qbot功能的重要部分,也用于模块之间的同步。
Qbot工作目录

 

Qbot的配置文件(以.dat结尾)和窃取信息文件(以.dll结尾)是最关键的,这些文件会被所有模块访问和加载:
配置文件

 

窃取信息文件

 

至此,我们要想清楚的一个问题是,在哪里可以找到真正的“银行木马”逻辑。旧版本的Qbot包含多个嵌入式恶意模块,但最新版本相当“干净”。

 

为此,我们必须更深入地研究通信协议,并找到获取恶意模块的方法。

通信模块

资源“311”,如我们先前所述,包含150个肉鸡IP,用作与受害者机器通信。每个肉鸡都会将流量转发到真正的C&C服务器或二层代理,我们将在后面展示。此方法是隐藏C&C IP地址的有效方法。

 

接下来,所有的消息通过POST方法发送至https://<BOT_IP>:<BOT_PORT>/t3,并且用随机初始值加密。为方便大家理解,我们只展示解密后的网络数据。

 

C&C通信数据以JSON格式发送,其中每个值均由唯一的数字ID标识。在后面的示例消息中我们可以看到,最重要的JSON字段是消息代码,key是8。我们总结出的映射如下:

 

受害者机器 -> C&C
1–从C&C请求下一条命令。
2–确认C&C下发的命令。
4–枚举计算机配置和进程。
7–上报窃取信息。
8–打开端口消息。
9–Keep-alive消息。

 

C&C -> 受害者机器
5-服务端确认。
6-要执行的命令。

 

该程序包含两个并行的网络循环-Keep-alive和上报会话,以及命令执行会话。

Keep-alive和上报会话

此会话非常简单。程序在keep-alive消息与窃取信息上报消息之间切换。对于受害者机器发出的每条消息,都会收到服务器确认。这些消息如下所示:
Keep-alive消息

// Victim -> C&C
{
    "8": 9, // MSG code
    "1": 17, // Network protocol version
    "2": "powqdc619830" // Victim BOT ID
}

上报窃取信息消息
发送加密的.dll文件:

// Victim -> C&C
{
    "8": 7, // MSG code
    "1": 17, // Network protocol version
    "2": "powqdc619830", // Victim BOT ID
    "3": "spx145", // Bot group
    "6": 223,
    "7": 4763,
    "36": "617c...icR67==" // Base64 encoded and encrypted information
}

Keep-alive和上报响应

// C&C -> Victim
{
    "8": 5, // MSG code
    "16": 270544960, // Victim IP address
    "39": "mzJzbJU", // Random data
    "38": 1
}

命令执行会话

恶意软件会周期性地请求并且依据下表执行新命令,下表包含命令ID和对应的handler:
Qbot命令表

 

请求命令消息的结构如下:

{
    "8": 1, // MSG code
    "1": 17, // Network protocol version
    "2": "powqdc619830", // Victim BOT ID
    "3": "b", // Bot group
    "4": 804, // Payload major version
    "5": 141,   // Payload minor vesion
    "10": "1582872269", // Timestamp
    "6": 6210,
    "7": 6278,
    "14": "U3HphEKFiQcKFFe0LUVZNDO9vsJ9zdEf09"
}

响应如下:

{
    "8": 6, // MSG code
    "15": "...",
    "16": 270544960, // Victim IP address
    "18": 252,
    "19": 31, // command ID to execute
    "20": ["TVqQAAM...="], // command payload
    "39": "<RANDOM_STRING>"  // Random data
}

模块下发

在研究过程中,我们找到几个下载的模块,其中一些是新添加的,正如我们在版本跟踪和分解中看到的那样。

 

我们注意到,每当C&C服务器“注册”新的肉鸡ID时,在下一个命令请求中,肉鸡将收到接下来要下载和安装的模块:

 

EXE更新模块-更新新版本EXE或新的肉鸡列表。C&C周期性地给所有肉鸡推送更新。
邮件收集模块-通过MAPI32.dll API,提取受害者机器Outlook客户端上所有的邮件会话,并且上传到硬编码地址的远程服务器。这些窃取的邮件会话之后会用作恶意软件分发。
邮件收集模块

 

Hook模块-该模块将自身注入至所有正在运行的进程,并hook相关的API函数,举例如下:
Hook模块

 

网页注入模块-该文件为注入模块提供了网站列表和JavaScript代码,如果受害者访问这些网站中的任何一个,这些代码将被注入。

 

我们可以看到访问目标之一的结果-大通银行:
注入目标HTML源码示例

 

密码窃取模块-该模块较大,会下载Mimikatz,以窃取密码。
密码窃取模块

 

hVNC插件-允许通过远程VNC连接控制受害者机器。也就是说,即使用户登录到计算机,攻击者也可以在用户不知情的情况下进行银行交易。该模块与TrickBot的hVNC等类似模块的代码大量相似。
隐藏的VNC插件

 

JS Updater下载者-解密并编写JavaScript更新程序脚本。最近,该脚本是payload内部的加密资源。因为该脚本包含加密的硬编码URL,所以使攻击者更容易将最新的域名推送给受害者机器。

 

我们编写了一个Python脚本,用于轻松地从指定JavaScript脚本中提取URL,见附录C
JS Updater脚本示例

 

Cookie窃取模块-针对主流浏览器:IE、Edge、Chrome以及Firefox。
Cookie窃取模块

 

我们在流量分析程序中发现了这些模块:
Fiddler中看到的已下载模块

 

一旦受害者机器被感染和入侵,Qbot的横向移动功能,也对本地网络中的其他计算机也构成了潜在威胁。然后,该恶意软件会检查受害者机器是否也可能成为Qbot基础设施中的一部分。

受害者机器变为肉鸡代理

McAfee三年前发表了一篇很好的文章,其中涵盖了有关肉鸡代理模块的重要细节。为了理解完整的感染过程,我们认为关于该模块本身以及下发方式还有很多待研究的地方。

 

我们接下来分析Qbot如何将无辜的受害者机器转化为活跃的肉鸡代理,并成为C&C基础设施的一部分。恶意软件会执行以下操作:

  • 执行shell命令以允许开放防火墙中的外部连接策略。
  • 发送特制的UPnP命令开启端口转发。
  • 开启端口后,程序会发送一条消息到远程肉鸡并等待连接,来验证外部连接是否真正被允许。
    • URL-https://<BOT_IP>:<BOT_PORT>/bot_serv
    • Payload样本-cmd=1&msg=J3zeJrBLh2sGU4ql0EIr9MncSBCnK&ports=443,995,993,465,990,22,2222,2078,2083,2087,1194,8443,20,21,53,80,3389,6881,6882,6883,32100,32101,32102,32103,50000,50001,50002,50003,50010,61200,61201,61202
  • 远程肉鸡尝试使用指定的端口连接到受害者机器,如果受害者收到了预期的数据(msg变量),则表明外部连接成功。
  • 删除端口监听。

程序完成端口验证后,将发送消息代码8到C&C服务器:

{
    "8": 8, // MSG code
    "1": 17, // Network protocol version
    "2": "jnugfv895664", // Victim BOT ID
    "4": 3,
    "5": 111,
    "55": 270544960, // External IP of the potential bot
    "56": [443, 995, 993, 465, 990, 22, 2222, 2078, 2083, 2087, 1194, 8443, 20, 21, 53, 80, 3389, 6881, 6882, 6883, 32100, 32101, 32102, 32103, 50000, 50001, 50002, 50003, 50010, 61200, 61201, 61202] // Potential ports
}

程序在执行上述过程时,我们观察到在下一个命令执行请求中,我们收到了一个代理模块,其中包含要监听的端口:

{
    "8": 6, // MSG code
        ...
    "19": 25, // command ID
    "20": ["TVqQAAM...=", "prt=443", "n=jnugfv895664"], // command payload
        ...
}

将以上过程可视化,如下图所示,并通过流量分析程序进行观察:
下载代理模块的流程图

 

Fiddler中看到的代理模块下载

分析代理模块

代理模块由rundll32.exe加载,并复制到其工作文件夹– C:\ProgramData\FilesystemMonitor\。如果为代理模块具有SYSTEM权限,它将创建一个名为fsmon的新服务,否则将更新CurrentVersio\Run注册表的值。

 

该模块的大部分代码均来自以下开源代码库:

  • 用于HTTP请求的libcurl 7.47.1。
  • OpenSSL 1.0.2r 26 Feb 2019–用于证书创建和签名验证。
  • miniupnp–用于开启端口。

该模块还包含3个二层代理服务器的硬编码IP地址。

 

自McAfee 3年前分析模块并发布至今,该模块并未发生太大变化。仅有的变化包括:

  • 更换了服务名称、描述、工作文件夹、窗口名称和EXE文件名。例如,服务名称从hwmon更改为fsmon。
  • OpenSSL版本从1.0.2f升级到1.0.2r。
  • 更新了二层代理服务器。

代理模块中一个相当有趣的功能是其控制API。Qbot背后的团伙开发了控制代理的API,该API与恶意payload的更新机制不相关。该API也是比较独特,主要通过推而不是拉,来接收控制消息,后者可能会导致肉鸡被他人控制。

 

整个过程非常简单,可以用下图表示:
代理模块控制API的流程

 

需要利用攻击者的硬编码公钥来验证签名。因此,除非我们有私钥,否则很难破解协议。

结论

本文分析了该恶意软件的两个方面-感染受害者机器的攻击活动以及不断发展的复杂多层次的恶意软件。本文还涵盖了Qbot过去一年的版本更新历史、解密方法、通信示例、代理服务器控制API等多个主题。

 

如今,Qbot比以前更加危险-团伙积极分发恶意软件,感染了众多组织,并且设法使用了像Emotet这样的“第三方”基础设施来进一步传播威胁。多年来,Qbot背后的团伙似乎在不断发展其技术,Check Point Research希望本文中的信息将有助于全球安全研究人员应对并阻止Qbot的攻击活动。

 

Check Point SandBlast Agent可以从很早期开始防御此类攻击。

IOC

本次研究分析了大量Qbot和VBS样本,以下是自2020年6月22日以来的一些样本和模块。

哈希值

9001DF2C853B4BA118433DD83C17617E7AA368B1 – VBS下载者
449F2B10320115E98B182204A4376DDC669E1369 – Qbot样本SPX145
F85A63CB462B8FD60DA35807C63CD13226907901 – 邮件收集模块下载模块【已解密】
B4BC69FF502AECB4BBC2FB9A3DFC0CA8CF99BA9E – Javascript Updater下载模块【已解密】
1AAA14A50C3C3F65269265C30D8AA05AD8695B1B – Javascript Updater【已解密】
577522512506487C63A372BBDA77BE966C23CBD1 – Hook模块下载模块【已解密】
75107AEE398EED78532652B462B77AE6FB576198 – Cookie窃取模块 【已解密】
674685F3EC24C72458EDC11CF4F135E445B4185B – 密码窃取模块【已解密】
BECD8F2D6289B51981F07D5FF52916104D764DD5 – hVNC模块【已解密】
18E8971B2DE8EA3F8BB7E1462E414DA936425D4E – 代理模块下载模块【已解密】
4C96D2BCE0E12F8591999D4E00498BCDB8A116DE – 代理模块

域名和IP

ZIP文件URL
hxxps://factory-hot[.]com/bafmxby/CcdEhoQGHq.zip

 

VBS下载者URL
hxxp://kiesow-auto[.]de/foojapfsyds/5555555.png
hxxp://test[.]africanamericangolfersdigest[.]com/kkmthjsvf/5555555.png
hxxp://frankiptv[.]com/liehyidqtu/5555555.png
hxxp://klubnika-malina[.]by/utgritefmjq/5555555.png
hxxp://centr-toshiba[.]by/wogvynkombk/5555555.png
hxxp://marokeconstruction[.]com[.]au/hhmzmlqct/5555555.png

 

网页注入JS文件URL
hxxps://fortinet-cloud[.]com/wbj/br/content/chase/tom/ajax.js
hxxps://fortinet-cloud[.]com/wbj/br/content/key/tom/ajax.js
hxxps://fortinet-cloud[.]com/wbj/br/content/schwab/tom/schw.js
hxxps://fortinet-cloud[.]com/wbj/br/content/bbt/tom/bbt.js
hxxps://fortinet-cloud[.]com/wbj/att/js/AMAZON.js
hxxps://fortinet-cloud[.]com/wbj/crt/uadmin/inj_src/usa/amex2019/script.js
hxxps://fortinet-cloud[.]com/wbj/crt/uadmin/inj_src/usa/costco/costco.min.js
hxxps://fortinet-cloud[.]com/wbj/crt/uadmin/inj_src/usa/verizon/script.js
hxxps://fortinet-cloud[.]com/wbj/crt/uadmin/gate.php
hxxps://callunaconycatcher[.]com/bre/content/bmo/ins/bmo.js
hxxps://callunaconycatcher[.]com/bre/content/desjardins/ins/desjardins.js
hxxps://callunaconycatcher[.]com/bre/content/rbc/ins/rbc.js
hxxps://requirejscdn[.]com/*
hxxps://cersomab[.]com/lob.php

 

邮件收集远程服务器
hxxps://82.118.22[.]125/bgate

 

Mimikatz下载URL
hxxps://onedrive.live[.]com/download.aspx?cid=CE32720D26AED2D5&authKey=%21AHHhrhk9od5OCBU&resid=CE32720D26AED2D5%21111&ithint=%2Eps1

 

二级代理服务器
46.228.199.235:443
93.88.75.176:443
207.244.112.112:443

 

JavaScript Updater URL
hxxp://backup.justthebooks[.]com/datacollectionservice.php3
hxxp://asn.crs.com[.]pa/datacollectionservice.php3
hxxp://chs.zarifbarbari[.]com/datacollectionservice.php3

 

肉鸡列表
79.115.207.120:443
156.213.80.140:443
189.160.203.110:443
71.114.39.220:443
189.236.166.167:443
193.248.44.2:2222
206.51.202.106:50003
24.152.219.253:995
2.50.47.97:2222
108.49.221.180:443
207.246.75.201:443
80.240.26.178:443
199.247.16.80:443
207.255.161.8:2222
69.92.54.95:995
199.247.22.145:443
2.50.171.142:443
24.110.14.40:3389
79.101.130.104:995
94.52.160.116:443
172.243.155.62:443
188.192.75.8:443
175.111.128.234:443
74.129.18.56:443
36.77.151.211:443
203.45.104.33:443
118.160.162.77:443
86.126.97.183:2222
185.246.9.69:995
140.82.21.191:443
66.208.105.6:443
206.183.190.53:993
5.12.111.213:443
72.177.157.217:995
98.210.41.34:443
98.242.36.86:443
199.116.241.147:443
49.144.81.46:8443
75.110.250.89:995
219.76.148.142:443
70.174.3.241:443
71.205.158.156:443
78.96.192.26:443
108.190.151.108:2222
81.133.234.36:2222
12.5.37.3:995
210.61.141.92:443
173.70.165.101:995
5.13.84.186:995
68.46.142.48:443
188.27.6.170:443
188.173.70.18:443
86.124.13.101:443
5.13.74.26:443
68.190.152.98:443
96.56.237.174:990
175.143.12.8:443
79.113.224.85:443
2.51.240.61:995
95.76.27.89:443
5.12.243.211:443
24.183.39.93:443
86.124.228.254:443
5.193.178.241:2078
2.88.186.229:443
108.227.161.27:995
188.192.75.8:995
98.32.60.217:443
176.223.35.19:2222
24.42.14.241:443
70.95.118.217:443
68.225.56.31:443
191.84.11.112:443
72.204.242.138:50001
173.22.120.11:2222
64.121.114.87:443
68.60.221.169:465
92.17.167.87:2222
47.138.200.85:443
71.187.7.239:443
151.205.102.42:443
72.179.13.59:443
172.113.74.96:443
5.193.61.212:2222
47.28.135.155:443
188.26.243.186:443
41.228.206.99:443
117.218.208.239:443
203.122.7.82:443
39.36.61.58:995
49.207.105.25:443
59.124.10.133:443
89.44.196.211:443
79.117.129.171:21
24.110.96.149:443
184.90.139.176:2222
82.79.67.68:443
86.153.98.35:2222
101.108.4.251:443
209.182.122.217:443
89.32.220.79:443
104.50.141.139:995
85.204.189.105:443
94.10.81.239:443
211.24.72.253:443
110.142.205.182:443
86.124.105.88:443
72.90.243.117:0
41.225.231.43:443
87.65.204.240:995
62.121.123.57:443
47.153.115.154:990
66.30.92.147:443
49.191.4.245:443
47.180.66.10:443
97.93.211.17:443
65.100.247.6:2083
65.131.43.76:995
45.45.51.182:2222
98.219.77.197:443
166.62.180.194:2078
72.16.212.108:995
73.217.4.42:443
76.187.8.160:443
67.182.188.217:443
37.182.238.170:2222
117.216.227.70:443
74.222.204.82:443
89.137.77.237:443
82.77.169.118:2222
188.27.36.190:443
108.39.93.45:443
72.181.9.163:443
58.233.220.182:443
73.137.187.150:443
97.127.144.203:2222
103.76.160.110:443
37.156.243.67:995
67.246.16.250:995
182.185.7.220:995
82.81.172.21:443
117.199.6.105:443
216.163.4.132:443
199.102.55.87:53
96.244.45.155:443
122.147.204.4:443
89.45.107.209:443
35.142.12.163:2222
73.94.229.115:443
165.0.3.95:995

其他IOC

代理服务名称
fsmon

 

代理文件路径
C:\ProgramData\FilesystemMonitor\fsmonitor.dll
C:\ProgramData\FilesystemMonitor\fsmonitor.ini

 

代理执行命令
C:\Windows\SysWOW64\rundll32.exe "C:\ProgramData\FilesystemMonitor\fsmonitor.dll",FsMonServerMainNT
C:\Windows\SysWOW64\rundll32.exe "C:\ProgramData\FilesystemMonitor\fsmonitor.dll",#1

 

代理RSA公钥
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEA4zJC+AO8v7U9WGOdqeqMn9CPrdgoz//B+f/xxb4UnSNM1NJ1RwTG
N2jf6JRRD2gZz9735DU4I9FlIDEiRDdNn4OxX76L5eKe2GF4/etZ23DfuomMNXVw
qwYcO8A7zjzG0+ybQH35eNoYJMJDwPOBWb/nHBlPNWXoyv7u8EzScENMBpfKWuMW
UgmV08dulHPPyi9fjSsY3DLo5zNE6A8UEk2e2R2UkmiDbENOARgsfwHosyqEcBGc
Pk/+EismU1rsabaQV/sHw1zQQ9vAH+27d/T13hCuIgq1B3vRYFIrPkJYAdaxOwto
AHn0rjeAN4tEIdDQ10RCriEmnNEBfxA9BwIDAQAB
-----END RSA PUBLIC KEY-----

附录

附录A:恶意VBS检测YARA规则

rule qbot_vbs
{
    meta:
        description = "Catches QBot VBS files"
        author = "Alex Ilgayev"
        date = "2020-06-07"
    strings:
        $s3 = "ms.Send"
        $s4 = "for i=1 to 6"
        $s5 = "if ms.readyState = 4 Then"
        $s6 = "if len(ms.responseBody) <> 0 then"
        $s7 = /if left\(ms.responseText, \w*?\) = \"MZ\" then/
    condition:
        filesize > 20MB and $s3 and $s4 and $s5 and $s6 and $s7
}

附录B:VBS URL提取脚本

"""Qbot VBS URL extractor and de-obfuscator.
This script is for research purposes, and far from production ready (missing exception handling and more).
"""
import re
import os
import sys
def remove_additions(lines):
    """Removes stub calculations. 
    Example:
    IZLmoJg = 277 + 15 + 23 + 468 - 345 - 18 - 471 - 15 + 617
    Will be replaced with:
    IZLmoJg = 551
    Args:
        lines (list): List of lines.
    Returns:
        list: List of modified lines.
    """
    pattern = r'(([0-9]{1,15} [\+,\-] )+[0-9]{1,15})'
    new_file  = []
    for line in lines:
        line = line.strip()
        res = re.search(pattern, line)
        if res:
            new_line = re.sub(pattern, lambda x:str(eval(x.group(1))), line)
        else:
            new_line = line
        new_file.append(new_line)
    return new_file
def remove_chr(lines):
    """Replaces "chr(*)" with their respective characters.
    Args:
        lines (list): List of lines.
    Returns:
        list: List of modified lines.
    """
    pattern = r'[c,C]hr\((\d?\d?\d?)\)'
    new_file  = []
    for line in lines:
        line = line.strip()
        res = re.search(pattern, line)
        if res:
            new_line = re.sub(pattern, lambda match: '\"' + str(chr(int(match.group(1)))) + '\"', line)
        else:
            new_line = line
        new_file.append(new_line)
    return new_file
def remove_replace(lines):
    """Replaces "replace(*)" with its respective string.
    Args:
        lines (list): List of lines.
    Returns:
        list: List of modified lines.
    """
    pattern = r'replace\(\"(.*)\"\, \"(.*)\"\, \"(.*)\"\)'
    new_file  = []
    for line in lines:
        line = line.strip()
        res = re.search(pattern, line)
        if res:
            new_line = re.sub(pattern, 
                lambda match: '\"' + match.group(1).replace(match.group(2), match.group(3)) + '\"'
            ,line)
        else:
            new_line = line
        new_file.append(new_line)
    return new_file
def remove_concat(lines):
    """Replaces the VB concatenation sign "&" with the result string.
    Args:
        lines (list): List of lines.
    Returns:
        list: List of modified lines.
    """
    pattern = r'\"(.*)\"\&\"(.*)\"'
    new_file  = []
    for line in lines:
        line = line.strip()
        res = re.search(pattern, line)
        if res:
            new_line = re.sub(pattern, 
                lambda match: '\"' + match.group(1) + match.group(2) + '\"'
            ,line)
        else:
            new_line = line
        new_file.append(new_line)
    return new_file
def remove_trailing_zeros(lines):
    """Removes all trailing NULL bytes from a file.
    Args:
        lines (list): List of lines.
    Returns:
        list: List of modified lines.
    """
    new_file  = []
    for line in lines:
        if len(line) > 0 and line[0] == '\x00':
            continue
        new_file.append(line)
    return new_file
def deobfuscate_file(fpath_in, fpath_out):
    """Converts Qbot VBS script into it's deobfuscated form.
    Main changes are:
    - removing stub calculations
    - converting "chr(*)" into their respective characters.
    - converting "replace(*)" into its respective string.
    - converting VB concatenations ("&") into the final string.
    - removing trailing NULL bytes.
    Args:
        fpath_in (str): Input VBS file path.
        fpath_out (str): Output file path.
    """
    try:
        with open(fpath_in, 'r') as f_in:
            in_data = f_in.read()
    except:
        return None
    lines = in_data.split('\n')
    lines = remove_additions(lines)
    lines = remove_chr(lines)
    lines = remove_replace(lines)
    for _ in range(100):
        lines = remove_concat(lines)
    lines = remove_trailing_zeros(lines)
    new_file_joined = '\n'.join(lines)
    try:
        with open(fpath_out, 'w') as f_out:
            f_out.write(new_file_joined)
    except:
        return None
def decrypt_data(enc_str, keys):
    """Decrypts long blob of text data.
    decryption method is looking for patterns of decimal numbers,
    and xor them with the key.
    do that with three different keys.
    Arguments:
        enc_str {string} -- encrypted data
        key1 {int} -- first key
        key2 {int} -- second key
        key3 {int} -- third key
    """
    def _decrypt_data_inner(str_param, key_param):
        """Helper method. actual decryption.
        """
        numbers = ""
        ret_decrypted = ""
        f = True
        for i in range(len(str_param)):
            if  '0' <= str_param[i] <= '9':
                numbers = numbers + str_param[i]
                f = True
            else:
                if f:
                    try:
                        enc_ch = int(numbers)
                    except:
                        break
                    dec_ch = enc_ch ^ key_param
                    ret_decrypted = ret_decrypted + chr(dec_ch)
                numbers = ""
                f = False
        return ret_decrypted
    key1 = keys[0]
    key2 = keys[1]
    key3 = keys[2]
    enc_str = _decrypt_data_inner(enc_str, key1)
    enc_str = _decrypt_data_inner(enc_str, key2)
    return _decrypt_data_inner(enc_str, key3)
class qbot_vbs(object):
    """Encapsulates qbot VBS artifacts.
    These artifacts are used for extraction.
    """
    num_urls = 0
    enc_str = None
    key_str = None
    seed = 0
    key_idxs = [None, None, None]
    def __init__(self, data):
        self.data = data
        self.lines = data.split('\n')
    def _extract_number_urls(self):
        """Extracts number of encrypted urls.
        """
        # sample:
        # number of urls: for i=1 to 6
        pattern = r'[F,f]or i=1 to (\d+)'
        res = re.search(pattern, '\n'.join(self.lines))
        self.num_urls = int(res.group(1))
    def _extract_enc_str(self):
        """Extracts the string which has the encrypted data.
        should be the biggest line in the script.
        """
        max_len = 0
        max_idx = -1
        for i, line in enumerate(self.lines):
            if len(line) > max_len and line[0] != '\x00':
                max_len = len(line)
                max_idx = i
        # removing variable name.
        res = re.search(r'^\w+ = \"(.*)\"$', self.lines[max_idx])
        self.enc_str = res.group(1)
    def _extract_key_str(self):
        """Extracts the string which the key is based upon.
        should be called after `extract_enc_str()`.
        should be the second biggest line after encrypted string.
        """
        second_max_len = 0
        second_max_idx = -1
        max_len = len(self.enc_str)
        for i, line in enumerate(self.lines):
            if len(line) > second_max_len and len(line) < max_len and line[0] != '\x00':
                second_max_len = len(line)
                second_max_idx = i
        # removing variable name.
        res = re.search(r'^\w+ = \"(.*)\"$', self.lines[second_max_idx])
        self.key_str = res.group(1)
    def _extract_seed_and_key_indexes(self):
        """Helper function for key extraction.
        """
        def _find_variables(var1_name, var2_name):
            """Finds variables values for two vars.
            for example:
            DgZlWOk = 8
            jRryhge = 4
            """
            pattern1 = fr'^{var1_name} = (\d+)$'
            pattern2 = fr'^{var2_name} = (\d+)$'
            var1_value = None
            var2_value = None
            for line in self.lines:
                res = re.search(pattern1, line)
                if res:
                    var1_value = res.group(1)
                res = re.search(pattern2, line)
                if res:
                    var2_value = res.group(1)
            return var1_value, var2_value
        def _extract_key_indexes(lines):
            # we have 6 'Mid' encounters:
            # yaGlYs = Mid(xHAaMv, 10, 2)
            # RLquKjB = Asc(Mid(HIbAriX, seSclZ, 1))
            # three times.
            # the second is not interesting for us.
            text = '\n'.join(lines)
            res = re.findall(r'Mid\(\w+\, (\w+)\, \w+\)', text)
            # the key creation order is reversed to their using. (first key3 is set and so on)
            self.key_idxs[0] = int(res[4])
            self.key_idxs[1] = int(res[2])
            self.key_idxs[2] = int(res[0])
        # sample line:
        # For uLRYNs = 0 To 2387414 Step 1
        pattern1 = r' (238\d\d\d\d) '
        # sample line:
        # YLTCm = YLTCm + DgZlWOk - jRryhge
        pattern2 = r'^(\w+) = (\1) \+ (\w+) - (\w+)$'
        for i, line in enumerate(self.lines):
            res = re.search(pattern1, line)
            if res:
                num = int(res.group(1))
                _extract_key_indexes(self.lines[i+1:])
                for inner_line in self.lines[i+1:i+10]:
                    res = re.search(pattern2, inner_line)
                    if res:
                        first_param = res.group(3)
                        second_param = res.group(4)
                        first_value, second_value = _find_variables(first_param, second_param)
                        num += 1
                        num *= (int(first_value) - int(second_value))
                        self.seed = num
                        return
    def extract_keys(self):
        """Main extraction method.
        Extracts keys for the URL decryption.
        Returns:
            list: List of 3 keys.
        """
        vbs._extract_number_urls()
        vbs._extract_enc_str()
        vbs._extract_key_str()
        vbs._extract_seed_and_key_indexes()
        seed = self.seed * 999999
        str_seed = str(seed)
        idx = int(str_seed[self.key_idxs[2] - 1:self.key_idxs[2] + 1])
        key3 = ord(self.key_str[idx - 1])
        idx = int(str_seed[self.key_idxs[1] - 1:self.key_idxs[1] + 1])
        key2 = ord(self.key_str[idx - 1])
        idx = int(str_seed[self.key_idxs[0] - 1:self.key_idxs[0] + 1])
        key1 = ord(self.key_str[idx - 1])
        return key1, key2, key3
if __name__ == "__main__":
    if len(sys.argv) != 2:
        print(f"Usage: python {os.path.basename(__file__)} <fpath_in>")
        exit(1)
    fname_tmp = 'tmp'
    deobfuscate_file(sys.argv[1], fname_tmp)
    if not os.path.exists(fname_tmp):
        print("Failed de-obfuscation script.")
        exit(0)
    with open(fname_tmp) as f:
        data = f.read()
    os.remove(fname_tmp)
    vbs = qbot_vbs(data)
    keys = vbs.extract_keys()
    dec = decrypt_data(vbs.enc_str, keys).strip('\ufeff').split('_______')
    for i in range(vbs.num_urls):
        url = dec[i]
        url = url.split('?')[0].strip()
        print(url)

附录C:JavaScript Updater URL提取脚本

import re
import os
def extract_urls_from_js_updater(js_data):
    """Extracts update URLs out of given Qbot Javascript updater.
    Args:
        js_data (str or bytes): Javascript code content.
    Returns:
        list: Returns list of extracted URLs or None if falied.
    """
    try:
        if isinstance(js_data, bytes):
            js_data = js_data.decode('ascii')
    except:
        return None
    arrays = []
    # var WcrApaqyDNEBJYsFkiXPVzHCeKGmnxd = [30,209...19];
    # encrypted urls
    pattern = re.compile(r"^\s*var [a-zA-Z0-9]+\s?=\s?\[(([0-9]+,)+)([0-9]+)\];$")
    for line in js_data.splitlines():
        match = pattern.match(line)
        if match:
            array = match.group(1) + match.group(3)
            arrays.append(array)
    if not len(arrays) == 2:
        return None
    suffix = 'datacollectionservice.php3'
    # encrypted text
    base_values = [int(c) for c in arrays[0].split(",")]
    # key
    xor_values = [int(c) for c in arrays[1].split(",")]
    res = ""
    for i in range(len(base_values)):
        res += chr(base_values[i] ^ xor_values[i % len(xor_values)])
    servers = res.split(";")
    urls = ['https://' + server + '/' + suffix for server in servers]

    return urls

原文链接:https://research.checkpoint.com/2020/exploring-qbots-latest-attack-methods/

 

翻译:看雪翻译小组 SpearMint


[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

收藏
点赞2
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回