一款商业窃密马的前世今生

 

0x00 前言

在分析非PE的样本中我们可以发现,有三个漏洞是office样本常客,分别是2017-1182、0802、0798

这几个漏洞非常常见,目前也是office样本漏洞利用的大头。包括很多APT现在都还在使用,比如Gorgon、Gamaredon就尝尝使用0798进行模板注入。

当然 还有一个常见的漏洞是2017-0261 EPS漏洞。

在本文中,我们将详细介绍2017-11882这个漏洞。并详细分析使用该漏洞的一个实战样本。 其他的几个漏洞在后面的文章中依次详细介绍。

 

0x01 POC复现

这个漏洞的poc有很多,这里使用的是https://github.com/Ridter/CVE-2017-11882/

通过python Command_CVE-2017-11882.py -c “cmd.exe /c calc.exe” -o test.doc命令生成test.doc

然后在安装了office2013上的机器打开,弹出了计算器。

调试样本

首先直接打开word程序:

在插入选项下,选择对象,然后插入Microsoft公式3.0

现在启动x32dbg,选择文件->附加:

因为我们这里知道文档运行之后会打开计算器,所以附加上EQNEDT32.exe之后通过分别给CreateProcessA和CreateProcessW设置断点

F9跑起来,然后使用word打开poc生成的test.doc文档,打开之后成功断下来:

在返回地址处设置断点,返回回去发现计算器已经弹出了,这里可以通过ebp查看一下CreateProcess的调用

返回回来发现是WinExec函数调用的

所以我们重新按照上面的方法,对WinExec函数下断:

此时的00430C12是用户态地址,按道理来讲,ebp应该存放了当前函数的返回地址,但是这里是41414141,很明显ebp已经被破坏了。

所以我们向上看看,ebp是在哪儿被破坏的。

由于ebp被淹没,我们这里往上看,找找其他的返回地址:

这里最近的一个返回地址是在00411837,返回回去,往上翻,找到00411837所在函数的起始地址,设置一个断点,重新运行,让程序断在这个函数里面。

往下走几步在00411658的地方发现这样一条指令:
rep movsd dword ptr es:[edi], dword ptr [esi]
该指令执行完之后,ebp就被41414141覆盖
而该指令的功能是将esi的值传送到edi所指的位置。
我们分别查看一下esi和edi的值:

ESI:

EDI

值得注意的是,此时ebp的值是0018F1D0
很明显该地址在EDI所指的地址后面

rep movsd dword ptr es:[edi], dword ptr [esi]执行完之后,EDI被成功赋值,可以看到,此时ebp所在的值已经从0018F214变成了41414141

ebp后面的返回地址已经从004115d8更改为了00430c12

查看一下00430C12地址:

OK原来是这样跳过来的,过来执行WinExec函数之后,又在里面调用了CreateProcess函数,函数的参数为:

所以成功弹出计算器。

漏洞的导致原因是strcpy函数在使用的时候,未对参数的长度进行判断和限制,从而导致栈溢出。

而至于流程到底是怎么过来的,以及文档格式的其他分析之后再单独写一个文章介绍。

 

0x02 实战样本分析

样本来源于https://app.any.run/

样本MD5:8c166d55a69dda96f56596a783100842

样本下载链接:https://app.any.run/tasks/36dbd94f-f87c-4226-bb6f-cbe8b0e2f73b/

根据VT我们可以知道,该样本属于CVE-2017-11882的利用样本。

将样本下载本地并解压。

第一,我们可以在断网的虚拟机中直接尝试运行该样本,看看%appdata% %application% %temp%这几个目录下是否释放了对应的文件。

首先通过火绒剑分别监视WINWORD.exe和EQNEDT32.exe的行为。

这里EQNEDT32.exe开始的地方应该就是漏洞开始的行为。

之前讲过,11882的漏洞触发一般有两种情况。 一种是在本地Drop并加载一个文件,还有一种是去指定的地址下载文件到本地执行。

在火绒剑中往下看,很容易就能看到这样的行为。

这里很明显是程序conect 194.180.224.87:80

然后后面http 请求了 abass.ir/kellyx/kellyx.exe

后面这个应该是个下载链接。下载的文件默认情况下的话会保存为kellyx.exe

直接访问这个地址,也是可以下载文件回来的:

因为我用的是家里的网络,我就直接访问了。如果是用公司的网络,请勿直接这样访问恶意地址。

我们首先查询一下这里conect 的194.180.224.87

根据奇安信的TI,初步确认这里是个恶意地址。

然后我们查询下后面请求的那个域名:

后面这个域名已经被打上了对应的标签,且看起来,这个域名并不像是属于某个家族,更像是一个恶意软件公共使用的一个地址,就类似于开发使用的github?

于是回到火绒剑中,再往下翻翻其实就可以发现,样本已经成功下载回来了。并且保存的路径的确就是我们之前猜测的%appdata%,保存的名称为:kellye358348.exe

我们再往下看看,看是否还有其他的行为。

EQNEDT32.exe的行为到这里就分析完成了,根据火绒剑的监控来看,这个11882的漏洞利用文档,执行完之后其实就是会尝试从目标地址去下载对应的文件到本地并加载执行。

转到%appdata%目录下,可以看到文件的确已经被成功下载回来了。

按到正常的分析流程,我们这里可以直接分析这个exe了,但是为了调试cve-2017-11882漏洞,所以我们还是调试下这个样本,看看shellcode是如何执行的。

 

0x03 漏洞调试

经过上面的分析,我们已经知道,2017-11882漏洞的触发点应该在0041160F函数中的00411658这一行。

于是我们可以还是像上面的方式附加QENDT32.exe到调试器然后在0041160F函数入口点设置断点。

成功命中断点:

我们运行到00411658这一行代码处

分别查看一下ESI 、EDI、以及EBP的值

EDI的值如下:

EBP的值如下:

EBP的值这里可以看到0018F214

我们注意到,这个地址其实就在EDI后面

然后我们继续F8继续往下走,此时记得注意EDI的变化

这里运行下来之后,ESI的值成功赋值给了EDI,且我们可以看到,刚好ebp的值就被覆盖了,这里被覆盖为了2F E0 FF BF

然后我们直接在该函数最末尾的地F4,执行完这个函数,然后返回回来。

往下走几步可以看到如下的语句:

这里jmp eax 就是跳转到shellcode执行,这里就是关键call

多调试几次 顺利找到了下载的操作

F7进入函数,可疑看到具体执行的代码如下:

这里call eax 由于电脑已经断网了,这里不能正常请求,会抛出异常。

我恢复快照,然后再重新设置网络,调试一下试试。

样本成功下载之后,进来,可以看到这里直接就尝试CreateProcess 的方式加载执行这个EXE。

 

0x04 下载样本分析

样本基本信息

通过CFF 查看样本可以发现,该样本是.NET平台的样本

然后我突然发现,这里File Size 和PE Size不同,我突然意识到之前我们看到的那个文件是没有下载完的。所以恢复快照,跳转过来,继续下载,这次下载完成之后,显示1111kb,应该正常了。

重新加载该文件,可以看到这次正常了。

我们顺便查看一下资源:

这里有9个资源,等会可能会加载其中的资源。

寻找样本真正入口点

使用dnspy加载这个样本,如下:

入口点函数代码如下:

dnpy左边,程序对应了几个这样的namespace:

程序分别有三个namespace 分别是

beerparlourbillingsystem
beerparlourbillingsystem.My
beerparlourbillingsystem.My.Resources

这个beerparlourbillingsystem
拆开就是
beer parlour billing system

直译过来就是:啤酒厅计费系统
感觉像是一个正常的程序

先google一波beerparlourbillingsystem:

这玩意儿居然还真是正常的一个管理程序。

不过源码好像是使用VB写的。

我们下载个源码回来看看。

在vs中打开源码,然后对比一下dnspy中的结构,发现有两个类是原本的程序中没有的

分别是HighScoreSave和TetrisDrop

其中HighScoreSave如下:

TetrisDrop如下:

其中HighScoreSave中的tr函数有点像是一个自定义的解密函数:

FindForm中好像有操作资源的代码。

阅读HighScoreSave的代码,我们可以发现,这里应该是从资源读取数据,并且调用下面的tr函数进行解密。

现在第一个问题解决了,我们可以直接在tr函数中设置断点,应该就可以直接F5跑过来了。

但是我还是好奇,程序到底是怎么过来的。

于是我还是右键转到入口点,继续看入口点的代码。
程序的入口点在beerparlourbillingsystem.My类中

此时我注意到,在当前类的最下面有这样一个方法:

这里base.MainForm = MyProject.Forms.Splashscreen;

将当前的MainForm替换为了MyProject类下面的Splashscreen

所以在上面的MyProject.Application.Run(Args); 启动的实际上不是当前的Form

而是启动的Splashscreen

于是点过来看看Splashscreen:

这里通过this.m_Splashscreen的方式反射加载。

我们继续跟进,看看this.m_Splashscreen

这里的Splashcreen看起来很正常。

但是这里的Splashcreen是beerparlourbillingsystem类的,我们当前类(入口点所在的类)是beerparlourbillingsystem.My

我点击Splashcreen,进入到beerparlourbillingsystem的Splashcreen:

这里首先是Splashcreen的实例化方法Splashcreen():

Splashcreen()中分别调用了Splashscreen_Load:

和InitializeComponent:

这里Splashscreen_Load方法看起来非常正常,就是设置窗口,并没有其他操作。

但是这里的InitializeComponent,第一行就是:
HighScoreSave highScoreSave = new HighScoreSave();
这里一开始就是实例化了HighScoreSave类。
HighScoreSave就是我们先前看到的,可能有问题的那个类。

然后我们点击过来,可以看到在HighScoreSave类的实例方法中,调用了下面的FindForm方法。

此时我们直接在this.FindForm这一行设置断点,然后F5跑过来就可以了。

成功命中:

然后F11进入到FindForm函数,然后F10单步往下走,就不用进入下面的tr解密函数。

单步执行完成之后,下面的局部变量窗口将会出现tr函数的返回值。

我们展开查看一下这个返回值:

果然是4D5A:

在这行变量上右键然后保存,存储下这个解密出来的PE并命名为dump_file1

接着往下看,程序从资源解密出PE之后,尝试对TetrisDrop.Ng赋值。
第一个赋值语句是:
TetrisDrop.Ng[1] = TetrisDrop.He;

这里He点进去如下:

然后赋值Ng[2]
TetrisDrop.Ng[2] = “beerparlourbillingsystem”;

最后将TetrisDrop.Ng作为参数和type一起传递到Activator.CreateInstance函数中。完成dll的调用加载。

这里的Ng赋值为了rxcwON和grI

小结

在这个样本的分析中,可以看到攻击者是将少量的恶意代码插入到了正常的程序中。并且从读取的资源我们可以发现,资源名应该也是攻击者精心构造的。这种方式可以让人一眼看上去,感觉这个程序并没什么毛病。而且程序通过了反射实例化和不同namespace下的同名类名的情况进行加载。算是一个比较隐蔽的加载方式。

 

0x05 dump_file1

dump出来的这个文件是一个带有轻微混淆的C#dll文件,这里的混淆几乎可以忽略不计,所以直接分析即可。

过来首先是GodofBeauty类的实例方法:
GODofBeauty.Roman(ugz1, ugz3, projname);

这里进来首先调用了Roman函数,Roman函数的具体实现在下面43行的位置。

来到Roman的具体实现:

程序首先是生成一个46000到59000的随机数用于sleep,也就是程序会休眠46秒到59秒。这一步应该是为了反沙箱。因为沙箱一般都会有时间限制,slepp是一个比较简单但是有效的反沙箱机制。

休眠之后,程序调用了当前类GODofBeauty的Capitoline方法

Capitoline方法看起来好像又是一个操作资源的函数:

资源读取成功之后,会赋值给ughHbnBnaWtlYkx
Bitmap ughHbnBnaWtlYkx = GODofBeauty.Capitoline(ugz1, projname);

然后ughHbnBnaWtlYkx会作为参数传递到Vulcan中:

而Vulcan执行完成的返回值,会作为参数传递到下面的Greek函数中。
Greek函数也是一个解密函数:

所以这里就是读取一个资源,然后解密两次。赋值给array

然后array会作为Assembly实例化的参数:

然后assembly经过转换,通过methdInfo.Invoke的方法进行调用。

然后这个程序看起来就执行完毕了。我们直接来调试一下这个dll。

首先我们在
Activator.CreateInstance(type, TetrisDrop.Ng);
这行代码处 F11进入系统空间

继续F11

然后F10往下走,当某一行执行完之后,程序跑飞了,就在这一行设置断点,然后重新跑过来。然后F11进去

进来之后继续刚才的方法,找到.Invoke或者CreateInstance函数,F11

进来之后继续重复刚才的操作,找到InvokeMethod方法
然后F11即可进入到dll空间:

然后就是正常的调试流程。

之前已经静态分析过,Capitoline函数用于读取资源,Greek和Vulcan函数用于解密。
这里读取的资源值就是”beerparlourbillingsystem.Resources”

直接F10略过中间解密资源的操作,这里可以看到资源已经成功解密

解密的资源又是一个PE,我们保存为dump_file2

dump出来的这个文件等下会通过Invoke加载,我们先静态分析一下dumpfile2

 

0x06 dump_file2分析

dump_file2的程序名为Jupiter.dll

这个样本的混淆就比较严重了。

但是大概看一下,可以发现这就是普通的混淆,直接用de4dot应该就可以去混淆。

去混淆之后大概结构如下:

先来分析一下这个都是什么。

经过分析,这个dll很明显是一个商业马,有很熟悉的窃取用户信息的操作以及后面的沙箱检测操作。

主要功能应该在这个去混淆之后名为Class3的类中。

这里Class3后面有个Token是 0x02000005,这是C#的一种结构,我们是可以点击注释信息中的TOKEN的:

点击过来可以查看到dnspy解析出来的一些结构信息

根据这个结构,我们可以顺利的找到去混淆之前的Class3类,这个类的token应该也是0x02000005

然后我们继续看解混淆之后的代码。发现在Class2中又有读取资源和解密的操作
代码和dump_file1中的操作方式很像,但是不同的是,这里读取的是当前文件的资源,而dump_file1中读取的是源文件的资源。

Jupiter.dll的资源列表如下

这意味着,这里其实还有可能不是真正的核心马,还只是一个loader。

分别分析一下这几个函数,找找调用位置。

跳转过来发现,程序还是首先通过smethod读取资源,然后分别通过smethod2和smethod1进行解密。

解密之后会将数据传递给byte_0。

查看一下byte_0的调用如下:

byte_0又会传递过来给Assembly,然后通过Invoke,和上一层的loader几乎一模一样。

所以我们现在知道了,我可以直接在这里设置断点拿到byte_0的数据,也可以在smethod1函数return的地方设置断点,拿到最后的这个文件。

所以我们现在需要做的是:去混淆的文件中找到smethod_11方法或者找到smethod1的return,然后在我们正在调试的样本中找到对应的位置设置断点。

我们注意到,smethod11是在class3中的,而class3是一个大类,有很多的函数。所以我们要想从混淆的代码中找到smethod11是非常困难的。

smethod0 在Class2中,该类只有三个方法,相对来说会容易很多

所以可以尝试去Class2中找一下对应的断点位置。

Class2的Token是02000004,我们去找到这个token的类

类中最上面的这个位置,应该对应的就是smethod0了

然后下面还有两个函数,我们就分别尝试在这几个return都去设置断点。

所以现在,我们需要回到调试窗口中,从dump_file1跳转到dump_file2继续执行。

还是跟我们从原始样本进入到dump_file1的方法一样,先F11进Invoke函数
然后一步一步进来 最后停留在了dump_file2的Class3中

然后我们分别去找到对应的地方,设置好断点:

然后F5跑起来。这个时候会发现,程序并没有命中我们的断点,而是直接运行结束了。

所以猜想是不是程序进来的时候,有地方实例化了这个类,调用了实例方法。简单来说就是这几个函数已经在我们调试过来之前执行完了。

所以这个时候 直接调试原样本,直接F5 不要中断,应该会跑过来的。
(中间有一个sleep,如果之前没patch的话,估计要等1分钟左右

成功命中第一个断点:

F10返回回来,此时这个资源还是加密状态:

然后继续运行,命中第二个断点:

这里可以看到,第一次解密已经完成。

然后继续运行,命中第三个断点,此时PE已经解密出来。

dump这个PE保存为dump_file3
这个dump_file3是一个exe,我们可以直接调试了。虽然这个也有轻微的混淆,但是这里的混淆还能接受,可以继续分析。

 

0x07 最终木马概述

最终的这个木马就是一个功能完整的商业窃密马了。这里就不详细分析其功能,概要分析一下。

程序首先会调用nq函数,这个函数其实并没有多大意义

然后程序通过两个永真循环,然后以switch case的方式去执行某些特定的功能:

后面会设置一个名为uge的计时器。

然后随便跑一下行为,抓个包。

这里可以看到程序在跟us2.smtp.mailhostbox.com三次握手建立连接,目标IP为:208.91.199.223
目标端口是587

然后成功拿到目标服务器的账号密码:

然后尝试登陆邮箱:

这里账号密码都是经过BASE64编码的,我们直接解码然后尝试登陆就行。
成功登陆,获取到邮件信息

通过查看邮件信息,我们可以确认该马是个窃密马,且不带屏幕截图功能。
该马的主要目的是窃取用户主机上存储的密码信息。
代码结构不像是AgentTesla,但是风格却很相似。
根据情况来看,这个马不是AgentTesla就是HawkEye。极大极大的概率是AgentTesla

(完)