Return of the Evilnum APT with updated TTPs and new targets

概括

自 2022 年初以来,ThreatLabz 一直在密切监视 Evilnum APT 组织的活动。我们发现了他们针对我们在英国和欧洲地区的客户发起的小规模针对性攻击活动的几个实例。
活动的新实例使用更新的战术、技术和程序。在 2021 年观察到的早期活动中,该威胁组使用的主要分发媒介是 Windows 快捷方式文件 (LNK),在恶意存档文件 (ZIP) 中作为电子邮件附件在鱼叉式网络钓鱼电子邮件中发送给受害者。
在最近的例子中,攻击者已经开始使用 MS Office Word 文档,利用文档模板注入将恶意负载传送到受害者的机器上。在这篇博客中,我们介绍了端到端攻击链中涉及的所有组件的技术细节。在撰写本文时,据我们所知,这个 Evilnum APT 组的新实例的完整攻击链并未在任何地方公开记录。
ThreatLabz 已经确定了几个与 Evilnum APT 组相关的域,这些域以前未被安全供应商检测到。这一发现表明,Evilnum APT 小组已经成功地在雷达下飞行,并且长期以来一直未被发现。

关键点

  • Evilnum APT 集团的主要目标主要集中在金融科技(金融服务)领域,特别是在英国和欧洲处理贸易和合规的公司。
  • 2022 年 3 月,我们观察到 Evilnum APT 组的目标选择有重大更新。他们瞄准了一个处理国际移民服务的政府间组织
  • 袭击的时间线和选定目标的性质与俄罗斯与乌克兰的冲突相吻合。
  • 模板注入阶段使用的基于宏的文档利用 VBA 代码 Stomping 技术绕过静态分析并阻止逆向工程。
  • 使用严重混淆的 JavaScript 解密并丢弃端点上的有效负载。JavaScript 配置了一个计划任务来运行已删除的二进制文件。与 EvilNum APT 小组使用的先前版本相比,此 JavaScript 在混淆技术方面有显着改进。
  • 在执行过程中创建的所有文件系统工件的名称都是由威胁参与者仔细选择的,以欺骗合法的 Windows 和其他合法的第三方二进制文件的名称。
  • 在活动的每个新实例中,APT 组使用与行业垂直目标相关的特定关键字注册了多个域名。

    攻击流

    22.06.27-Zscaler - 带有更新的 TTP 和新目标的 Evilnum APT 回归 - 图1
    图 1:攻击链

    技术分析

    出于技术分析的目的,我们将考虑带有 MD5 的文档:0b4f0ead0482582f7a98362dbf18c219

    第 1 阶段:恶意文档

    第 1 阶段的恶意文档通过鱼叉式网络钓鱼电子邮件传递。一旦用户下载并打开恶意文档,它就会从攻击者托管的域中获取第 2 阶段的宏模板并显示诱饵内容,如图 2 所示,要求用户启用宏内容:
    22.06.27-Zscaler - 带有更新的 TTP 和新目标的 Evilnum APT 回归 - 图2
    图 2:诱饵内容

    第 2 阶段:宏模板 [VBA 代码 Stomping 技术]

    阶段 2 模板包含主要的恶意宏代码。它利用了在野外相当少见的 VBA 代码 Stomping 技术。这种技术会破坏原始源代码,并且仅将 VBA 宏代码(也称为 p 代码)的编译版本存储在文档中。
    因此,这种技术可以防止 olevba 等静态分析工具提取反编译的 VBA 代码。
    使用沃尔玛团队在这里提到的技术,我们能够提取完整的宏代码。(你个沃尔玛还有这功能)
    宏代码中的所有字符串均使用图 3 所示的字符串解密函数进行解密:
    22.06.27-Zscaler - 带有更新的 TTP 和新目标的 Evilnum APT 回归 - 图3
    图 3:VBA 宏代码中使用的字符串解密函数
    以下是宏的主要功能。
  1. 文档文件有两个文本框,内容是加密的。这些文本框将在运行时由 VBA 宏代码解密。
    1. 文本框 1“msform_ct.TextBox1.Text”将被解密并将内容写入%appdata%\ThirdPartyNotices.txt。
    2. 文本框 2“msform_ct.TextBox2.Text”将被解密并将内容写入 %appdata%\Redist.txt
  2. 将合法的 Windows 二进制Wscript.exe复制到名为“ msdcat.exe ”的文件中。此类文件复制操作是由恶意软件完成的,以此作为绕过端点安全产品的一种方式。
  3. 文件“Redist.txt”包含将使用以下命令行执行的经过混淆的 JavaScript:
  • msdcat.exe” /E:jscRipt “%appdata%\Redist.txt” dg ThirdPartyNotices.txt

注意: “dg”是存在于 VBA 宏代码中的硬编码命令行参数。

  1. VBA宏代码执行过程中,多次调用doc.Shapes.AddPicture()从攻击者控制的服务器获取JPG图片。我们认为这是攻击者为了跟踪和记录端点上代码的执行而完成的。

图 4 显示了一个这样的示例。在命令行的构建和命令行的执行之间存在对 doc.Shapes.AddPicture() 的调用:
22.06.27-Zscaler - 带有更新的 TTP 和新目标的 Evilnum APT 回归 - 图4
图 4:VBA 代码从攻击者的服务器获取图像以记录端点上的操作

第 3 阶段:丢弃 JavaScript [去混淆和分析]

基于 VBA 的宏代码丢弃的原始 JavaScript 被严重混淆。我们将重点介绍一些在混淆的 JavaScript 中很少观察到的独特混淆技术。
在执行时使用以下命令行向此 JavaScript 传递了两个参数:

  • msdcat.exe” /E:jscRipt “C:\Users\user\AppData\Roaming\Redist.txt” dg ThirdPartyNotices.txt

参数 1:“dg”。此字符串稍后用于 JavaScript 中的字符串解密函数。
参数 2:文件“ThirdPartyNotices.txt”包含加密代码,该代码将由 JavaScript 解密并在二进制名称的文件系统上删除“SerenadeDACplApp.exe”。
大多数混淆技术涉及大量加密和编码字符串,这些字符串在整个代码中使用索引进行引用。对此进行去混淆的常用方法需要多次“搜索和替换”操作,其中您将引用替换为实际解密和解码的字符串。
在这种情况下,JavaScript 使用了一种有趣的技术,其中原始字符串数组被打乱,并在执行时在内存中被打乱。因此,任何在不解散数组的情况下取消引用字符串的尝试都会导致错误。这种方法可以用来阻止逆向工程,也可以绕过一些试图自动化反混淆过程的工具。
让我们更详细地看一下。
下面的图 5 显示了在 JavaScript 开头定义的巨大字符串数组。这个数组被包裹在一个函数中,作为一个额外的混淆层。
22.06.27-Zscaler - 带有更新的 TTP 和新目标的 Evilnum APT 回归 - 图5
图 5:编码和加密字符串数组
下一步是解散数组。下面的图 6 显示了相关的 JavaScript 代码,该代码使用蛮力方法来解散数组。它有一个预定义的种子值“0x6467a”。在每次迭代中,该函数使用各种数学运算计算种子并将其与预定义的种子“0x6467a”进行比较。该函数继续将数组的内容向右移动一个位置,直到满足此条件。
代码中包含相关注释以说明 unshuffle 逻辑:
22.06.27-Zscaler - 带有更新的 TTP 和新目标的 Evilnum APT 回归 - 图6
图 6:解洗数组的 JavaScript 函数
用于混淆的其他技术涉及利用开关盒混淆的控制流扁平化技术。图 7 显示了使用这种混淆技术的字符串解密函数之一:
22.06.27-Zscaler - 带有更新的 TTP 和新目标的 Evilnum APT 回归 - 图7
图 7:使用 switch-case 混淆来扁平化控制流
使用 switch-case 对解密步骤的顺序进行打乱,并按照以下顺序遵循顺序:

  • “15|12|3|2|14|5|1|10|9|17|8|7|6|4|13|16|0|11”

这意味着,执行“case 15”,然后执行“case 12”,依此类推。最后的“case 11”返回解密后的字符串。
我们可以重写图 8 所示的字符串解密函数,这样更容易分析:
22.06.27-Zscaler - 带有更新的 TTP 和新目标的 Evilnum APT 回归 - 图8
图8:改写的字符串解密函数
我们在附录 IoCs 中包含了从 JavaScript 中提取的有趣的解密字符串列表。

持久性

威胁参与者通过计划任务实现持久性。在 JavaScript 执行期间,将创建一个名为“UpdateModel Task”的计划任务,以使用所需的命令行参数执行已删除的加载程序二进制文件。

任务详情
  1. <Exec>
  2. <Command>
  3. %appdata%\Microsoft\FontCache\CloudFonts\SerenadeDACplApp.exe
  4. </Command>
  5. <Arguments>
  6. "OUM3NjBDNjAtRkNDQi00Q0FDLUE5NEMtNzY0RTc5MDNDN0Mw" "devZUQVD.tmp" "NzkzMTA3" "Ni4xLjc2MDE%3D" 0 "E4A6450B" "NTk1NDQxWwpaWhlhdmVbB1tf" Z
  7. </Arguments>
  8. <WorkingDirectory>
  9. %appdata%\Microsoft\FontCache\CloudFonts
  10. </WorkingDirectory>
  11. </Exec>

第 4 阶段:丢弃二进制文件(加载程序)

如上一节所述,JavaScript 删除了两个文件:

  • 一个可执行文件 (SerenadeDACplApp.exe) - 它原来是一个加载器
  • 二进制文件 (devZUQVD.tmp) - 这是加载程序在运行时加载的文件

加载器由计划任务连同所需的参数一起执行。在执行过程中,它执行以下操作:

  1. 执行命令行检查并提取要加载的二进制文件的文件名

加载器检查命令行是否以 ( ) 结尾。如果为真,则终止进程,否则它将解析参数以提取要加载的二进制文件的文件名。
# 提取文件名有两种代码逻辑

  • 如果第一个参数具有格式(—[char]=[char]*),那么加载器将从该参数字符串中删除前 5 个字符,在其前面加上“dev”附加“.tmp”。生成的字符串用作已删除二进制文件的文件名。

例子:
参数字符串: —E=nThisIsUsedInFileName
提取的文件名: devThisIsUsedInFileName.tmp

  • 第二个参数字符串用作已删除二进制文件的文件名
  1. 生成DOS格式的二进制文件的完整路径

加载程序首先提取当前正在执行的二进制文件的完整路径,并在其前面加上“\??\”,然后用步骤 1 中提取的文件名覆盖当前正在执行的二进制文件的文件名。

  1. 使用Heaven’s gate技术调用NtOpenFile API创建文件句柄
  2. 使用 RtlAllocateHeap API 为读取文件内容分配内存
  3. 使用Heaven’s gate技术调用NtReadFile API 读取文件内容到分配的内存
  4. 解密文件内容

# 加密内容格式
XOR 密钥长度(1 字节)+ XOR 密钥 + 加密内容大小(4 字节)+ 加密内容:
22.06.27-Zscaler - 带有更新的 TTP 和新目标的 Evilnum APT 回归 - 图9
图 9:加密内容格式
解密后的内容原来是一个 PE 文件,它使用自定义标头来存储 PE 标头和节标头信息。
# 解密内容格式
PE头格式(+节头格式+节数据)节数
#PE头格式
解密内容的开始以及 PE heaser(1 字节 - 00)+ 图像基础(4 字节)+ 图像大小(4 字节)+ 入口点(4 字节)+ 节数(4 字节)+ 到第一节的偏移量解密内容开头的信息(4 字节)+ 解密内容的大小(4 字节)
*# 节头格式

节号标记(1 字节)+ 节 RVA(4 字节)+ 节 VirtualSize(4 字节)+ 未知(4 字节):
22.06.27-Zscaler - 带有更新的 TTP 和新目标的 Evilnum APT 回归 - 图10
图 10:PE 头、Section 头和 Section 数据

  1. 使用Heaven’s gate技术调用NtAllocateVirtualMemory API 为要映射的PE文件分配内存。

注意:大小取自上述 PE 标头格式。

  1. 映射内存中的PE文件。
  2. 使用Heaven’s gate技术调用NtCreateThreadEx API 创建一个线程指向映射PE 的入口点。

注意:加载程序使用Heaven’s gate技术来逃避端点安全产品以及系统调用或 API 监控应用程序。它使用自定义标头格式来阻止对 PE 标头或节标头模式的内存扫描,并且还难以将 PE 文件作为独立的可执行文件进行转储和分析。

阶段 5:映射 PE(主后门)

映射的 PE 是攻击链的主要后门。在执行时,它执行以下操作:

  1. 解密后门配置,包括:
    1. C2 域
    2. 用户代理字符串
    3. 网络路径
    4. Referer字符串
    5. Cookies 类型字符串
    6. 请求方法 + 库名称(这些将在进一步执行期间加载) + 网络通信密钥生成种子字节 + 互斥体名称

配置中的所有信息都使用 XOR 密钥加密。配置中的每个数据项的 XOR 键都不同。
# 加密数据项格式
加密数据大小(2 字节)+ 加密数据 + XOR 密钥长度(2 字节)+ XOR 密钥:
22.06.27-Zscaler - 带有更新的 TTP 和新目标的 Evilnum APT 回归 - 图11
图 11:加密数据项格式

  1. 解析从配置中检索到的库的 API 地址
  2. 执行互斥检查
  3. 构建要作为信标请求的一部分发送的数据泄露字符串

# 字符串格式

  1. {
  2. "v":"62","u":"{first_arg-user_id}","a":"{third_arg}","w":"{fourth_arg}","d":"{sixth_argument}", "n":"{seventh_arg}","r":"0","xn":"{name_of_executing_binary}","s":0
  3. }
  1. 对生成的字符串进行加密和Base64编码:

22.06.27-Zscaler - 带有更新的 TTP 和新目标的 Evilnum APT 回归 - 图12
图 12:对泄露的数据进行加密和编码的步骤
注意:您可以在附录 III 部分找到解密代码

  1. 通过从配置中选择一种 cookie 类型字符串,将编码字符串嵌入 cookie 标头字段中。

    [+] 网络通讯

    完成以上所有操作后。后门从配置中选择一个 C2 域和一个路径字符串,并发送信标网络请求。
    如果信标请求成功,后门将向服务器查询可用内容并下载。
    根据内容大小执行两种不同的操作:

  2. 如果内容大小为4,则后门检查下载的数据是否等于“01”。如果为真,它会获取机器快照并通过 POST 请求将其发送到 C2 服务器。快照数据以加密形式泄露,cookie 标头包含附加信息。

# cookie头字符串的格式

  1. {
  2. "u":"{first_arg-user_id}", "sc":1, "dt"="{snapshot_date_time}"
  3. }
  1. 如果内容大小大于4,则后门将下载的数据解密并执行。

    Zscaler 沙盒报告

    模板有效载荷

    22.06.27-Zscaler - 带有更新的 TTP 和新目标的 Evilnum APT 回归 - 图13

    IoCs

    | MD5 | 描述 | 文件名 | | —- | —- | —- | | 0b4f0ead0482582f7a98362dbf18c219 | Document | proof of ownership.docx | | 4406d7271b00328218723b0a89fb953b | Document | tradersway compliance.docx | | 61776b209b01d62565e148585fda1954 | Document | vantagemarkets documents.docx | | 6d329140fb53a3078666e17c249ce112 | Document | vantagefx compliance.docx | | db0866289dfded1174941880af94296f | Document | calliber docs (2).docx | | f0d3cff26b419aff4acfede637f6d3a2 | Document | complaince tfglobaltrading.docx | | 79157a3117b8d64571f60fe62c19bf17 | Document | complaint europatradecapital.com.docx | | 63090a9d67ce9534126cfa70716d735f | Document | fxtm_compliance.docx | | f5f9ba063e3fee25e0a298c0e108e2d4 | Document | livetraderfx.docx | | ea71fcc615025214b2893610cfab19e9 | Loader | SerenadeDACplApp.exe | | 51425c9bbb9ff872db45b2c1c3ca0854 | Encrypted binary | devZUQVD.tmp |

Domain

travinfor.com
webinfors.com
khnga.com
netwebsoc.com
infcloudnet.com
bgamifieder.com
bunflun.com
refinance-ltd.com
book-advp.com
mailservice-ns.com
advertbart.com
inetp-service.com
yomangaw.com
covdd.org
visitaustriaislands.com
traveladvnow.com
tripadvit.com
moreofestonia.com
moretraveladv.com
estoniaforall.com
bookingitnow.org
travelbooknow.org
bookaustriavisit.com
windnetap.com
roblexmeet.com
netrcmapi.com
meetomoves.com
bingapianalytics.com
azuredcloud.com
appdllsvc.com
udporm.com
pcamanalytics.com
nortonalytics.com
deltacldll.com
mscloudin.com
msdllopt.com

URI

下面的 URI 路径被恶意软件在发送 POST 请求时附加到域名
/actions/async.php
/admin/settings.php
/admin/user/controller.php
/admin/loginauth.php
/administrator/index.php
/cms/admin/login.php
/backend/login/ajax_index.php
/wp-admin/media-new.php
/get.php
/auth/login

Scheduled task names

UpdateModel Task
PropertyDefinitionSync
Schedule Defrag

Unique strings extracted from the deobfuscated JavaScript

appdata%\Microsoft\FontCache\CloudFonts\Fonts
Schedule.Service
SELECT UUID FROM Win32_ComputerSystemProduct
SELECT Version FROM Win32_OperatingSystem
%USERDOMAIN%
%USERNAME%
MUID
UpdateModel Task
/c start /min “” powershell -inputformat none -outputformat none -windowstyle hidden -c
%localappdata%\DELL\DellMobileConnect\Dumps\TechToolkit.exe
%localappdata%\DELL\DellMobileConnect\Dumps
PropertyDefinitionSync
PT5H
%appdata%\Mael Horz\HxD Hex Editor\Logs\nvapiu.exe
%appdata%\Mael Horz\HxD Hex Editor\Logs
Schedule Defrag
PT5H
MetadataRefreshTask
WsSwap AssessmentTask
U64Pan.exe
cmd /c “”ping 1.1.1.1 -n 5 -w 10000 > nul & del /q
avast
avg
AntiVirusProduct
SerenadeDACplApp.exe
Western Digital\WD Backup\Storage
calcy
SupportAssistAppWire.exe
E4A6450B
wctXSPKB.tmp
msdcat

运行时字符串存在于解压后门二进制文件中

/admin/settings.php
/admin/index.php
/actions/authenticate.php
/index.php
/actions/async.php
/wp-admin/media-new.php
/backend/login/ajax_index.php
/administrator/index.php
/admin/login.php
/admin/loginauth.php
/wp-admin/admin-ajax.php
/admin/user/controller.php
/get.php
/cms/admin/login.php
APISID
SAPISID
SIDCC
MSFPC
__cfruid
_vwo_uuid_v2
campaign
source
Referer: http://www.bing.com
Referer: http://www.google.com
Referer: http://www.yahoo.com
Referer: http://www.facebook.com
Referer: http://github.com
Referer: http://www.instagram.com
Referer: http://mail.google.com
Connection: keep-Alive
Content-Type: text/plain
Accept-Language: en-US,en;q=0.8
Accept: /
Cookie:
Global\wU3aqu1t2y8uN
ntdll.dll
kernel32.dll
combase.dll
ole32.dll
OleAut32.dll
wininet.dll
Shell32.dll
Shcore.dll
User32.dll
Gdi32.dll

网络通信的解密代码

  1. #include <Windows.h>
  2. #include <stdio.h>
  3. #define SEED_SIZE 32
  4. VOID DeriveKey(BYTE seed[], BYTE key[])
  5. {
  6. BYTE swapByte = 0;
  7. BYTE seedIndex = 0;
  8. BYTE calKeyIndex = 0;
  9. /* Initialize the key array */
  10. for (int i = 0; i < 256; i++)
  11. {
  12. }
  13. /* Calculate XOR key */
  14. for (int currKeyIndex = 0; currKeyIndex < 256; currKeyIndex++)
  15. {
  16. calKeyIndex = seed[currKeyIndex % SEED_SIZE] + key[currKeyIndex] + calKeyIndex;
  17. swapByte = key[currKeyIndex];
  18. key[currKeyIndex] = key[calKeyIndex];
  19. key[calKeyIndex] = swapByte;
  20. }
  21. /* Print the derived XOR key */
  22. for (int k = 0; k < 256; k++)
  23. {
  24. printf("%02x ", key[k]);
  25. }
  26. }
  27. VOID Decrypt(BYTE data[], BYTE key[])
  28. {
  29. BYTE XORKeySize = data[0];
  30. BYTE *XORKey = (BYTE*)data + sizeof(BYTE);
  31. UINT encryptedDataSize = data[sizeof(BYTE) + XORKeySize];
  32. BYTE *encryptedData = (BYTE*)data + (sizeof(BYTE) + XORKeySize + sizeof(UINT));
  33. BYTE *layer1DecryptedData = (BYTE*)malloc(encryptedDataSize);
  34. for (UINT dataIndex = 0; dataIndex < encryptedDataSize; dataIndex++)
  35. {
  36. layer1DecryptedData[dataIndex] = encryptedData[dataIndex] ^ XORKey[dataIndex % XORKeySize];
  37. }
  38. BYTE swapByte = 0;
  39. BYTE calKeyIndex = 0;
  40. BYTE finalKeyIndex = 0;
  41. for (UINT index = 1; index <= encryptedDataSize; index++)
  42. {
  43. calKeyIndex = key[index] + calKeyIndex;
  44. swapByte = key[index];
  45. key[index] = key[calKeyIndex];
  46. key[calKeyIndex] = swapByte;
  47. finalKeyIndex = key[index] + key[calKeyIndex];
  48. printf("%c ", layer1DecryptedData[index - 1] ^ key[finalKeyIndex]);
  49. }
  50. }
  51. int main()
  52. {
  53. BYTE key[256];
  54. BYTE seed[SEED_SIZE] =
  55. {
  56. // Taken from configuration
  57. 0xBD, 0xDE, 0x96, 0xD2, 0x9C, 0x68, 0xEE, 0x06, 0x49,
  58. 0x64, 0xD1, 0xE5, 0x8A, 0x86, 0x05, 0x12, 0xB0, 0x9A,
  59. 0x50, 0x00, 0x4E, 0xF2, 0xE4, 0x92, 0x5C, 0x76, 0xAB,
  60. 0xFC, 0x90, 0x23, 0xDF, 0xC6
  61. };
  62. BYTE data[] =
  63. {
  64. // Put Base64 decoded encrypted data here in HEX format
  65. };
  66. DeriveKey(seed, key);
  67. printf("\n\n");
  68. Decrypt(data, key);
  69. return 0;
  70. }