BokBot恶意软件由LUNAR SPIDER恶意组织开发和运营,在2017年首次出现,CrowdStrike的Falcon Overwatch和Falcon Intelligenc团队对被感染主机进行了分析。最近,由于借助MUMMY SPIDER恶意组织的Emotet恶意软件进行分发活动,BokBot的感染数量又有所增加。
BokBot恶意软件具有强大的功能,分为命令和控制、模块化两类,具体功能包括:执行进程、注册表编辑、写入文件系统、登录、混淆、防篡改、窃取凭据、拦截代理、通过VNC进行远程控制。
此外,已经发现BokBot可以从其他恶意软件中下载并执行二进制代码,例如Azorult infostealer。
本文将深入探讨BokBot主要模块的技术细节。
最初,BokBot被加密包装。在最终解压缩BokBot二进制文件并将其注入到svchost.exe之前,加密器经历了几个阶段。以下是这些不同阶段的简单概述:
· 第一阶段(加密器):解码第二阶段的内容并执行。
· 第二阶段(加密器):解码ShellCode并执行。
· 第三阶段(ShellCode):对基础进程映像进行Hollow,解码核心进程注入PE,使用核心进程注入PE覆盖基本进程映像。
· 第四阶段(进程注入):执行进程注入代码,启动svchost.exe子进程,将BokBot作为无头部的PE映像注入子进程。
我们使用CrowdStrike Falcon平台,主要可以分析第四阶段的过程。因此,本文的重点是BokBot注入子进程的这一过程,恶意软件作者在这里采用了一种独特的方法。
2.1 进程注入
为了绕过反病毒检测,从而可以对进程进行Hollowing,BokBot对几个Windows API函数进行了挂钩,并执行挂钩代码,然后删除钩子。
2.1.1 模拟进程Hollowing过程
为了模拟这一过程,ZwCreateUserProcess例程将被首先挂钩。BokBot调用ZwProtectVirtualMemory,将例程的权限修改为PAGE_READWRITE。接下来,前五个操作码(字节)被替换为JMP指令的操作码。在恢复权限后,调用CreateProcessA。
在调用CreateProcessA后,函数调用链将会走到调用ZwCreateUserProcess的地方,随后调用挂钩代码,如上图所示。
挂钩代码将通过从ZwCreateUserprocess例程中删除挂钩,来完成子进程的创建,然后调用没有挂钩的ZwCreateUserProcess过程。这样一来,将会创建子进程,但在CreateProcessInternal返回之前不会执行。其余的挂钩例程将解码,并将嵌入的BokBot二进制文件注入svchost.exe的子进程。
2.1.2 代码注入
在注入代码之前,BokBot PE首先被解压缩,并被加载到本地进程内存中。加载后,以下Windows过程用于分配和写入svchost子进程:
ZwAllocateVirtualMemory
ZwWriteVirtualMemory
ZwProtectVirtualMemory
将主BokBot模块写入子进程后,将开始执行BokBot代码。
2.1.3 代码执行
BokBot采用了一种新颖的技术,来实现代码在子进程内部的执行。使用与之前相同的API,Dropper将挂钩子进程中的RtlExitUserProcess。由于svchost.exe是在没有参数的情况下启动的,因此会立即终止。当进程尝试退出时,它将调用挂钩的RtlExitUserProcess,从而执行BokBot Payload。
在CreateProcessInternalW恢复执行之前,针对挂钩的例程,还有一个任务要完成。
2.1.4 注入上下文数据结构
将BokBot Payload注入子进程后,要将上下文数据结构写入子进程。下面的上下文中,包含所需的所有数据,可以确保BokBot的主模块在执行过程中不出现问题。
1、Windows过程地址:
ntdll.ZwAllocateVirtualMemory
ntdll.ZwWriteVirtualMemory
ntdll.ZwProtectVirtualMemory
ntdll.ZwWaitForSingleObject
ntdll.LdrLoadDll
ntdll.LdrGetProcedureAddress
ntdll.RtlExitUserProcess
ntdll.ZwCreateUserProcess
ntdll.RtlDecompressBuffer
ntdll.ZwFlushInstructionCache
2、加载Payload的地址
3、Dropper二进制文件的路径
4、C&C URL
5、项目ID
在Dropper进程的整个生命周期中,将会收集上述数据。此外,在下载和执行模块时,类似的结构将会被写入到BokBot的子进程。
在注入后,CreatProcessInternalW会恢复,并且Dropper进程将退出。BokBot的主模块将进入到初始化阶段。
在执行主循环和C&C通信之前,BokBot首先经历了几个初始化步骤,为C&C通信做好准备。初始化的具体步骤如下:
1、删除RtlExitUserProcess的挂钩
2、创建内存映射文件,以存储日志记录数据
3、以登录用户身份,执行BokBot(如果当前进程是以System运行)
4、关闭错误告警
5、收集系统信息:Windows版本信息、用户SID、域成员信息
6、生成唯一ID
7、防止多次执行
8、在主机上安装BokBot
9、将现有下载模块注入到子进程中
在下面,我们将详细介绍其中的一些步骤。
3.1 关闭错误告警
为了防止出现错误提示,从而使受害者得知具体问题,BokBot将进程的错误模式设置为0x8007,具体对应如下:
SEM_FAILCRITICALERRORS
SEM_NOALIGNMENTFAULTEXCEPT
SEM_NOGPFAULTERRORBOX
SEM_NOOPENFILEERRORBOX
这样一来,将会禁用进程崩溃时产生的大多数错误通知。
3.2 生成唯一ID
在早期进程执行期间,生成了几个唯一的ID,BokBot将使用这些ID。这些值将被传递给C&C服务器,并会作为RC4加密算法的密钥,同时也传递给子进程。
3.2.1 项目ID
除了将主BokBot模块注入svchost之外,Dropper还会将一大块二进制数据注入,为BokBot执行提供上下文,其中也包括项目ID。这些唯一的项目ID值,似乎是用于识别与分发活动对应的感染。项目ID是一个4字节值。
3.2.2 Bot ID
针对受感染主机上每个用户的特定实例,其Bot ID是唯一的。该值将用作加密密钥,以及生成BokBot各种唯一值所需的种子。例如,为文件及事件名称生成的伪随机字符串,我们将在后续章节中进一步讨论。
Bot ID会以下面两种方式中的一种来生成:
1、帐户名称的安全ID(Security ID)
2、文件时间格式的系统时间
由于这两个值都是64位,所以无论是用哪种方法,该值都会被分为两个32位块,并且会被异或。
3.2.3 ID哈希值
除了Bot ID之外,还会生成一个简单的哈希值,用来验证Bot ID和项目ID的有效性。该哈希值使用Bot ID和项目ID来生成,具体方法如下:
hash = (( BotID / 0x100001E) & 0xFF) + ((BotID/0x10000) & 0xFF)
hash = (ProjectID[3] + hash[0]) & 0xff
hash = (BotID [0] + hash) & 0xff
hash = (ProjectID[0] + hash[0]) & 0xff
hash = (ProjectID[1] + hash[0]) & 0xff
hash = (ProjectID[2] + hash[0]) & 0xff
该值将与项目ID、Bot ID一起作为C&C URL参数的一部分来传递。如果该请求无效,受感染的主机将不会收到来自C&C的任何指令。
Bokbot包含编码后的C&C主机名列表,这些主机名会作为Dropper注入的上下文数据结构中的一部分。该结构中的C&C列表,也会使用由上下文提供的密钥进行解码,然后使用rdtsc指令生成的新密钥进行重新编码,最后存储为指针数组。
使用Bot ID生成唯一的全局命名事件。通过调用GetLastError,继续成功调用CreateEvent。如果恶意软件已经在执行,那么最后一个产生的错误将会是ERROR_ALREADY_EXISTS,随后进程将退出。
在安装过程中,将BokBot Dropper二进制文件写入安装目录,并创建计划任务,以保证其持久性。
安装目录将在以下根目录中创建:C:\ProgramData
安装目录的名称是唯一的,使用Bot ID生成。在创建目录后,使用Bot ID作为种子对原始的Dropper文件进行重命名,并写入目录。由于Bot ID基于系统信息,因此将其用作种子就可以确保恶意软件始终在特定主机上生成相同的安装路径和文件名。
在生成安装目录名称后,BokBot需要为将要写入该目录的BokBot二进制文件生成一个文件名。下面的Python代码重现了BokBot用于生成文件名的算法,以及其他各种字符串。
脚本中的str_id值是一个硬编码的整数,与Bot ID一起使用,可以生成一致的字符串。例如,使用Bot ID 0x2C6205B3以及str_id 2,总是会生成字符串ayxhmenpqgof,但假如将str_id换为6,生成的字符串就会变为bwjncm。
以下是安装路径的示例:
C:\ProgramData\{P6A23L1G-A21G-2389-90A1-95812L5X9AB8}\ruizlfjkex.exe
在这里,将会创建计划任务,以在Windows登录时执行。任务名称的生成方式与安装目录相同。
· 任务名称:{Q6B23L1U-A32L-2389-90A1-95812L5X9AB8}
· 触发:登录时
· 动作:启动一个程序
· 详细信息:BokBot Dropper路径
BokBot通过HTTPS请求与C&C服务器进行通信,并通过URL参数和POST参数,将各种值传递给服务器。除了服务器使用的SSL/TLS之外,URL请求数据没有进行加密或模糊处理。
下面详细介绍了所有请求所必须的主要参数、一些其他的可选参数,以及Bot的注册过程。
7.1 主要C&C请求和响应参数
每个请求或响应都会将这些参数发送到服务器。这些内容将会为C&C提供有关请求/响应类型以及唯一标识受感染计算机的信息:
/in.php?g=2&c=3592L387P92A771N77&p=0&r=102
对于这些参数,更详细的描述请参见下表:
URL路径通常会在不同版本之间有所不同。例如,版本100-102使用的是/data100.php,而不是/in.php。
7.2 附加C&C请求和响应参数
在BokBot中,包含一个连续循环的通信线程,该循环直至进程退出,持续从C&C服务器检索指令。除了上面已经描述的参数之外,请求中还包含一些附加参数。当Bot向C&C服务器返回命令的执行结果时(例如:上传屏幕截图时),不会发送这些参数。
下面URL参数中,展示了与C&C初始连接的示例:
/in.php?g=2&c=42454B233B&p=0&r=104&i=0&n=0&o=0&k=3073&a=2&l=
在这个示例中,没有Web注入,没有C&C URL,也没有下载任何模块,因此其中的一些重要参数值都为0或空。在这里,生成了初始时间戳,版本号为静态的。
注册请求会与每个发送到C&C的标准C&C URL参数相组合。在初始请求之后,C&C服务器将命令发回受害者主机,并发送信号通知其下载Web注入、更新的C&C主机名、可执行模块或其他要执行的任务。
初始注册URL中,包含与系统信息相关的参数。以下字符串是一个示例:
在下表中,描述了注册URI参数:
下面是注册请求(红色)和C&C(蓝色)响应的示例,其中包含发送给受感染主机的命令。
8.1 C&C命令
在本节中,将分析C&C发出的命令请求。来自C&C的每个命令,都采用以下格式:
当前版本的BokBot中,提供了以下命令:
需要注意的是,这些命令的ID值可能会在不同版本之间更改。如此列表所示,BokBot为运营商提供了各种与受感染机器进行交互的选项。
8.2 URL下载命令处理程序
其中,许多命令都会触发命令处理程序函数,该函数需要与C&C URL或服务器请求参数中指定的其他URL进行通信。如果请求指定,那么从目标URL下载的数据就会被写入到DAT。无论下载的数据是否被写入到DAT文件,它都将由以下C&C命令之一的回调函数处理:
1、启动一个新的可执行模块,重启当前可执行模块。
2、更新Web注入(任一命令)
3、更新配置
4、更新BokBot
5、写入文件
6、下载并执行二进制文件
使用C&C URL主机名的命令,将会发送d URL参数,示例如下:
该值通常设置为0,要下载的文件将由g参数指定。
从C&C接收的所有需要在重新启动计算机后保留的数据,都会在受感染的计算机上写入到DAT文件中。这些文件包括:Web注入配置、C&C配置和外部模块。
使用Bot ID作为密钥,主模块和子模块将根据需要,对每个文件进行加密和解密。每个模块都有一个唯一的标签。
9.1 生成唯一标签
BokBot会为注入的进程、下载的模块和下载的DAT文件分配唯一的标签。这些标签是执行BokBot进程以识别外部进程资源的一种便捷方法。标签的生成非常简单:
· 18 – Web注入配置文件,在二进制文件中静态定义。
· 19 – 报告配置文件,在二进制文件中静态定义。
· 20 – C&C配置文件,在二进制文件中静态定义。
· 33-46 – 下载的模块将注入子进程。需要以增量的方式分配,不一定是对模块所做的唯一标记。
在对BokBot的分析过程中,这些值将会定期出现,其中也包括生成唯一文件名的值,会在后面详细描述。
如前所述,DAT文件是根据C&C发送的命令进行下载。一旦从C&C接收到命令,就会调用特定于此命令的处理程序,来处理该请求。作为响应,受感染的机器将会通知C&C它已经准备好接收RC4加密后的Blob。下图说明了下载配置文件和模块的命令的过程。
一个8字节的RC4命令将会被添加到数据缓冲区。在将Blob写入文件之前,BokBot会对文件进行解密,然后使用基于Bot ID的新RC4密钥,对其进行重新加密。
10.1 写入文件
BokBot在C:\ProgramData下创建一个新目录来存储DAT文件。使用前面描述的字符串生成算法,生成目录名称。使用唯一标记值,生成DAT文件名。该唯一标记值也是通过字符串生成算法(取决于Bot ID)运行,该算法会返回DAT文件的唯一文件名。
上表中引用了我们测试过程中发现的所有DAT文件。在我们的测试环境中,安装目录是C:\ProgramData\yyyyyyyyiu\。
10.2 可执行模块
BokBot有几个可执行模块,可以下载并注入svchost.exe子进程。一旦使用RC4解码相关的DAT文件,就不需要对可执行模块DAT文件进行额外的解码或解压缩。可执行模块头包含识别模块所需的信息:
文件的其余部分,包含加载和执行模块所需的数据,包括PE文件的各个部分,以及自定义PE头。
模块的注入与执行过程,使用类似于Dropper的技术来注入可执行模块,并且去掉了ZwCreateUserProcess的挂钩,并暂停子进程启动(CREATE_SUSPENDED)。通过添加RtlExitUserProcess挂钩,它更接近于传统的进程迁移。
1、PE图像加载
由于没有标准的PE头部,所以DAT文件中必须包含所有相关信息(虚拟大小、重定位等),以便将这个二进制文件正确映射到子进程。该数据是DAT文件标头的一部分。BokBot在将二进制文件注入子进程之前,现在本地进程内存中构建二进制文件。
2、注入
注入过程,使用与Dropper相同的API,也就是ZwAllocateVirtualMemory、ZwWriteVirtualMemory和ZwProtectVirtualMemory。在注入后,使用ResumeThread恢复进程。
3、执行上下文注入
再一次,在执行之前,先将执行上下文结构写入子进程。这里包含的一些信息包括:Bot ID、项目ID、C&C主机名、URL路径格式字符串。
这样一来,就可以保持父进程与子进程之间的一致性,不需要生成新的唯一标识符,所有加密密钥都是相同的:相同的主机名,甚至也有相同的URL路径。父进程和子进程之间的一致性对于使用进程间通信(IPC)在两者之间发送的消息是非常必要的。
将模块注入子进程后,解密的DAT文件的前四个字节将添加到一个数组中,BokBot使用该数组来标识当前正在执行的模块。
10.3 数据文件
其他DAT文件包含与C&C通信或Web注入相关的所必须的数据。从本质上讲,这些文件提供了主要BokBot进程和可执行模块完成其工作所需的任何其他数据。
配置文件包含BokBot主模块与C&C保持通信所需的所有数据。一旦使用特定于进程的RC4密钥解密文件,就不再需要额外的解压缩或者解密过程。
每个配置文件都带有一个数字签名块,用于验证C&C主机名数据的完整性。根据混淆部分中说明的签名验证方法,来对签名进行验证。以下是C&C配置的示例,签名块为红色:
其中,有多个Web注入文件。一个包含所有目标URL和主机名数据,另一个包含正则表达式模式以及需要注入的代码。这些文件都事先经过RC4加密和压缩。
这些文件并非由主BokBot二进制文件解析,而是由拦截代理模块对其进行解析。其过程是,验证Zeus File Magic,分配缓冲区,然后解压缩文件。
BokBot使用内存映射文件和事件,与包含注入模块的所有子进程进行通信。通过使用CreateEvent、OpenEvent和OpenFileMapping利用命名事件的过程,BokBot主模块能够为这些子进程提供附加信息。
11.1 共享模块日志
这些模块会写入到同一个共享内存映射文件中,并使用父进程和子进程之间的共享名称创建内存映射文件。可以生成此名称的每个进程,都可以使用它来打开内存映射文件,并将数据写入共享模块日志。更多细节我们会在下一节中介绍,所编写的具体数据会在下面单独的模块描述中进行分析。主模块负责清除日志,并将数据发送到C&C服务器。
11.2 特定模块的通信
BokBot的主模块通常需要向包含注入模块代码的子进程发出命令。这些命令可以触发模块中特定命令的更新,或者指示模块执行特定功能(例如:从Outlook收集数据)。下图该输了这一过程,我们也将在后续章节中进一步说明。
11.2.1 事件名称生成
为了使BokBot主模块和子进程、事件之间成功通信,需要生成唯一的名称,并且必须在所有进程中保持一致。其具体方法如下:
父进程和子进程将使用这些事件来交换数据。
11.2.2 BokBot主模块
此过程能够与注入模块的所有子项进行通信。这些通信都围绕C&C发出的命令。一旦有需要通知可执行模块子进程的命令启动,就会打开一个命名的Q事件,以确保子进程已经准备好接收数据。如果Q事件不存在,那么就表示尚未启动子进程。BokBot将目标模块注入紫禁城中,并循环检查,以查看是否可以打开该事件。
成功打开Q事件后,BokBot将创建一个新的命名R事件,创建一个内存映射文件(名称为M event),将数据写入文件,并发出打开Q事件的信号,等待子进程的响应。子进程清除R事件后,内存映射文件将被取消映射,所有句柄都将关闭。
11.2.3 BokBot可执行模块
初始化之后,子进程将创建一个命名的Q事件,并等待父进程发出信号。在发出信号后,将打开命名的R事件,并处理内存映射文件中的数据。
BokBot的主模块会将一些上下文信息写入注入的模块,并告诉它执行特定的操作。这些操作会根据接收数据的模块而有所更改。以下命令在模块之间是一致的,但执行的操作可能会有所不同:
· 0xFF00 – 使用0x1122代码处理退出
· 0xFF01 – 检查Web注入或无操作
· 0xFF02 – 更新C&C主机名
除了命令之外,还会基于命令,告知注入模块要完成的指令,以处理与命令相关联的相关数据。
在父进程分配的任务完成后,将取消映射内存映射文件,发出R事件信号,并关闭所有其他打开的事件。
BokBot使用几种方法进行混淆以阻碍分析过程,分别是:字符串混淆、从服务器发出加密的DAT文件、签名验证和多态性。
12.1 字符串混淆
为了使分析更加困难,这里使用了移位密钥算法对重要字符串进行XOR编码。所有编码的字符串都具有以下结构:
· 0x0 – DWORD – 初始XOR密钥
· 0x4 – WORD – 字符串大小
· 0x8 – char[] – 编码后字符串
解码字符串的算法如下(Python):
#
# Encoded string structure
#
String_struct = “”
xor_key = string_struct[:4]
string_length = string_struct[4:6]
ciphertext = string_strct[6:]
plaintext = “”
For idx in range(string_length)
xor_key = (((xor_key >> 0x3) | (xor_key << 0x1D)) + idx) & 0xFFFFFFFF
enc_byte = ciphertext[idx]
if (enc_byte ^ (xor_key & 0xFF) != 0:
plaintext += chr(enc_byte & (xor_key & 0xFF))
12.2 签名验证
签名验证会在以下两种情况下发生:C&C URL发生更新、BokBot二进制文件发生更新。在这两种情况下的验证过程完全一致,验证函数主要接收两部分内容:要验证的128字节签名,和要验证的数据。
首先,BokBot创建需要验证的数据的MD5哈希值。接下来,嵌入在执行二进制文件中的RSA公钥将通过CryptImportKey导入。生成哈希并导入密钥后,CryptVerifySignature将用于验证签名。这可能是为了防止某些第三方接管,或者以其他方式破坏僵尸网络。
12.3 多态性
每次安装BokBot时,在将其写入安装目录之前,都会使用垃圾数据修改二进制文件的.text部分,并更新其虚拟大小。同时,生成新的校验和(Checksum),并替换当前的校验和。
BokBot是一款功能强大的银行木马,可以为攻击者提供强大的功能。BokBot最独特之处之一是它与子模块通信的方法。我们后续还将对该木马进行持续分析,并将及时发布最新成果。
本文所涉及的全部样本哈希值如下: