深入分析GootLoader木马化jQuery脚本:恶意代码的层层剥茧

4 阅读14分钟

木马化jQuery脚本分析:GootLoader深度剖析

更新 24/10/2022: 自本报告发布三个月以来,我们注意到两处变化:

  1. 代码已改用注册表键 HKEY_CURRENT_USER\SOFTWARE\Microsoft\Personalization 而非 HKEY_CURRENT_USER\SOFTWARE\Microsoft\Phone (样本 SHA256: ed2f654b5c5e8c05c27457876f3855e51d89c5f946c8aefecca7f110a6276a6e)
  2. 当有效载荷为Cobalt Strike时,信标配置现在包含C2主机名,例如 r1dark[.]ssndob[.]cn[.]comr2dark[.]ssndob[.]cn[.]com(我们分析的所有早期CS样本均使用IPv4地址)。

在这篇博客文章中,我们将深入分析GootLoader。该恶意软件已知可投放多种类型的有效载荷,例如Kronos木马、REvil、IcedID、GootKit载荷,以及本文案例中的Cobalt Strike。

我们的分析将使用初始恶意软件样本本身,以及从其执行系统收集的一些恶意软件痕迹。恶意JavaScript代码隐藏在一个jQuery JavaScript库中,包含约287KB的数据,近11000行代码。我们将对这个恶意的JavaScript文件进行逐步分析。

TLDR 我们用于分析此GootLoader脚本的技术:

  • 阶段 1: 利用合法的jQuery JavaScript脚本来隐藏木马下载器:
    • 在原始jQuery脚本中添加了多个新函数。分析这些函数会发现一个混淆的数据块以及用于解混淆的函数。
    • 用于解混淆该数据块(木马下载器)的算法:
      • 对于混淆数据中的每个字符,判断其位于偶数还是奇数位置(索引从0开始)
      • 如果是奇数,将其放在累加器字符串的前面
      • 如果是偶数,将其放在累加器字符串的后面
    • 结果是更多的JavaScript代码。
    • 尝试从生成的JavaScript代码中列出的三个URL之一下载(混淆的)有效载荷。
    • 由于有效载荷不再被提供,此步骤失败。我们转而进行有根据的猜测,使用VirusTotal的“内容”过滤器搜索混淆的“createobject”字符串,并得到了一些匹配项。
  • 阶段 2: 解码混淆的有效载荷
    • 取2位数字
    • 将这2位十进制数字转换为整数
    • 加30
    • 转换为ASCII字符
    • 重复直到末尾
    • 结果是JavaScript和PowerShell的组合。
    • 提取JavaScript、PowerShell加载器、PowerShell持久化模块,并进行分析以提取嵌入在有效载荷中的混淆.NET加载器。
  • 阶段 3: 分析.NET加载器以解混淆Cobalt Strike DLL。
  • 阶段 4: 从Cobalt Strike DLL中提取配置。

阶段 1 – sample_supplier_quality_agreement 33187.js

  • 文件名: sample_supplier_quality_agreement 33187.js
  • MD5: dbe5d97fcc40e4117a73ae11d7f783bf
  • SHA256: 6a772bd3b54198973ad79bb364d90159c6f361852febe95e7cd45b53a51c00cb
  • 文件大小: 287 KB

为了在这个JavaScript文件中找到木马下载器,执行了以下grep命令:

grep -P "^[a-zA-Z0-9]+\("

此grep命令将找到在函数定义外部(即没有缩进/前导空格)调用JavaScript函数的入口点。这是许多开发人员遵循的惯例,但并不能保证能快速找到入口点。在本例中,在像jQuery这样成熟的JavaScript库中,函数调用 hundred17(3565) 显得格格不入。

在追踪不同的调用时,存在大量混淆代码,观察到了函数 "color1"。另一种弄清楚脚本中被修改部分的方法可能是将其与脚本的合法版本进行比较,并进行"差异对比"以查看不同之处。基于恶意脚本开头显示的版本,从jQuery网站本身获取了合法脚本。

在对整个jQuery文件进行完整差异对比之前,我们首先使用以下grep命令提取了函数名:

grep 'function [0-9a-zA-Z]'

这分别对合法的jQuery文件和恶意文件执行,使我们能够快速查看恶意软件作者添加了哪些额外的函数。比较这两个文件立即显示了一些有趣的函数名和参数:

在不只关注函数名的情况下对两个文件进行差异对比,我们得到了恶意软件作者添加的所有代码。

Color1 是添加的函数之一,包含了大部分数据,似乎是混淆的,这可能表明这是最相关的函数。

在此函数中,变量 has6 值得关注,因为它将所有先前定义的变量组合成一个:

进一步追踪函数最终会找到负责解混淆此数据的主要函数:modern00gun6

解混淆算法非常简单: 对于混淆字符串中的每个字符(从第一个字符开始),将其添加到一个累加器字符串中(初始为空)。如果字符位于奇数位置(索引从0开始),则将其放在累加器前面,否则放在后面。当所有字符处理完毕后,累加器将包含解混淆后的字符串。

用于实现该算法的脚本类似于以下用Python编写的代码:

在解混淆后的脚本中观察到的 CreateObject,用于创建一个脚本执行对象(WScript.Shell),然后传递要执行的脚本(第一个脚本)。这个脚本(以白色高亮显示)也使用了JavaScript混淆以及第一个脚本中观察到的相同脚本混淆技术。

解混淆该脚本会产生第二个JavaScript脚本。接下来是第二个脚本,包含解混淆后的字符串和代码,并进行了“美化打印”:

该脚本是一个下载器脚本,尝试从3个域发起下载。

  • www[.]labbunnies[.]eu
  • www[.]lenovob2bportal[.]com
  • www[.]lakelandartassociation[.]org

HTTPS请求包含一个随机组件,并且可以传递少量信息:如果请求以“4173581”结尾,则请求源自一台已加入域的Windows机器(脚本通过检查环境变量 %USERDNSDOMAIN% 是否存在来确定这一点)。

以下是一个URL示例:hxxps://www[.]labbunnies[.]eu/test[.]php?cmqqvfpugxfsfhz=71941221366466524173581

如果下载失败(即HTTP状态码不是200),脚本会休眠12秒(精确地说是12345毫秒),然后尝试下一个域。当下载成功时,下一阶段被解码并作为(另一个)JavaScript脚本执行。尝试了不同的方法来下载有效载荷(使用不同的URL),但所有方法均未成功。大多数情况下无法与服务器建立TCP/TLS连接。在收到HTTP回复的情况下,响应体为空(内容长度0)。尽管我们无法从恶意服务器下载有效载荷,但我们能够从VirusTotal检索到它。

阶段 2 – 有效载荷

我们找到了一个我们非常有信心认为是原始阶段2的有效载荷。有高度把握确定这确实是发送给受感染机器的有效载荷,关于如何确定这一点的更多信息可以在以下部分找到。该有效载荷最初从德国上传,可以在此处找到:www.virustotal.com/gui/file/f8…

  • MD5: ae8e4c816e004263d4b1211297f8ba67
  • SHA-256: f8857afd249818613161b3642f22c77712cc29f30a6993ab68351af05ae14c0f
  • 文件大小: 1012.97 KB

有效载荷由数字组成。要解码它,取2位数字,加30,转换为ASCII字符,并重复此过程直到有效载荷结束。这个解混淆算法是从上一个脚本的最后一步推导出来的:

作为示例,我们将详细解码字符串的前几个字符:88678402

  • 88 --> 88+30 = 118
  • 67 --> 67 + 30 = 97
  • 84 --> 84 + 30 = 114
  • 02 --> 02+30 = 32

结果是:"var ",这表示JavaScript中变量的声明。这意味着我们还有另一个JavaScript脚本需要分析。为了更快地解码整个字符串,我们可以使用一个小的Python脚本来自动化这个过程:

解码字符串的前半部分:

解码字符串的后半部分:

使用以下CyberChef配方也可以达到同样的效果,由于数据量大,需要一些时间,但我们将其视为使用CyberChef完成同样任务的一个小挑战。

#recipe=Regular_expression('User%20defined','..',true,true,false,false,false,false,'List%20matches')Find_/_Replace(%7B'option':'Regex','string':'%5C%5Cn'%7D,'%2030,',true,false,true,false)Fork(',','',false)Sum('Space')From_Charcode('Comma',10)

解码后的有效载荷结果是另一个JavaScript脚本。

  • MD5: a8b63471215d375081ea37053b52dfc4
  • SHA256: 12c0067a15a0e73950f68666dafddf8a555480c5a51fd50c6c3947f924ec2fb4
  • 文件大小: 507 KB

JavaScript脚本包含插入编码的PE文件(非托管代码)、创建一个以编码程序集为值的注册表项("HKEY_CURRENT_USER\SOFTWARE\Microsoft\Phone"),然后启动2个PowerShell脚本的代码。这两个PowerShell脚本是无文件的,因此没有文件名。为了在本文档中引用,PowerShell脚本命名如下:

  • powershell_loader:这个PowerShell脚本是一个加载器,用于执行注入到注册表中的PE文件。
  • powershell_persistence:这个PowerShell脚本创建一个计划任务,在启动时执行加载器PowerShell脚本(powershell_loader)。

利用一个自定义脚本将整个有效载荷解码并从中提取所有独立的元素(基于对脚本本身的反向工程)。以下是自定义脚本的输出:

使用此脚本提取的所有工件与从受感染机器恢复的工件完全匹配。这些可以通过从Defender日志中提取的无文件工件进行验证,具有匹配的加密哈希值:

  • 阶段 2 SHA256 脚本: 12c0067a15a0e73950f68666dafddf8a555480c5a51fd50c6c3947f924ec2fb4
  • 阶段 2 SHA256 持久化PowerShell脚本 (powershell_persistence): 48e94b62cce8a8ce631c831c279dc57ecc53c8436b00e70495d8cc69b6d9d097
  • 阶段 2 SHA256 包含在持久化PowerShell脚本中的PowerShell脚本 (powershell_loader): c8a3ce2362e93c7c7dc13597eb44402a5d9f5757ce36ddabac8a2f38af9b3f4c
  • 阶段 3 SHA256 程序集: f1b33735dfd1007ce9174fdb0ba17bd4a36eee45fadcda49c71d7e86e3d4a434
  • 阶段 4 SHA256 DLL: 63bf85c27e048cf7f243177531b9f4b1a3cb679a41a6cc8964d6d195d869093e

基于此信息,可以有把握地得出结论,在VirusTotal上找到的有效载荷与受感染机器下载的有效载荷是相同的:所有哈希值都与来自受感染机器的工件匹配。

除了这些匹配哈希值带来的证据外,阶段2有效载荷文件还以以下字符串结尾(这不是编码脚本的一部分):@83290986999722234173581@。这是用于请求此有效载荷的URL的随机部分。注意它以4173581结尾,这是在木马化jQuery脚本中为已加入域的机器发现的唯一数字。

从VirusTotal检索有效载荷 尽管VirusTotal有关于此恶意脚本使用的几个URL的报告,但没有一个报告包含实际下载内容的链接。然而,使用以下查询:content:"378471678671496876716986",在VirusTotal上找到了下载内容(有效载荷);这串数字对应于字符串"CreateObject"的编码。(见图20)

为了尝试检索下载内容,我们做了一个有根据的猜测,即下载的有效载荷将包含对函数CreateObject的调用,因为此类函数调用也存在于木马化jQuery脚本中。VirusTotal上有无数文件包含字符串"CreateObject",但在这种特定情况下,它是使用GootLoader特定的编码进行编码的。字符串"CreateObject"的每个字母都被编码为其数字表示(ASCII码),并减去30。这返回字符串"378471678671496876716986"

阶段 3 – .NET 加载器

  • MD5 程序集: d401dc350aff1e3fd4cc483238208b43
  • SHA256 程序集: f1b33735dfd1007ce9174fdb0ba17bd4a36eee45fadcda49c71d7e86e3d4a434
  • 文件大小: 13.50 KB

此.NET加载器是无文件的,因此没有文件名。 PowerShell加载器脚本 (powershell_loader)

  • 从注册表中提取.NET加载器
  • 解码它
  • 动态加载并执行它(即,它不被写入磁盘)。

.NET加载器以十六进制编码并存储在注册表中。它被轻微混淆:字符 # 必须替换为 1000

.NET加载器:

  • 从注册表中提取DLL(阶段4)
  • 解码它
  • 动态加载并执行它(即,它不被写入磁盘)。

DLL以十六进制编码,但使用了替代字符集。通过以下替换表将其转换为常规十六进制:

此Test函数解码DLL并在内存中执行它。请注意,即使没有.NET加载器,统计分析也可能揭示DLL。我们同事Didier Stevens撰写的一篇关于如何通过执行统计分析来解码有效载荷的博客文章可以提供一些关于如何做到这一点的见解。

阶段 4 – Cobalt Strike DLL

  • MD5 DLL: 92a271eb76a0db06c94688940bc4442b
  • SHA256 DLL: 63bf85c27e048cf7f243177531b9f4b1a3cb679a41a6cc8964d6d195d869093e

这是一个典型的Cobalt Strike信标,具有以下配置(使用1768.py提取)

现在,Cobalt Strike作为感染链的最后一部分被加载,攻击者可以控制受感染的机器,并可以从此机器开始他的侦察,或者利用Cobalt Strike中的后利用功能,例如下载/上传文件、记录击键、截图等。

结论

对木马化jQuery JavaScript的分析确认了对从受感染机器收集的工件的初步分析,并确认木马化jQuery包含恶意混淆代码,用于从Internet下载有效载荷。该有效载荷旨在以无文件方式,并通过启动持久性,实例化一个Cobalt Strike信标。

关于作者

Didier Stevens Didier Stevens是NVISO的恶意软件专家。Didier是SANS互联网风暴中心的高级处理员和微软MVP,并开发了许多流行的工具来协助恶意软件分析。你可以在Twitter和LinkedIn上找到Didier。

Sasja Reynaert Sasja Reynaert是NVISO的法证分析师。Sasja是GIAC认证的事件处理员、取证审查员和分析师(GCIH, GCFE, GCFA)。你可以在LinkedIn上找到Sasja。

你可以在Twitter上关注NVISO实验室,以了解我们未来的所有研究和出版物。

JsOOLkj6O336Tkz+mrHPMLerEHP1Mgf7o1+Ojniyd/+9lw6giSs+4eFeiSLrXTxxhsqatvrK5vxGop79J+5Al0+krx32HjO8PmhYTzX/1YzHSkpYQzmCgJWGoJm/1Iay