0x00 前言

在日常玄武推送中,看到卡巴的一篇关于Lazarus新攻击框架MATA的概要分析:https://securelist.com/mata-multi-platform-targeted-malware-framework/97746/

里面提到了最近比较火的Lazarus MATA多平台攻击的新框架。

里面提到了部分的hash,于是下载下来尝试分析一下。

0x01 MATA loader分析

首先是loader的md5 list:
f364b46d8aafff67271d350b8271505a
85dcea03016df4880cebee9a70de0c02
1060702fe4e670eda8c0433c5966feee
7b068dfbea310962361abf4723332b3a
8e665562b9e187585a3f32923cc1f889
6cd06403f36ad20a3492060c9dc14d80
71d8b4c4411f7ffa89919a3251e6e5cb
a7bda9b5c579254114fab05ec751918c
e58cfbc6e0602681ff1841afadad4cc6
7e4e49d74b59cc9cc1471e33e50475d3
a93d1d5c2cb9c728fda3a5beaf0a0ffc
455997E42E20C8256A494FA5556F7333
7ead1fbba01a76467d63c4a216cf2902
7d80175ea344b1c849ead7ca5a82ac94
bf2765175d6fce7069cdb164603bd7dc
b5d85cfaece7da5ed20d8eb2c9fa477c
6145fa69a6e42a0bf6a8f7c12005636b
2b8ff2a971555390b37f75cb07ae84bd
1e175231206cd7f80de4f6d86399c079
65632998063ff116417b04b65fdebdfb
ab2a98d3564c6bf656b8347681ecc2be
e3dee2d65512b99a362a1dbf6726ba9c
fea3a39f97c00a6c8a589ff48bcc5a8c
2cd1f7f17153880fd80eba65b827d344
582b9801698c0c1614dbbae73c409efb
a64b3278cc8f8b75e3c86b6a1faa6686
ca250f3c7a3098964a89d879333ac7c8
ed5458de272171feee479c355ab4a9f3
f0e87707fd0462162e1aecb6b4a53a89
f1ca9c730c8b5169fe095d385bac77e7
f50a0cd229b7bf57fcbd67ccfa8a5147

下载到本地之后,文件大小分布如下:
image.png

可以看到,文件大小分布还是比较均匀,大多数都在80到90kb左右,其中有一个75kb的文件,具体是否有区别,等会分析了就知道了。

我这里IDA随便加载了一个文件,md5是:8e665562b9e187585a3f32923cc1f889

通过IDA加载之后,我们可以发现该文件是一个DLL并非EXE,接着查看了其他的loader,发现均为dll文件,回到卡巴的文章中我们可以发现如下描述:
image.png

卡巴这里提到,这些dll是由windows系统级的文件C:\ Windows \ System32 \ wbem \ WmiPrvSE.exe 加载调用的,说明该阶段,攻击者应该已经完成了攻击的第一阶段,目前应该是在利用MATA框架在进行后续的横向移动。

IDA加载样本

image.png

该dll仅有一个导出函数,关键功能在DllMain函数中实现,在dllMain函数开始的地方,程序首先通过call sub_180001000的操作获取指定函数的ProcAddress。
于是我们可以将sub_180001000重命名为ReName_GetProcAddress
image.png

程序这里分别尝试获取了VirtualFree和LoadLibraryA的函数地址,接着通过call 调用了sub_180001034
image.png

sub_180001034函数中,首先是给r14寄存器赋值了一个特殊字符串:781D49A4CDB20FD520896DF6D096A6938C96D72
接着在后面调用我们先前看到的GetProcAddress函数获取VirtualAlloc函数的地址并且通过call rax的方式调用
image.png

VirtualAlloc分配成功之后,程序会尝试通过一个循环计算操作刚才我们看到的字符串,最后在下面call sub_18000194C,因为call完这个函数之后,程序就准备调用VirutalFree函数释放内存了,所以我猜测这里这个call应该是有内容的。
image.png

我们直接调试器跑过来看看。

将该文件重命名为load.dll 并且使用x64dbg加载:
image.png

加载之后会默认停留在系统断点,我们可以直接F9跑一下,让其停在程序断点:
image.png

这里程序提示停留在了该dll的EntryPoint,地址是7FEF81A2540
我们在IDA中看看DLLEntry的地址:180002540
image.png

可以看到,这里基地址是不一样的,此时有两种方法可以快速定位到我们想要的dllMain地址

一是我们看一下dllMain函数的地址和DllEntryPonit函数的地址是否只有最后的尾数不一样:
image.png

这里我们可以看到,的确,dllMain和DllEntryPonit只有最后四位不一样,dllmain对应1354,DllEntryPonit对应2540
所以我们只需要在调试器中,将DllEntryPonit的最后四位替换为1354即可来到dllMain:
image.png

成功来到DllMain
image.png

二是我们在IDA中交叉引用一下Dllmain的调用位置:
image.png

这里过来,再往上一级,就是DllEntryPonit:
image.png

所以我们也可以直接在DllEntryPonit中进入这个函数,然后对应的f7进入到DllMain即可。

sub_180001034

来到DllMain之后,依次往下走。F7进入到sub_180001034函数。

在该函数中,程序首先是通过VirtualAlloc分配一个1F0000开始的地址空间
image.png

通过我们先前在IDA中看到的循环,这里是对预定义的一个字符串解密,然后放入到VirutalAlloc分配的内存中
image.png

F4跑完之后,得到的内容如下:
image.png

看起来还像是加密的数据,我们继续往下看
接着程序是调用了我们之前在IDA中标注的关键函数,F7跟进进来看看
image.png

函数解密完成之后,解密出如下信息,看起来像是个释放文件的路径
C:\Windows\System32\10fd905.dat
image.png

接着程序通过VirtuAlFree释放刚才开辟的内存
image.png

然后执行ret退出函数,DllMain中的第一个函数执行完毕。

通过初略的分析我们可以知道该函数用于解密获取文件释放的路径,解密成功之后程序会调用sub_180001168函数
image.png

sub_180001168

在该函数中,程序首先是通过GetProcAddress函数获取一系列的API地址
获取的API有:
GetFileAttributesW
CreateFileW
CreateFileMappingW
MapViewOfFile
VirtualAlloc
GetFileSize
image.png

看起来像是对文件进行校验的操作。

然后程序有一些call寄存器的操作,需要调试才能看到
image.png

程序首先通过GetFileAttributesW获取先前解密出来的文件的属性:
image.png

如果返回值是-1 这里应该表示文件不存在
image.png

64位loader总结

通过上面的分析我们可以知道,在lazarus调用该dll的时候,C:\Windows\System32\10fd905.dat这个文件已经存在了,该dll会尝试去从这个文件中读取数据继续执行。

32位loader分析

IDA加载这个dll,只有一个导出函数,默认停留在了DLLMain的入口
image.png

看了下导入表和字符串表,没有发下比较可疑的东西,于是直接看看代码。
程序首先是调用了两次sub_10001000函数,并且传递了两个不同的值进去。
image.png

进入到sub_10001000函数大概看一下可以得知该函数好像在64位的loader中看到过,是一个解密函数。
image.png

先不详细分析这个算法,我们直接调试器过来看看,会进行什么操作:
image.png

这里看起来像是在将传入进来的值进行计算,解密出API,我们再运行到下面那个函数调用,看看是否解密出第二个API:
image.png

这里可以看到,sub_10001000函数的确是解密API使用的,于是我们可以将sub_10001000重命名,并且将对应的API标记出来。
image.png

LoadLibrary成功之后,程序会调用sub_100010EB函数
image.png

100010EB

sub_100010EB函数进来又解密了两个API,然后看起来做了一些运算操作。调试器进来可以看到解密的API分别为VirtualAlloc和VirtualFree
image.png

然后VirutalAlloc分配内存空间:
image.png

循环解密出了如下的内容:
image.png

然后再下面的178E函数这里继续解密刚才解出来的数据
image.png

解出来得到一个文件路径:
image.png
C:\Windows\SoftwareDistribution\Download\c4654f9e3710c20a1a4b62256d904b03452fcbcc

sub_10001000函数调用完成,成功解密出对应的文件路径之后,程序将会尝试调用sub_100011F9
image.png

100011F9

函数进来是一大段解密API的操作
image.png

解密之后如下:
image.png

解密之后的API和x64版本用的几乎一模一样,所以这里基本上也可以确定后面的操作,也是尝试从刚才解密的路径去判断是否存在文件,如果存在,则尝试读取文件的数据。

首先还是通过GetFileAttributesw尝试获取文件属性。
image.png

这里可以看到,如果函数返回值为-1,文件不存在的话,程序将直接通过jmp跳过去结束程序,相反,如果确认文件存在的话,则jnz跳转到下面继续执行。
image.png

如果文件存在,程序则通过CreateFilew函数获取文件句柄。
image.png

程序还是会每执行一步操作就进行判断,如果某一个操作失败,则退出。
image.png

最后程序将尝试通过Virtualloc分配内存空间并将对应的数据读取进去。
image.png

最后程序会通过sub_10017be这个函数,对目标内存进行解密填充。对其交叉引用可以发现,先前在解密文件路径中就调用了该函数,说明这个解密算法是相同的。
image.png

最后程序通过UnmapViewOfFile停止当前程序的一个内存映射,100011F9函数运行结束。

到这里我们就可以发现,x64和x86两个平台的loader,其实功能是一样的,都是先解密出一个路径,然后尝试从读取这个路径的文件并通过自定义的解密算法进行解密加载执行。解密出来的文件应该就是后续的Orchestrator

其中,一个通用的解密算法如下:
image.png
image.png

还有一个算法,这两个算法都是在多个样本中复用的,可以考虑提取yara查杀此类的样本。
image.png

0x02 Orchestrator 概要分析

根据卡巴的报告来看,Windows平台下的Orchestrator有如下几个公开的样本:
bea49839390e4f1eb3cb38d0fcaf897e rdata.dat
8910bdaaa6d3d40e9f60523d3a34f914 sdata.dat
6a066cf853fe51e3398ef773d016a4a8
228998f29864603fd4966cadd0be77fc
da50a7a05abffb806f4a60c461521f41
ec05817e19039c2f6cc2c021e2ea0016

样本相比之前的loader偏大,在1300k到1700k左右
image.png
先打开最小的这个1363kb的文件看看。

首先,看看这个文件的导入表和字符串表,在导入表中可以看到一些加密相关的API:
image.png

其次,在字符串表中也可以看到一些AES相关的字符串,可以初步推测该样本将会使用AES的方式加密数据。
image.png

除此之外,在字符串表中还发现了tls相关的字符串,基本可以知道,该样本的通信应该走的TLS协议,老实说看到这里我已经准备放弃了,之后必定是肯多坑。
image.png

来到代码,该文件还是一个dll,有两个导出函数分别是Cron和DLLEntryPoint。
image.png

可以直接双击来到Cron函数处,过来之后可以发现这里的函数调用方式很熟悉,跟上面分析loader的时候解密API的方式一样,所以这里基本也可以推测,sub_10002BC3是跟之前一样的API解密函数。
image.png

于是调试器加载该dll,停留在的EntryPoint是741CB123 正好对应我们IDA中看到的DLLentryPoint 100CB123
image.png

通过对比Cron和DLLEntryPoint的地址我们可以推测出,在x32dbg中 Cron函数的的其实地址应该是741033D3
image.png

于是在调试器中跳转到741033D3并在此行设置新的运行点。
image.png

验证了一下,跟我们的猜想一致,这里还是一个同之前一样的解密函数,解密出来得到LoadLibrary
image.png

接下来程序通过mov esi ,eax成功赋值给esi之后,就通过call esi来load这些Library
image.png

LoadLibrary操作完成之后,程序将会解密并且调用GetTickCount获取系统启动的时间
image.png

然后生成随机数并且调用sub_10001809
image.png

sub_10001809

来到函数中,首先会将Software\mthjk 这个字符串拷贝到String1的位置,通过后面的发现我们可以知道,样本会直接尝试读取string1的内存判断字符串是否为Software\mthjk
image.png

并且这里可以看到,样本在代码中硬编码了三个IP地址分别是:
23.227.199.53:443
209.90.234.34:443
23.254.119.12:443
这些地址应该会用于后续的请求。
image.png

且这三个地址都已经被打上了Lazarus相关的标签:
image.png

然后程序调用了sub_10003F6E这个函数,该函数又是一个解密函数,解密之后会将数据放入到ecx指向的内存中。
image.png

于是,该函数调用完成,在sub_10001809函数最后,程序会调用sub_100018e2
但是1000182函数的功能和刚才分析的sub_100019BD基本相同,看起来像是在检查注册表键值。
image.png

至此,sub_10001809函数执行完成,程序接下来会通过CreateThread创建线程,踩坑开始了。

程序的主要功能就是在新线程中进行的,且在后面的调试中发现,不仅仅会创建一个线程,且在新线程中会有很多的骚操作,由于时间的关系,后面就没继续分析了,感觉至少需要投入很多精力, 而且在此分析过程中,发现样本的前后校验很严重,如果要继续往后分析,估计得将之前分析中提到的几个算法都完整的逆出来才行。有兴趣的老哥可以去采坑试试。

YARA规则

由于考虑到样本都复用了一个加密算法,于是尝试对此算法提取YARA规则试试。
通过分析可以看到,图中左右两边IDA中的代码是完全相同的,但是我发现在下面的IDA中,有微小的变化。
image.png
首先,三个样本都是 64 A1 30 00 00 00 的方式打头
然后在上面的两个样本中 是执行了 53 56 57 的操作
但是在下面的操作中只有 53 56 的操作
此外,上面都是通过 8B 40 0C 的方式直接赋值
而下面在执行8B 40 0C之前还执行了89 4D E8的操作
所以我们可以编写如下的规则,我们只去匹配 64 A1 30 00 00 00 的操作和 8B 40 0C 8B 70/50 14 的操作

  1. rule Lazarus_MATA
  2. {
  3. meta:
  4. description = "用于查杀Lazarus mata 的yara特征"
  5. author = "p1ut0"
  6. date = "2020-08-05"
  7. reference = "https://securelist.com/mata-multi-platform-targeted-malware-framework/97746/"
  8. hash = "hash"
  9. strings:
  10. $init_shellcode = {64 A1 30 00 00 00 53 56 [0-10] 8B ?? 0C [0-10] 8B ?? 14 }
  11. condition:
  12. uint16(0) == 0x5A4D and $init_shellcode
  13. }

这里直接使用[0-10]的方式过滤了中间可能的其他操作,然后通过??通配符去匹配70/50

扫描结果如下:
image.png

扫描结果好像不是很好,于是我尝试继续查看样本之间的差异,找到没有被扫描到的样本,发现刚才的算法是32位的,而64位样本的解密算法如下(左边为64位)
image.png

经过对比,发现64位样本间是相同的:
image.png

所以可以考虑在原有特征上加一个rule:
image.png

但是查杀结果还是不够理想,这个时候我突然想到,既然调用的解密算法是一样的,那么传递进来解密的API的hex数据肯定也是相同的,我直接去扫这个hex不就可以了吗。
我们找到几个常用的API,比如VirutalAlloc LoadLibrary VirutalFree 这几个api对应的hex数据:
分别是
726774Ch LoadLibrary
300F2F0Bh VirutalFree
5BB1CE93h GetFileAttributesW
0E553A458h VirtualAlloc

操作方式应该都是 B9 xxx 然后call
所以yara可以如下写:

  1. import "pe"
  2. rule Lazarus_MATA
  3. {
  4. meta:
  5. description = "用于查杀Lazarus的yara特征"
  6. date = "2020-08-05"
  7. file_type = "dll"
  8. reference = "https://securelist.com/mata-multi-platform-targeted-malware-framework/97746/"
  9. hash = "hash"
  10. strings:
  11. $Hex_loadLibrary = {B9 4C 77 26 07 [0-10] E8}
  12. $Hex_VirutalFree = {B9 0b 2f 0f 30 [0-10] E8}
  13. $Hex_GetFileAttributesW = {B9 93 ce b1 5b [0-10] E8}
  14. $Hex_VirutalAlloc = {b9 58 a4 53 e5 [0-10] e8}
  15. condition:
  16. uint16(0) == 0x5A4D and pe.characteristics & pe.DLL and all of ($Hex*)
  17. }

image.png

最后 突然发现,这个算法是ror13AddHash32Sub算法,最开始没注意,果然还是算法菜狗。
所以最后这个yara规则其实有误报风险,毕竟很多恶意软件都会使用这个算法,所以传入进来的hex值可能是相同的。
所以要是想提取好的检测规则,还是提算法最靠谱 哈哈。