Cyril FrançoisSamir Bousseaden

剖析 REMCOS RAT:对 2024 年广泛传播的恶意软件的深入分析,第二部分

第二部分:深入了解 REMCOS 的录制功能、启动和 C2 通信

阅读时长 8 分钟恶意软件分析
Dissecting REMCOS RAT: An in-depth analysis of a widespread 2024 malware, Part Two

在本系列关于 REMCOS 植入的上一篇文章中,我们分享了关于执行、持久化和防御规避机制的信息。继续本系列,我们将介绍其执行流程的后半部分,您将了解更多关于 REMCOS 录制功能及其与 C2 的通信。

启动看门狗

如果启用了 enable_watchdog_flag(索引 0x32),REMCOS 将激活其看门狗功能。

此功能涉及恶意软件启动一个新进程,将自身注入其中,并监视主进程。看门狗的目标是在主进程终止时重新启动它。如果看门狗终止,主进程也可以重新启动它。

看门狗注入的目标二进制文件是从硬编码列表中选择的,选择第一个进程创建和注入成功的二进制文件

  • svchost.exe
  • rmclient.exe
  • fsutil.exe

在此示例中,看门狗进程为 svchost.exe

在启动看门狗进程之前,会创建注册表值 HKCU/SOFTWARE/{MUTEX}/WD,其中包含主进程 PID。

一旦 REMCOS 在看门狗进程中运行,它会通过验证恶意软件注册表项中是否存在 WD 值来采用“特殊”执行路径。如果存在,则删除该值,并调用监视过程函数。

值得注意的是,看门狗进程具有特殊的互斥锁,以将其与主进程互斥锁区分开来。此互斥锁字符串派生自配置(索引 0xE),并附加 -W

当主进程终止时,看门狗会检测到它,并使用 ShellExecuteW API 重新启动它,该 API 的恶意软件二进制文件路径是从 HKCU/SOFTWARE/{mutex}/exepath 注册表项中检索的。

启动录制线程

键盘记录线程

离线键盘记录器有两种操作模式

  1. 记录所有内容
  2. 当特定窗口位于前台时启用键盘记录

当配置中的 keylogger_mode(索引 0xF)字段设置为 1 或 2 时,REMCOS 会激活其“离线键盘记录器”功能。

键盘记录是使用具有 WH_KEYBOARD_LL 常量的 SetWindowsHookExA API 完成的。

存储键盘记录数据的文件是使用以下配置字段构建的

  • keylogger_root_directory(索引 0x31
  • keylogger_parent_directory(索引 0x10
  • keylogger_filename(索引 0x11

键盘记录器文件路径为 {keylogger_root_directory}/{keylogger_parent_directory}/{keylogger_filename}。在这种情况下,它将是 %APPDATA%/keylogger.dat

可以通过启用配置中的 enable_keylogger_file_encryption_flag(索引 0x12)标志来加密键盘记录器文件。它将使用 RC4 算法和配置密钥进行加密。

还可以通过启用配置中的 enable_keylogger_file_hiding_flag(索引 0x13)标志来使该文件超级隐藏。

当使用第二种键盘记录模式时,您需要使用将在当前前台窗口标题中每 5 秒搜索的字符串设置 keylogger_specific_window_names(索引 0x2A)字段。

匹配后,键盘记录开始。随后,每秒检查当前前台窗口,如果标题不再包含指定的字符串,则停止键盘记录器。

屏幕录制线程

当配置中启用了 enable_screenshot_flag(索引 0x14)时,REMCOS 将激活其屏幕录制功能。

要截取屏幕截图,REMCOS 利用 CreateCompatibleBitmapBitBlt Windows API。如果启用了 enable_screenshot_mouse_drawing_flag(索引 0x35)标志,则还会使用 GetCursorInfoGetIconInfoDrawIcon API 在位图上绘制鼠标。

存储屏幕截图的文件夹路径是使用以下配置构建的

  • screenshot_parent_directory(索引 0x19
  • screenshot_folder(索引 0x1A

最终路径为 {screenshot_parent_directory}/{screenshot_folder}

REMCOS 利用 screenshot_interval_in_minutes(索引 0x15)字段,每 X 分钟捕获一次屏幕截图,并使用以下格式字符串将其保存到磁盘:time_%04i%02i%02i_%02i%02i%02i

与键盘记录数据类似,当启用 enable_screenshot_encryption_flag(索引 0x1B)时,屏幕截图将使用 RC4 加密算法和配置密钥加密保存。

在顶部,REMCOS 的屏幕录制功能与其键盘记录功能类似,都具有“特定窗口”功能。当 enable_screenshot_specific_window_names_flag (索引 0x16) 被设置时,会启动第二个屏幕录制线程。

这次,它会利用 screenshot_specific_window_names (索引 0x17) 字符串列表,当活动窗口的标题包含指定字符串之一时,就会捕获屏幕截图。屏幕截图会按照 screenshot_specific_window_names_interval_in_seconds (索引 0x18) 字段指定的 X 秒间隔进行拍摄。

在这种情况下,屏幕截图会使用不同的格式字符串保存在磁盘上:wnd_%04i%02i%02i_%02i%02i%02i。下面是一个示例,使用 ["notepad"] 作为特定窗口名称列表,并将记事本进程窗口设置为活动窗口。

音频录制线程

enable_audio_recording_flag (索引 0x23) 被启用时,REMCOS 将启动其音频录制功能。

录制是使用 Windows Wave* API 进行的。录制时长由 audio_recording_duration_in_minutes (0x24) 配置字段以分钟为单位指定。

录制 X 分钟后,录制文件会被保存,并开始新的录制。REMCOS 使用以下配置字段来构建录制文件夹路径

  • audio_record_parent_directory (索引 0x25)
  • audio_record_folder (索引 0x26)

最终路径为 {audio_record_parent_directory}/{audio_record_folder}。在本例中,它将是 C:\MicRecords。录音会使用以下格式保存到磁盘:%Y-%m-%d %H.%M.wav

与 C2 的通信

初始化后,REMCOS 会启动与其 C2 的通信。它会尝试连接其 c2_list (索引 0x0) 中的每个域,直到其中一个响应为止。

根据之前的研究,如果为特定的 C2 启用了 TLS,则可以使用 TLS 加密通信。在这种情况下,TLS 引擎将使用 tls_raw_certificate (索引 0x36)、 tls_key (索引 0x37) 和 tls_raw_peer_certificate (索引 0x38) 配置字段来建立 TLS 隧道。

需要注意的是,在这种情况下,只能为多个启用了 TLS 的 C2 域提供一个对等证书。因此,有可能使用相同的证书识别其他 C2。

连接后,我们收到了第一个数据包

正如 Fortinet 深入描述的那样,协议没有改变,所有数据包都遵循相同的结构

  • (橙色)magic_number: \x24\x04\xff\x00
  • (红色)data_size: \x40\x03\x00\x00
  • (绿色)command_id (数字): \0x4b\x00\x00\x00
  • (蓝色)数据字段由 |\x1e\x1e\1f| 分隔

在收到来自恶意软件的第一个数据包后,我们可以使用以下函数发送我们自己的命令。

MAGIC = 0xFF0424
SEPARATOR = b"\x1e\x1e\x1f|"


def build_command_packet(command_id: int, command_data: bytes) -> bytes:
	return build_packet(command_id.to_bytes(4, byteorder="little") + command_data)


def build_packet(data: bytes) -> bytes:
	packet = MAGIC.to_bytes(4, byteorder="little")
	packet += len(data).to_bytes(4, byteorder="little")
	packet += data
	return packet

在这里,我们将使用命令 0x94 更改记事本窗口的标题,并将窗口句柄 (329064) 和我们选择的文本作为参数传递。

def main() -> None:
	server_0 = nclib.TCPServer(("192.168.204.1", 8080))

	for client in server_0:
    	print(client.recv_all(5))

    	client.send(build_command_packet(
            			0x94,
            			b"329064" + SEPARATOR + "AM_I_A_JOKE_TO_YOU?".encode("utf-16-le")))

这是第二篇文章的结尾。第三部分将介绍 REMCOS 的配置及其 C2 命令。