针对使用Doppelgänging技术的SynAck勒索软件变种的分析

前言

2017年12月,在BlackHat会议上,新型代码注入技术Process Doppelgänging被首次发布( https://www.blackhat.com/docs/eu-17/materials/eu-17-Liberman-Lost-In-Transaction-Process-Doppelganging.pdf )。在会议的相关资料发布后,一些恶意软件的开发者已经开始借助这种复杂的技术来绕过当今流行的安全解决方案。
在2018年4月,我们首次发现了使用该绕过技术的勒索软件——SynAck。在这里需要指出的是,SynAck并不是一种新的勒索软件,它早在2017年9月就被发现,然而如今发现其变种中使用了Doppelgänging技术,这也引起了我们的关注。本文主要针对使用了Doppelgänging技术的新SynAck变种进行分析。

 

反分析与反检测技术

新型代码注入技术Process Doppelgänging

SynAck勒索软件借助该技术试图绕过当今流行的安全解决方案。该技术的主要目的是,使用NTFS文件系统事物(NTFS Transactions),从事务处理文件(Transacted File)中启动恶意进程,从而使该恶意进程看起来像是合法的进程。
使用Process Doppelgänging技术的部分代码如下:

二进制混淆

为了扰乱恶意软件分析者的分析工作,恶意软件开发人员通常会使用自定义的PE加壳器来保护木马可执行文件的原始代码。然而,大多数的加壳器都可以被安全人员轻松脱壳,还原出可供分析的原始的木马PE文件。
然而,SynAck并不是这样做的,该木马的可执行文件没有经过加壳。相反,该可执行文件在编译前就已经被彻底混淆。因此,如果要对SynAck进行逆向,其工作量要远远超过其他勒索软件。
特洛伊木马可执行文件的控制流非常复杂,大部分的调用都是间接进行,其目标地址是通过两个DWORD常量的算术运算计算而成。

所有WinAPI函数地址都是动态导入的,需要先对导出的系统DLL进行解析,然后再对函数名称进行CRC32哈希值计算。这一点并不是新的方法,分析过程也不复杂。但在SynAck中,开发者隐藏了检索API函数地址的过程和目标哈希值,从而加大分析的工作量。
接下来,我们来详细说明SynAck是如何调用WinAPI函数的。我们首先来看下面的反汇编代码:

该代码将位于403b13的DWORD与常数78f5ec4d相减,得到结果403ad0,即为调用过程的地址。

该过程将两个常量(N1 = ffffffff877bbca1和N2 = 2f399204)压入栈,并将执行传递给403680处的过程,该过程将计算N1 xor N2 = a8422ea5的结果。
计算出的该值,即是SynAck希望调用的API函数名称的散列值。过程403680接下来会通过解析系统DLL的导出表,计算每个函数名称的哈希值,并将其与值a8422ea5进行比较,从而找到该函数的地址。在找到这个API函数地址之后,SynAck会将执行传递给这个地址。
请注意,实际上进行的并不是上图中的简单调用(CALL),而是使用了PUSH + RET指令,这也可以从另一个方面让分析工作变得更复杂。在调用WinAPI函数时,SynAck的开发者使用了不同的指令组合,而没有使用CALL:

push reg
retn
jmp reg
mov [rsp-var], reg
jmp qword ptr [rsp-var]

反混淆过程

为了对抗恶意软件开发者设置的绊脚石,我们写了一个IDA Python脚本,该脚本可以自动分析代码、提取所有中间过程的地址、提取常量,同时还能够计算恶意软件希望导入的WinAPI函数的哈希值。
然后,我们计算出从Windows系统DLL导出的函数的哈希值,并将它们与SynAck所需的值进行匹配。下表中为我们所得到的结果,展示了各个哈希值所对应的API函数:

我们的脚本借助上述得到的列表,来保存IDA数据库中的注释,以指示出哪个API将会被木马调用。在去除混淆之后,我们得到了下面的代码。
下图为反汇编后的代码,其中加上了目标API函数名称的注释:

下图为借助Hex-Rays反编译后的代码,再次识别出了API函数的名称:

语言检查

在其执行的早期阶段,特洛伊木马会执行语言检查,以确定是否在特定国家或地区的计算机上运行。为了验证语言,该木马会查看计算机上安装的所有键盘布局是否与列表中的匹配,该列表是以硬编码的形式写入到恶意软件之中。一旦发现匹配,SynAck将会休眠300秒,然后调用ExitProcess来防止这些国家的用户文件被加密。
如果语言检查未通过,则停止木马程序的运行:

检查受感染计算机上键盘布局的过程:

目录名称验证

在进行语言检查之后,SynAck会对其可执行文件的目录进行检查,这一检查过程在如今流行的许多勒索软件中都是比较常见的。如果试图从“不正确的”目录运行木马,则该木马会自动退出。这是恶意软件开发者用来对抗自动沙盒分析所设计的机制。
与API导入一样,特洛伊木马并没有存储想要检查的字符串本身,而是存储了它们的哈希值。通过这种方式,恶意软件分析者就难以找出原始字符串。
SynAck中共包含9个哈希值,我们已经尝试出了其中的两个:

0x05f9053d == hash("output")
0x2cd2f8e2 == hash("plugins")

在这一寻找过程中,我们发现了很多哈希值碰撞的情况(有意义的字符串的哈希值与乱码字符串的哈希值相同的情况)。

 

加密方案

如同其他勒索软件一样,SynAck综合使用了对称加密算法和非对称加密算法。SynAck所使用的核心算法是混合ECIES方案( https://en.wikipedia.org/wiki/Integrated_Encryption_Scheme )。该算法由彼此交互的“构件块(Building Blocks)”组成:ENC(对称加密算法)、KDF(密钥导出函数)和MAC(消息认证码)。ECIES方案可以采用不同的构件模块来具体实现。为了计算出对称算法ENC的密钥,该方案采用了ECDH协议(在特定椭圆曲线上的Diffie-Hellman)。
木马开发者选择了以下方案来实现加密算法。
ENC:XOR;
KDF:PBKDF2-SHA1经过一轮运算;
MAC:HMAC-SHA1;
ECDH曲线:标准NIST椭圆曲线secp192r1。

ECIES-XOR-HMAC-SHA1

下面是在SynAck中使用的ECIES方案的示例。
输入:明文、input_public_key。
输出:密文、ecies_public_key、MAC。
1、该木马会生成一对非对称密钥:ecies_private_key和ecies_public_key。
2、使用生成的ecies_private_key和ecies_public_key,木马按照椭圆曲线上的Diffie-Hellman协议计算共享密钥:

ecies_shared_secret = ECDH(ecies_private_key, input_public_key)

3、使用PBKDF2-SHA1函数进行一轮运算后,木马会从ecies_shared_secret派生出两个字节数组key_enc和key_mac。其中,key_enc的大小与明文大小相同。
4、将key_enc与明文逐字节进行异或(XOR)操作。
5、使用key_mac作为密钥,通过HMAC-SHA1算法计算并获得密文的MAC(消息认证码)。

初始化

在第一步中,木马会生成一对私钥和公钥。私钥(session_private_key)是一个192位的随机数,公钥(session_public_key)是标准NIST椭圆曲线secp192r1上的一个点。
随后,木马会收集用户的特定信息,例如计算机名称、用户名、操作系统版本、特定感染ID、会话私钥和一些随机数据。收集完成后,使用随机生成的256位AES密钥对其进行加密,并将加密后的数据保存到encrypted_unique_data缓冲区。
为了加密AES密钥,特洛伊木马使用了ECIES-XOR-HMAC-SHA1函数(详见上面的步骤说明,以下简称为ECIES函数)。SynAck将AES密钥作为明文参数,会将硬编码的master_public_key(恶意软件开发人员的密钥)作为input_public_key。字段encrypted_aes_key包含函数返回的密文,public_key_n是ECIES的公钥,message_authentication_code则是MAC。
接下来,木马将会产生cipher_info结构。

struct cipher_info
{
uint8_t encrypted_unique_data[240];
uint8_t public_key_n[49];
uint8_t encrypted_aes_key[44];
uint8_t message_authentication_code[20];
};

如下图所示:

这些数据会使用Base64编码,并写入到勒索提示中。

正如我们所见,恶意软件发布者要求被感染用户在提供的信息中包含这个编码文本。

文件加密

AES-256-ECB算法使用随机生成的密钥对每个文件的内容进行加密。 加密后,木马会生成一个包含信息的结构,其中包含加密标签0xA4EF5C91、使用的AES密钥、加密块大小和原始文件名等信息。该结构具体如下:

struct encryption_info
{
uint32_t label = 0xA4EF5C91;
uint8_t aes_key[32];
uint32_t encrypted_chunk_size;
uint32_t reserved;
uint8_t original_name_buffer[522];
};

然后,Trojan调用ECIES函数,并将crypt_info结构作为明文,将此前生成的session_public_key作为input_public_key。该函数返回的结果会保存到我们称为file_service_structure的结构中。 字段encrypted_file_info包含函数返回的密文,ecc_file_key_public是ECIES公钥,message_authentication_code则是MAC。

struct file_service_structure
{
uint8_t ecc_file_key_public[49];
encryption_info encrypted_file_info;
uint8_t message_authentication_code[20];
};

该结构将写入到加密文件的末尾,加密文件的结构如下:

struct encrypted_file
{
uint8_t encrypted_data[file_size - file_size % AES_BLOCK_SIZE];
uint8_t original_trailer[file_size % AES_BLOCK_SIZE];
uint64_t encryption_label = 0x65CE3D204A93A12F;
uint32_t infection_id;
uint32_t service_structure_size;
file_service_structure service_info;
};

加密文件的结构如下图所示:

在加密后,文件将会具有随机生成的扩展名。

 

其他特性

终止进程和服务

在文件加密之前,SynAck会检查所有正在运行的进程和服务,并根据两个硬编码写入的哈希值列表(包含数百个项目)检查其名称的哈希值。如果发现匹配,木马将会尝试使用TerminateProcess API函数终止进程,或使用带有参数SERVICE_CONTROL_STOP的ControlService停止服务。
为了找出该木马列表中包含的进程和服务,我们从木马中获得了哈希值,并得到了部分结果,如下所示:

正如我们所看到的,SynAck会试图终止与虚拟机、办公应用程序、脚本解释器、数据库应用程序、备份系统、游戏应用程序等相关的程序。其目的在于防止在自身访问某些有价值文件时,由于其他进程正在访问而导致的文件被占用。

清除事件日志

为了阻止安全研究人员对受感染机器进行分析,SynAck会清除系统存储的事件日志。为此,它使用了两种方法。
针对Vista之前的Windows版本,它会列举注册表项SYSTEMCurrentControlSetServicesEventLog,并使用OpenEventLog或ClearEventLog API函数。 对于Vista及以上的Windows版本,它使用来自EvtOpenChannelEnum/EvtNextChannelPath/EvtClearLog和Wevtapi.dll的函数实现对事件日志的清除。

在登录屏幕显示勒索提示

SynAck还能够在Windows登录屏幕中显示自定义文本,具体是通过修改注册表中的LegalNoticeCaption和LegalNoticeText键来实现的。这样一来,在用户登录其帐户之前,Windows会显示来自勒索软件作者的消息。

 

攻击目标

截至目前,我们只在美国、科威特、德国和伊朗发现了几起病毒攻击事件。在对上述攻击事件的分析中,我们认定该恶意软件属于勒索软件。

 

检测

Trojan-Ransom.Win32.Agent.abwa
Trojan-Ransom.Win32.Agent.abwb
PDM:Trojan.Win32.Generic

 

IoCs

0x6F772EB660BC05FC26DF86C98CA49ABC
0x911D5905CBE1DD462F171B7167CD15B9

 

(完)