该木马在分析之前已经在受害者机器上运行, 攻击者执行了一系列脚本和PowerShell命令, 使该恶意软件在Windows进程中运行。在分析溯源过程中, 没有找到恶意软件的可执行文件, 但成功获取了运行中恶意软件进程的内存转储文件以及整台被感染机器的完整内存转储文件, 转储的文件大小有33GB。可以通过扫描该文件来构建一个本地测试环境, 用于分析该恶意软件。转储的文件如下图:
该恶意软件运行在进程 dllhost.exe
中,进程 ID 为 8200。转储文件名为 pid.8200.vad.0x1c3eefb0000-0x1c3ef029fff.dmp
。该文件名表明恶意软件被加载运行在内存地址范围 0x1c3eefb0000
到 0x1c3ef029fff
。
转储文件本质上是一个已部署的 64 位 PE文件。在执行期间,Windows 加载器会读取并解析其 DOS 和 PE 头部来加载和部署该 PE 文件。部署完成后,这些头部信息便不再需要。为了防止恶意软件被转储为文件供研究人员分析,一些恶意软件(包括本例)会通过将这些头部区域用零或随机数据覆盖来破坏它们。使用二进制文件分析工具WinHex加载转储文件, 如下图所示,发现DOS 和 PE 头部均已被0填充, 整个PE头被破坏,使得从内存中重建整个可执行文件变得异常困难。
为了动态分析恶意软件, 需要在本地复现被攻陷系统的运行环境。为此,需在调试器中启动 dllhost.exe
进程,作为部署转储恶意软件的目标进程,以便在本地分析环境中分析恶意软件。
要让恶意软件在这种受控环境下正确运行,需要经过多个复杂步骤。
第一步:定位入口点
第一步是定位入口点函数(启动函数),也就是恶意软件被 Windows 加载器加载进内存后最先执行的代码。
虽然入口点的偏移通常记录在 PE 头中,但在本例中并没有。因此只能选择手动定位入口点。根据经验,入口函数的第一条指令通常为 sub rsp, 28h
,但其他函数中也可能包含该指令。这里使用静态反汇编利器 IDA Pro 对恶意软件进行反汇编,并在数据库中搜索所有此类指令。
比较幸运, 该恶意软件中仅出现了八处该指令。通过分析最终确认第4个函数(地址 0x1C3EEFEE0A8
)为入口点。如下图:
第二步: 分配主要内存区域
使用x64位动态调试工具x64dbg加载dllhost.exe, 在新启动的 dllhost.exe
进程中,需要手动执行一些指令,为部署转储的恶意软件分配内存。它调用相关的 VirtualAlloc()
API,并使用与被攻陷系统中相同的基地址:0x1C3EEB70000
。
内存分配完成后,将转储的恶意软件复制到了新创建的内存中。如下图:
第三步: 修复导入表
PE 文件的导入表列出了其依赖的 Windows API。这些 API 的加载地址因系统不同而不同。为了使转储恶意软件在本地系统中成功运行和分析,需要将其引用的 API 地址重定位为本地系统中实际加载的地址。如下图:
上图展示了导入表中部分 Windows API 的地址信息。根据分析,可以从中计算出最终的 API 地址。
例如,地址 0x1C3EF0240D0
处的 API 实际为 0x1C3EEEE1CE0
,它通过执行地址 0x1C3EEEE1CE0h
处的汇编代码计算出最终的 API 地址为 0x7FFD74224630
:
001C3EEEE1CE0 mov r10, 0E528F49552F112B4h 001C3EEEE1CEA mov r11, 0E5288B6826D35484h 001C3EEEE1CF4 xor r11, r10 001C3EEEE1CF7 jmp r11 ; 0x7FFD74224630
dllhost.exe
进程(PID 8200)中加载的模块, 地址 0x7FFD74224630
的 API 来源于模块 GDI32.dll
。如下图:通过从“fullout”文件中提取 GDI32.dll
并分析,最终确认该地址对应的是被攻陷系统中的 GetObjectW()
API。
在本地测试环境中,该 API 地址为 0x07FFFF77CB870
。因此,将恶意软件中原地址替换为本地地址。
该恶意软件一共需要重定位 16 个模块中的 257 个 Windows API。包括:
- kernel32.dll
- ws2_32.dll
- ntdll.dll
- gdi32.dll
- shlwapi.dll
- sspicli.dll
- user32.dll
- shell32.dll
- msvcrt.dll
- advapi32.dll,
- comctl32.dll
- crypt32.dll
- gdiplus.dll
- ole32.dll
- rpcrt4.dll
- userenv.dll
GetObjectW()
相同的方法,将其原始地址替换为本地系统中的对应地址。此外,还需要加载所有未被 dllhost.exe
自动加载但恶意软件依赖的模块。为此,必须调用 LoadLibraryA()
或 LoadLibraryW()
API 并传入模块名,将它们加载到恶意软件的内存中。上图显示了恶意软件在调试器中的运行情况。RIP 寄存器指向入口点地址 0x1C3EEFEE0A8
。调试器底部还显示了一些已重定位的 Windows API 函数。
第四步: 分配更多内存
根据分析,恶意软件还需要使用一段全局变量数据,该数据位于地址 0x1C3EEB7000
,大小为 0x5A000
字节。
使用 Volatility 工具和 dd
命令从 “fullout” 文件中提取了所需的全局数据。随后,再次调用 VirtualAlloc()
API,在 dllhost.exe
进程中分配新的内存区域,并指定期望的地址和大小。分配成功后,将提取出的数据复制到新分配的内存空间中, 如下图:
第五步:修复入口点参数与堆栈
对恶意软件入口点函数的静态分析表明,该函数需要三个参数:
- 第一个参数(RCX):为加载的恶意软件的基地址,本例中为
0x1C3EEFB0000
。
- 第二个参数(RDX):值为
0x1
。
- 第三个参数(R8):为一个指向 0x30 字节缓冲区的指针,该缓冲区同样可以从 “fullout” 文件中提取。
这里相应准备了这三个参数, 并为第三个参数的 30H 数据分配了额外内存, 如下图:
最后一个关键步骤是正确对齐 RSP
寄存器的值。在断点处进入入口点时,RSP
值的最低四位必须为 0x8
,如果对齐不正确,恶意软件启动时将触发 EXCEPTION_ACCESS_VIOLATION
(异常代码 0xC0000005
)。
这是由于指令对齐错误,特别是在执行如 movdqa
等要求 16 字节对齐的指令时。在 64 位代码模式下,调用入口函数前,RSP
初始值为 16 字节对齐(最低四位为 0x0
),随后压入返回地址,RSP
减 8。因此,这里将其从 0x09CAD11D850
调整为 0x09CAD11D848
。
在经历多次尝试、出错和修复后,最终在本地环境中成功运行了恶意软件。
执行后,恶意软件调用函数以解密存储在内存中的 C2 服务器域名信息, 解密函数展示了解密出的域名细节,包括域名(“rushpapers.com”)和端口号(“443”), 如下图:
CreateThread()
API,并指定线程函数地址为 0x1C3EEFDE300
, 如下图:新创建的线程负责与 C2 服务器通信, 线程启动后,主线程进入休眠状态,直到通信线程执行完毕。上图右侧展示了部分线程代码。
恶意软件通过 TLS 协议与 C2 服务器通信。它调用 getaddrinfo()
API,通过 DNS 查询获取域名 “rushpapers.com” 的 IP 地址。使用 Wireshark 捕获的恶意软件与其 C2 服务器通信的网络流量如下图:
由于 TLS 数据包已加密,需要在加密前或解密后分析数据的明文内容。可在恶意软件使用的加解密函数上设置调试断点来实现这一目标。
该恶意软件使用两个 API 函数 SealMessage()
和 DecryptMessage()
分别对 TLS 流量数据进行加密和解密。
如上图所示, 恶意软件正准备使用 SealMessage()
加密一个 HTTP GET 请求。
以下是两个明文数据包的示例——一个发送,一个接收:
- Request packet (before TLS encryption):
GET /ws/ HTTP/1.1
Host: rushpapers[.]com
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: OCnq155rYct3ykkkdLrjvQ==
- Response packet (after TLS decryption):
HTTP/1.1 101 Switching Protocols
Server: nginx/1.18.0
Date: Fri, 28 Mar 2025 06:13:24 GMT
Connection: upgrade
Upgrade: websocket
Sec-WebSocket-Accept: Bzr0K1o6RJ4bYvvm4AM5AAG172Y=
这两个明文数据包用于完成类似握手的过程。随后,恶意软件切换到自定义加密算法,对数据进行加密,然后再通过 TLS 发送。
以下是使用自定义算法加密后的数据示例:
根据可变扩展长度规则,数据长度计算方式为:0xA6 - 0x80 = 0x26
。
加密密钥(如 0x755C9816
)为随机生成的数值。该自定义加密算法通过对加密数据的每个字节与密钥的每个字节进行重复异或(XOR)操作实现。
解密后的数据如下:
当 C2 服务器请求时,恶意软件会收集该信息并发送回 C2。
以下是用于加解密数据的自定义算法代码片段:
通过对其 API 调用及执行流程的全面分析,可以确认该恶意软件为一个远程访问木马(RAT,Remote Access Trojan)。本节将详细介绍该恶意软件控制被感染系统的功能。
(1)截屏功能
该恶意软件具备截取受害者屏幕并以 JPEG 图像形式发送至其 C2 服务器的功能。它还会收集当前活动(最上层)窗口的标题,以提供用户在截图时的操作上下文。
为实现该功能,它依次调用了一系列 API,包括:CreateStreamOnHGlobal()
、GdiplusStartup()
、GetSystemMetrics()
、CreateCompatibleDC()
、CreateCompatibleBitmap()
、BitBlt()
、GdipCreateBitmapFromHBITMAP()
、GdipSaveImageToStream()
和 GdipDisposeImage()
。
下图展示了恶意软件如何调用上述 API 实现屏幕截图功能:
(2).充当服务器
该恶意软件包含一个线程函数,其设计用于充当服务器,监听由 C2 服务器指定的 TCP 端口。一旦激活,该函数即可使恶意软件等待攻击者的传入连接。
它实现了多线程的 socket 架构:每当有新的客户端(攻击者)连接时,恶意软件就创建一个新线程处理通信。这种设计支持并发会话,并能执行更复杂的交互操作。
通过这种方式,恶意软件实际上将被感染系统变成了远程访问平台,使攻击者能够发起进一步攻击或执行各种远程操作。
(3).控制系统服务
该恶意软件能够枚举并操作受感染主机上的系统服务。它通过调用多个 Windows 服务控制管理器(SCM)API 实现此功能,包括:OpenSCManagerW()
、EnumServicesStatusExW()
、ControlService()
等。
URL: hxxps://rushpapers.com/ws/
Sha256:
F3EB67B8DDAC2732BB8DCC07C0B7BC307F618A0A684520A04CFC817D8D0947B9
原文始发于微信公众号(二进制空间安全):没有本体!弄残自己躲避查杀的远程木马
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论