Elastic Security Labs 此前对REF6598的报道记录了一组入侵活动:其 Windows 工具链通过滥用 Obsidian 插件植入系统,借助内存中 PE 加载器(PHANTOMPULL)提升权限,最终通过远程访问工具(RAT,即 PHANTOMPULSE)完成攻击。那篇文章主要讨论了交付问题。本文分析了最后一个阶段:PHANTOMPULSE。这是一种植入式恶意代码,支持三种进程注入技术,通过以太坊/Base/Optimism交易输入解析其C2通道,并利用公开的schuac 技术绕过用户账户控制(UAC)。该分析揭示了一个可被“吞噬”的区块链C2通道,一种能够禁用AMSI/WLDP/ETW的统一硬件断点原语,以及植入程序调试字符串中普遍存在的AI辅助开发指纹。
关键要点
- PHANTOMPULSE 实现了三种注入技术,这些技术改编自近期公开的攻击性安全概念验证(PoC)。
- AMSI、WLDP 和 ETW 通过单个共享的 HWBP 原语被绕过
- 该区块链C2解析器不具备发件人验证功能,这使得防御方只需发布一笔交易,即可覆盖每个植入程序的C2网址
- 二进制文件中存在强烈的AI辅助开发迹象
关于人工智能辅助开发的说明
PHANTOMPULSE 处处可见人工智能编码辅助的痕迹,这一点在调试字符串中尤为明显。
最明显的迹象:
- 操作日志中的结构化步骤编号:
[STEP 1] Staged mode — payload downloaded from C2 at runtime,[STEP 1/3] Scheduled Task (DotNetSvcUpdateTask, logon + every 3 min),[STEP 2/3] Boot Task (DotNetSvcCoreTask, INTERACTIVE_TOKEN + BootTrigger),[UNINSTALL 4/6] Removing persist_loader DLL + registry PE data...,[REPAIR] Reinstalling boot task (INTERACTIVE_TOKEN).... - ENTER/DONE 功能追踪:
"[HEIS] encrypt_text_only ENTER"/"[HEIS] encrypt_text_only DONE","KeylogResolveAPIs: ENTER"。大型语言模型在生成新函数时默认采用的诊断风格。 - 详细诊断信息:
"FindHostProcessEx: scan stats: total=%lu sessSkip=%lu openFail=%lu native=%lu wow64=%lu mapReject=%lu dbgReject=%lu sess=%lu","ManualMap: thread hijacked and resumed — DLL injection via thread hijack complete"。输出内容一目了然,对于恶意软件来说,这番话倒是不寻常地多。 - C字符串中的连字符:
"elevate: FAIL — no deployed DLL path",">>> .elevate: NOT proxy — spawning trusted host to handle elevation"。
执行链
MainEntryLogic 这是在进入 C2 循环之前运行完整初始化序列的协调函数:
start
└─ WinMain
└─ MainEntryLogic
1. DynInit // Bootstrap API resolution
2. ElevationStateCheck // ".elevate" marker detection, routes by token elevation state
3. SingleInstanceCheck // XOR-decrypted mutex, exit if already running
4. EvasionInit // Direct syscalls + ETW HWBP
5. SyscallResolverInit // CPUID + hash-based kernel32 resolution
6. SleepMaskInit // Sleep obfuscation setup
7. ComputeMachineID // DJB2(module name) ^ volume serial
8. IsRunningHollowed // Process hollowing self-check
9. CollectSysInfo // CPU, GPU, RAM, OS, AV, apps
10. FilelessPersist // Drop stub DLL, registry artifact
11. InstallPersistence // Three scheduled tasks via COM ITaskService
12. C2Loop_Init → C2Loop_Main
启动时,该植入程序会对用户名和计算机名进行DJB2哈希运算,并分别在预计算表中进行查找。一个匹配项已退出进程。通过使用公开的反沙箱词表对该表进行暴力破解,成功恢复了 61 中 20 条记录:WDAGUtilityAccount (Windows Defender Application Guard)、若干DESKTOP-XXXXXXX 默认虚拟机名称,以及Joe Sandbox 角色(abby 、patex 、george 、john 、lisa 、frank 、RDhJ0CNFevzX )。
防御规避
直接系统调用和 API 封装
PHANTOMPULSE 通过使用 DJB2 哈希遍历 `PEB→Ldr ` 来解析 ntdll 函数,从每个 NT 函数的序言中提取系统服务号(SSN),并构建私有系统调用存根。这些存根被封装在植入程序其余部分通用的更高层级辅助函数中:
NtCreateFile_WrapNtWriteFile_WrapNtClose_WrapNtCreateSection_WrapNtMapViewOfSection_WrapNtProtectVirtualMemory_WrapNtWriteVirtualMemory_Wrap
该插件的其余部分调用这些封装函数,而非kernel32/ntdll 导出的函数,从而规避了EDR产品注入到已文档化API接口中的用户模式ntdll 钩子(IAT替换、内联重定向或跳板补丁)。
一个辅助函数会将每次磁盘写入操作直接路由至NtCreateFile +NtWriteFile ,并在访问错误时执行删除并重试。
字符串和配置混淆
PHANTOMPULSE 采用四个异或(XOR)层来处理不同的伪影:
| 什么 | Key | 密钥的存放位置 |
|---|---|---|
| C2 备用 URL、互斥锁、掉线路径文件名 | 16字节: F7 7C 8E 40 DF C1 7B E5 E7 4D 86 79 D5 B3 53 41 | 嵌入 .rdata |
| 区块链提供商的主机名(UTF-16 LE) | 8字节: 5A 3C 7E 1D 9F 2B 4E 8A | 嵌入 .rdata |
| COM Elevation Moniker,键盘记录文件有效载荷 | 0xE95CA237,在运行时计算,以避免将常量包含在 .rdata | 计算得出,未存储 |
从区块链交易中提取的 C2 URL input | 解析器钱包地址本身 | 从公共查询密钥中复用 |
AMSI、WLDP 和 ETW 通过硬件断点进行绕过
PHANTOMPULSE 通过一个共享的原始操作来禁用 AMSI、Windows 锁定策略的代码信任检查以及 ETW 遥测功能:该操作是在每个 API 入口处设置一个硬件断点,并由一个向量异常处理程序进行拦截,该处理程序在不进行内联修补的情况下伪造返回值。
| 老虎机 | 目标 API | 伪造的返回值 (RAX) |
|---|---|---|
| DR0 | WldpQueryDynamicCodeTrust | 0 (S_OK) |
| DR1 | AmsiScanBuffer | 0x80070057 (E_INVALIDARG) |
| DR2 | EtwEventWrite | 0 (STATUS_SUCCESS) |
该机制的步骤如下:
- 该植入程序解析了目标 API。AMSI 和 WLDP 会遍历
LoadLibraryA并进行基于哈希的导出查找;而 ETW 则采用 PEB→Ldr 遍历,因为 ntdll 已经加载完毕。 - HWBP 描述符(目标 API 地址、模式、伪造的返回值)会被写入全局槽表中的四个 40 字节槽之一。
- 辅助线程会暂停目标线程,调用
NtGetContextThread/NtSetContextThread来写入DR0–DR3 + DR7,然后恢复目标线程。(如果植入程序的向量异常处理程序(VEH)已安装,则会抛出一个进程内的STATUS_BREAKPOINT异常,从而允许VEH在无需辅助线程的情况下读取插槽表并编程DR。) - 调用受保护的 API 时,CPU 会在该函数的第一条指令处触发
Debug Exception异常。 - 该植入程序的向量化异常处理程序会拦截
Debug Exception,遍历其4槽表以查找触发地址,并修改线程上下文:将CONTEXT.Rax设置为每个槽对应的伪造返回值,将CONTEXT.Rip重定向至预先存储的"跳过" 跳转函数,该函数会返回给调用者。 - 该处理程序返回
EXCEPTION_CONTINUE_EXECUTION。调用方看到的伪造的 RAX 值,就好像 API 已经运行过一样。
调度器支持两条路径。一个处理程序(VEH_Dispatcher )既处理植入体自身的RaiseException(STATUS_BREAKPOINT) 调用(用于根据插槽表初始化并重新编程DR寄存器),也处理在调用受保护API时触发的STATUS_SINGLE_STEP 异常。异常代码决定了分支:STATUS_BREAKPOINT 触发 DR 编程,STATUS_SINGLE_STEP 触发伪造。
该处理程序也没有直接注册。AddVectoredExceptionHandler 接收一个在运行时分配的微型JMP thunk,该thunk位于一个全新的MEM_PRIVATE 页面中(VirtualAlloc +VirtualProtect 指向PAGE_EXECUTE_READ )。该 thunk 是一个指向JMP [RIP-relative] 的间接跳转(6 字节指令码FF 25 00 00 00 00 ),其后紧接着是调度器的地址。由于从未向AmsiScanBuffer 、WldpQueryDynamicCodeTrust 或EtwEventWrite 写入任何字节,因此基于签名的检测(即扫描前言补丁)完全无法发现这一情况。
构建变体:活动和休眠子系统
该二进制文件中存在若干子系统,它们以代码或字符串的形式存在,但在本次构建中尚未启用。这是基于一个更大代码库精简而成的版本。
- NTDLL 解挂钩:解挂钩子系统的调试字符串位于
.rdata(UnhookNtdll: ntdll base = %p,applied %d relocation fixups to .text),但没有任何地方引用它们。在此变体中已失效。 - 驻留于注册表的 PE 代码块加载器:早期版本会将下一阶段的 PE 文件存储在注册表中。此版本虽不包含该功能,但卸载程序仍会清理旧版注册表项。
- COM 劫持持久化:此构建版本从未安装过。其清理逻辑仍保留在卸载例程中。
- 打印机监视器的持久化机制:与 COM 劫持的模式相同;安装路径缺失,但卸载路径保留。
最后三项(注册表二进制数据加载器、COM 劫持、打印监视器)则呈现出相反的模式:仅包含清理逻辑而无安装逻辑,此设计是为了向后兼容旧版部署。
| 功能 | 有效载荷构建 | 证据 |
|---|---|---|
| 直接系统调用(SSN提取) | 有效 | 已确认社会安全号码提取及存根生成 |
| AMSI / WLDP / ETW HWBP 绕过 | 有效 | DR0/DR1/DR2 通过共享的辅助线程原语 |
| 三向工艺注入 | 有效 | PhantomInject,DbgNexum,ManualMap 均可正常访问 |
| 区块链 C2 决议 | 有效 | 查询了三家Blockscout服务提供商 |
| NTDLL 解挂钩 | 死代码 | 存在字符串,零代码引用 |
| HEIS 加密 | 已禁用 | 加密/解密代码已实现为存根 |
| 驻留于注册表的 PE 代码块加载器 | 仅限旧版 | 仅在卸载时进行清理 |
| COM 劫持的持久化机制 | 仅限旧版 | 已在卸载过程中清理,从未安装过 |
| 打印监视器的持久性 | 仅限旧版 | 已在卸载过程中清理,从未安装过 |
| 诱饵字符串 | 有效 | 文件中存在 4 个未引用的诱饵字符串 .rdata |
命令和控制
区块链 C2 决议
PHANTOMPULSE 通过三个 Blockscout 提供商实现了 C2 查询的去中心化:
eth.blockscout[.]com(以太坊 L1)base.blockscout[.]com(基准 L2)optimism.blockscout[.]com(乐观主义 L2)
钱包地址0xc117688c530b660e15085bF3A2B664117d8672aA 是使用一个 16 字节的密钥从存储中通过异或解密得到的。对于每个提供商,植入程序会发出一个 HTTPS GET 请求(端口 443,忽略 SSL 证书错误),提取最新交易的input 字段,将其十六进制解码,使用钱包地址的字节作为密钥进行异或解密,并验证结果是否以http 开头。如果完全失败,则会回退到硬编码的 URLhttps://panel.fefea22134[.]net 。
解析器不会验证交易的发送方。它仅检查最新解码的input 是否以http 开头。任何人都可以向该钱包提交一笔交易,将自己的 URL 通过 XOR 编码嵌入钱包字节中,此后对该活动进行轮询的每个 PHANTOMPULSE 实例都会解析为该 URL。对于网络防御人员而言,这是一个只需消耗一次交易成本即可实现的可行陷阱。
端点与心跳检测
运行时会构建五个 API 路径,并在内存中使用会话密钥对其进行重新加密:
| 路径 | 方法 | 内容类型 | 用途 |
|---|---|---|---|
/v1/telemetry/report | 职位 | application/json | Heartbeat 搭配完整的系统遥测功能 |
/v1/telemetry/tasks/<machine_id> | Get | 命令获取 | |
/v1/telemetry/upload/ | 职位 | image/bmp | 屏幕截图 / 文件上传 |
/v1/telemetry/result | 职位 | application/json | 命令结果传送 |
/v1/telemetry/keylog/ | 职位 | text/plain | 上传键盘记录数据 |
心跳会以 JSON 格式发送完整的系统配置文件:
{
"machine_id": "<uint32>",
"status": "online",
"cpu": "<model>",
"gpu": "<description>",
"ram_mb": "<uint32>",
"os": "<version>",
"username": "<user>",
"computer_name": "<name>",
"cores": "<uint32>",
"screen_w": "<int>",
"screen_h": "<int>",
"privilege": "<user|admin|admin_nouac|system>",
"build": "payloads",
"public_ip": "<ip>",
"av_list": ["<av1>", "<av2>"],
"apps": ["<app1>", "<app2>"],
"last_cmd": "<cmd>",
"last_cmd_result": "<result>"
}
解析了两个响应字段:"status":"deleted" 触发完全卸载;"ip":"<value>" 填充公共 IP 缓存,但仅当本地发现(ipif[.]org)/ icanhazip[.]com/checkip.amazonaws[.]com)还没有填满。
循环节奏与韧性
- 睡眠:[20, 40] 秒内的均匀随机值
- 自我修复:在第2次迭代时运行,之后每10次迭代运行一次
- 健康监控检查:植入体上线后首次调用,此后每隔5次迭代调用一次。填充一个本地系统信息结构体(CPU、% 、RAM、操作系统版本、运行时间、计算机名称)。
- 故障阈值: 10 次连续的心跳失败将触发卡住的 SSL/TLS 恢复过程的自动重启
- 重新解析:发生故障时,将执行区块链重新解析;如果解析后的 URL 发生变化,故障计数器将重置
- 公共IP:
api4.ipify[.]org→ipv4.icanhazip[.]com→checkip.amazonaws[.]com - 连接性检查:探针
microsoft[.]com、google[.]com、cloudflare[.]com、github[.]com
指挥调度
命令调度器根据 DJB2 哈希值对命令进行路由。总共八条指令:
| 散列 | 命令 | 行为 |
|---|---|---|
0x04CF1142 | inject | 注入shellcode/DLL/EXE。按类型分类的入侵途径:shellcode →PhantomInject; DLL →ManualMap; EXE →DbgNexum.AMSI 和 WLDP 的 HWBP 绕过机制已安装在首次调用 `inject ` 时(ETW 的 HWBP 已通过 `EvasionInit` 实现)。 |
0x7C95D91A | drop | 将文件拖放到磁盘上并运行。支持 DLL、EXE、shellcode(APC 注入)和 MSI 有效载荷。 |
0x9A37F083 | screenshot | 通过GDI捕获图像,将宽度缩小至960像素,然后以BMP格式上传。 |
0x08DEDEF0 | keylog | 启动或停止内联键盘记录器。 |
0x4EE251FF | uninstall | 6步清理与终止流程。 |
0x65CCC50B | elevate | 通过schuac 技术绕过UAC(IElevatedFactoryServer::ServerCreateElevatedObject(CLSID_TaskScheduler) );注册一个临时的高权限任务,用于重新启动植入程序。 |
0xB3B5B880 | downgrade | 系统 → 提升为管理员权限。 |
0x20CE3BC8 | (自动重启) | 级联式自终止:NtTerminateProcess(-1, 0) 首先调用直接系统调用;如果该调用无法解析,则回退到ExitProcess(0) 。Persistence 将在下一个计划任务周期重新启动该植入体。在操作上相当于软重启。 |
第八个处理程序没有以它命名的调试日志。它会自动终止;定时任务将在下一个时间点重新启动该植入程序。由于缺乏LLM风格的辅助代码(调试字符串),这使得该处理程序成为二进制文件中为数不多的、似乎是由人工添加而非由LLM生成的处理程序之一。
注射技术
PHANTOMPULSE 支持三种注入技术,每种有效载荷类型对应一种。inject 的C2命令会将shellcode转发至PhantomInject ,DLL文件转发至ManualMap ,EXE文件转发至DbgNexum 。
AMSI/WLDP硬件断点绕过机制会在首次调用inject 时安装,即在任何注入器运行之前。
| 有效负载类型 | 喷油器 | 战略 |
|---|---|---|
| Shellcode | PhantomInject | 通过dbghelp.dll 进行模块踩踏SEC_IMAGE |
| EXE | DbgNexum | 调试 API 状态机 |
| DLL | 手动地图 | 完整的 PE 手动映射 |
PhantomInject:将模块注入到 dbghelp.dll 中
模块覆盖通过将一个合法的 Windows DLL 映射为SEC_IMAGE ,并覆盖.text ,从而避免分配MEM_PRIVATE :
-
获取
SeDebugPrivilege(通过OpenProcessToken/LookupPrivilegeValueW/AdjustTokenPrivileges),然后遍历七个候选主机进程之一(不区分大小写)的进程快照。按优先级顺序尝试了以下链接:sihost.exe、taskhostw.exe、backgroundTaskHost.exe、RuntimeBroker.exe、dllhost.exe、ctfmon.exe、explorer.exe。 -
通过
NtOpenFile打开dbghelp.dll,创建SEC_IMAGE部分,并通过NtMapViewOfSection -
解析
.text的本地副本以获取其RVA和大小,然后将其释放 -
选择并挂起一个线程,捕获上下文
-
构建一个82 字节的保存-调用-恢复跳板
-
将shellcode和trampoline写入已映射DLL的
.text中 -
将保护功能切换至
PAGE_EXECUTE_READ -
将 RIP 重新指向蹦床,恢复该帖子
在内存扫描器看来,该结果就像是一个线程正在合法的dbghelp.dll 中执行,这是一个基于文件的图像区域,具有正确的文件路径、段名和首页哈希值。
DbgNexum:作为执行控制器的调试 API
DbgNexum 支持处理 EXE 有效载荷。它并非预先将可执行代码写入目标系统,而是利用 Windows 调试 API 逐个异常地驱动执行:这是一个 ROP 链,其小工具在目标系统中都是完整的 Windows API。
该技术并非由PHANTOMPULSE首创。这是对 dis0rder0x00/DbgNexum,该代码是2026年1月4日发布在GitHub上的公开概念验证。操作者保留了植入程序调试字符串中已公开的技术名称("DbgNexum" )不变,其内部状态机也与之完全一致:诱饵 API、段名称、gadget 链和常量均完全相同。PHANTOMPULSE 在提取的 x64 内核外层包裹了一套概念验证(PoC)所不具备的运行时框架:主机进程选择(FindHostProcessEx ),一种会创建新的SysWOW64\cmd.exe /rundll32.exe /notepad.exe 的备用方案,一个自定义的 PE 加载引导程序(因此它能够加载完整的 EXE 文件而非原始 shellcode), 以及一个独立的 WoW64 跨架构变体。
对于原生 x64 有效载荷,植入程序会将 PE 文件、引导程序存根以及跳板配置预先加载到一个命名的文件映射段中。该节的名称是字面上的两字节字符串"MZ" ,植入程序通过DebugActiveProcess 进行连接,并在FileTimeToSystemTime 上创建一个远程线程,并在DR0上设置了一个硬件断点。
当诱饵触发断点时,状态机将通过此 API 链驱动目标:
- 将
RIP重定向至DbgBreakPoint+1,并设置 trap 标志;由此产生的单步异常将过渡到链的其余部分。 LocalAlloc(LMEM_ZEROINIT, 3),分配 3 字节的名称缓冲区。memcpy(buf, kernel32_base, 2),将kernel32.dll中的 DOS 标头文件"MZ"复制到缓冲区中。memset(stack+40, 0, 8): 将栈参数槽清零。OpenFileMappingA(0x1F, FALSE, "MZ"),以全切片映射权限打开已准备好的切片。MapViewOfFile(...),将其映射到目标中。- 将
RIP重定向至mapped_base + 0x400,即 Bootstrap 的占位页面。这是唯一一个直接记录的阶段:DbgNexumLoop64: stage 6 -> stub at %llx, base=%llx。(该PoC会重定向至mapped_base + 0以获取原始shellcode;PHANTOMPULSE 会添加+0x400偏移量,从而跳转至其自定义的PE加载器。)
每次过渡都会拦截下一个调试事件,恢复RSP ,清除中断标志,修改RIP 以及下一个调用的参数寄存器(RCX 、RDX 、R8 、R9 ),然后继续被调试程序的执行。DR0会被重复用作每个保存的返回地址上的硬件执行断点,因此无需对目标进行内联修补或WriteProcessMemory 操作。从内核的角度来看,发生的一切只是kernel32.dll 中的一个线程调用了LocalAlloc 、OpenFileMappingA 和MapViewOfFile 。
跨架构路径(从64位植入体发出的PE32)是PHANTOMPULSE独有的变体,公开的PoC中并不包含该功能。它采取了一条捷径:植入程序通过NtReadVirtualMemory 遍历目标的PEB.Ldr (使用ProcessWow64Information 来选择 32 位或 64 位 PEB 布局),以查找具有可调用入口点的 DLL,然后通过NtCreateSection +NtMapViewOfSection 将包含 32 位加载器存根和跳板程序的段预映射到两个进程中。重定向仅分两个阶段:首先在RtlExitUserThread 设置诱饵,随后通过DbgBreakPoint 进行单步跳转,将RIP 引导至跳板。此路径上没有 API 链,因为该段已在两端完成映射,因此无需OpenFileMappingA/MapViewOfFile 。
跨架构主机选择通过FindHostProcessEx 进行,该过程会排除关键系统进程(csrss.exe 、lsass.exe 、smss.exe 、winlogon.exe 、services.exe 、wininit.exe 、svchost.exe 、MsMpEng.exe ),若未找到可用的 WoW64 主机,则会回退到创建新的SysWOW64\cmd.exe /rundll32.exe /notepad.exe 。
ManualMap:完整的 PE 映射工具
ManualMap 通过完整的 PE 手动映射实现来处理 DLL 有效载荷:
-
验证 MZ/PE 签名;拒绝在 x64 主机路径中运行 PE32(调试日志:
"PE32 DLL in x64 host is impossible") -
通过以下方式在目标中分配 `
SizeOfImage`:NtAllocateVirtualMemory -
将标题和章节复制到本地暂存缓冲区
-
应用基地址偏移(
IMAGE_REL_BASED_DIR64,IMAGE_REL_BASED_HIGHLOW) -
通过
LoadLibraryA解决导入问题 +GetProcAddress -
清空 PE 头部(
SizeOfHeaders字节的零值) -
将暂存映像写入远程分配区
-
设置按节的内存保护
-
在单独的 0x2000 字节远程分配区(设置为
PAGE_EXECUTE_READ)中构建一个137 字节的跳板:
完整的 trampoline shellcode Gist包含完整的字节序列。
10. 通过 suspend / get-context / set-context 劫持线程
权限提升
elevate 命令是一种利用schuac 技术(IElevatedFactoryServer::ServerCreateElevatedObject(CLSID_TaskScheduler) )绕过UAC的漏洞,由zcgonvh作为UACME第129号漏洞发布,当前编号为74。
机制
MaintenanceUI.dllCMaintenanceUIVirtualFactory (CLSID{A6BFEA43-501F-456F-A845-983D3AD7B8F0} )已通过注册表键Elevation 进行注册,因此操作系统会向非管理员调用者提供一个具有提升权限的实例。其IElevatedFactoryServer 接口提供了ServerCreateElevatedObject(rclsid, riid, ppv) 方法,具有提升权限的服务器可通过该方法在其提升的上下文中实例化任何其他CLSID。PHANTOMPULSE 向其提供CLSID_TaskScheduler ,返回一个提升权限的ITaskService ,并利用该服务注册一个HighestAvailable-RunLevel 任务,该任务将重新启动植入程序。
elevate 的C2命令
在 `ProcessCommands` 文件中,elevate 处理程序:
- 构建任务操作。该命令为
<system_dir>\rundll32.exe,参数为\"<deployed_dll>\",DllRegisterServer。用户标识符为COMPUTERNAME\USERNAME,来自GetEnvironmentVariableW。 ![Elevate 命令调用][/assets/images/blockchain-c2-phantompulse-rat-sinkhole/image17.png] - 将
.elevate标记写入为单个字节("1",0x31),而非编码参数。该写入操作会经过NtCreateFile+NtWriteFile,以绕过用户模式钩子。该标记仅作为存在标记;海拔参数则包含在植入体即将注册的任务定义中。 - 在运行时对 COM 权限提升 Moniker 进行异或解密,即从
.rdata获取的 66 字节数据,与种子0xE95CA237进行异或运算,解码后得到Elevation:Administrator!new:{A6BFEA43-501F-456F-A845-983D3AD7B8F0}。 - 调用
CoGetObject(moniker, &BIND_OPTS3{dwClassContext=CLSCTX_LOCAL_SERVER}, IID_IElevatedFactoryServer, &factory)以获取具有提升权限的IElevatedFactoryServer*,然后调用factory->ServerCreateElevatedObject(CLSID_TaskScheduler, IID_ITaskService, &elevatedTaskService)以获取继承了该提升权限的ITaskService*。 - 使用上述 rundll32 操作,在
HighestAvailableRunLevel 处注册一个临时任务DotNetSvcElevateTask。 - 删除任何现有的非提升权限的持久化任务,以确保旧的低IL持久化操作不会与提升权限的重新启动操作发生竞争。
- 在临时任务上调用 `
ITaskService::Run`,并在随后立即将其删除。释放单实例互斥锁,并退出中等复杂度IL植入程序。
通过代理 rundll32 进行备用重试
如果CoGetObject /RegisterTask 链接出现故障,将由备用路径接管。另一条独立的启动路径会通过 `CreateProcessW` 直接生成一个新的 `rundll32.exe "<deployed_dll>",DllRegisterServer `,并支持重试(`">>> .elevate redirect attempt %d"`)。在该 rundll32 进程内部,植入程序会检测isProxy=1 、elevated=0 ,并使用三种注册变体(ELEVATED+INTERACTIVE+user 、ELEVATED+INTERACTIVE 、INTERACTIVE )重试 SCHUAC 序列。操作成功后,具有更高权限的任务将启动,并由具有更高权限的重启进程接管。当资源耗尽时,代理会记录">>> Phase 1: all registration methods failed, cleaning marker" 并退出。
以管理员身份重新启动 rundll32
当临时任务触发时,svchost.exe (Schedule) 会使用一个新的高IL令牌启动rundll32.exe "<deployed_dll>",DllRegisterServer 。该植入程序的DllRegisterServer 输出作为入口运行;启动时,它检测到.elevate 标记和一个高权限IL令牌,并转至提升权限的路径:
- 读取并删除
.elevate标记。 - 在提升权限的环境下重新安装持久化配置,包括位于
\Microsoft\Windows\NetFramework\下的启动任务DotNetSvcCoreTask,该任务通过INTERACTIVE_TOKEN+BootTrigger进行注册,注册时需要管理员权限。 - 作为高IL服务,继续正常运行。
标记状态路由
每次启动时,MainEntryLogic 都会在.elevate 路径上调用GetFileAttributesW 。如果返回值为INVALID_FILE_ATTRIBUTES ,则植入体将跳过所有海拔计算逻辑,并正常启动。如果该标记存在,则植入程序会收集另外两项信息:当前进程是否为rundll32.exe/regsvr32.exe 的代理,以及该令牌是否具有提升权限,然后根据这两项信息的组合进行路由:
| 标记 | 代理 | 升高的 | 会发生什么 |
|---|---|---|---|
| 缺席 | 不适用 | 不适用 | 正常启动,无特殊路由 |
| 现在 | 否 | 否 | 启动 rundll32 代理以重试 schuac 链,然后退出 |
| 现在 | 是 | 否 | 在 rundll32 内部重试 schuac;若尝试失败,则删除标记并退出 |
| 现在 | * | 是 | 删除标记,通过启动任务重新安装持久化数据,并以高IL模式继续 |
标记文件的内容永远不会被读取,仅凭其存在即可驱动路由。
Elastic Security Labs 的《探索 Windows UAC 绕过技术》一文直接介绍了针对“IElevatedFactoryServer ”类绕过技术的检测模式。
持久化
三个计划任务
PHANTOMPULSE 通过 COM 接口 `ITaskService ` 安装三个计划任务,每个任务都会执行 `rundll32.exe "<stub_dll>",DllRegisterServer`:
| 任务 | 触发 | 时间间隔 | 运行级别 |
|---|---|---|---|
DotNetSvcUpdateTask | 用户登录 + 时间 | 3 分钟 | 标准 |
DotNetSvcCoreTask | 启动 + 时间 | 15 分钟 | 最高可用值 + 隐藏 |
DotNetSvcUserTask | 用户登录 | 登录时 | 标准 |
该启动任务位于\Microsoft\Windows\NetFramework\ 目录下,以便与合法的.NET维护任务相融合。其完整的 XML 主体已嵌入.rdata 中,并非在运行时生成。此字面量上的签名在不同构建之间是持久的。
安装程序会针对每个任务尝试多种RegisterTask 备用方案,将INTERACTIVE_TOKEN 和S4U 登录类型与不同的用户标识符格式(COMPUTER\User 、SID 、短用户名)进行组合。尝试的具体顺序取决于植入程序的权限上下文(SYSTEM、管理员或标准用户);若某次尝试失败,则会依次尝试下一个变体,直到其中一次成功。
DLL 文件放置
已部署的代理程序以加密和压缩的形式存储在该EXE文件自身的.rdata 文件中。FilelessPersist 按需对其进行解密,并通过 (NtCreateFile +NtWriteFile) 将结果写入磁盘。
解密分为两个阶段:
- 对一个数据块进行异或解码,使用一个旋转的16字节密钥(详见
decoded[i] = blob[i] ^ key[i & 0xF],其中decoded[0] = 0x1E作为硬编码的首字节,用于初始化LZNT1分块头)。 RtlDecompressBuffer(COMPRESSION_FORMAT_LZNT1, ...)在解码后的缓冲区上,这是一个 DLL(svcagent.dll)。
按优先级顺序列出路径,若访问出错则依次尝试:
%ProgramData%\AssetMon\svcagent.dll(小学)%APPDATA%\AssetMon\svcagent.dll%TEMP%\svcagent.dll- 一个冗余的
%ProgramData%" 休眠文件" 位于另一个路径下的副本
分析人员可以通过从EXE文件中读取上述两个区域,应用XOR循环,并将结果输入RtlDecompressBuffer (或任何LZNT1实现),从而在脱机状态下重现已部署的DLL,如下面的CyberChef截图所示。
DLL 旁加载迁移
SetupRegistryPE 文件中的一个代码块(通过MigrateSideload /MigrateLegacySideloads 调试字符串前缀记录)会枚举正在运行的进程及其可执行文件目录,以查找diagcore.dll 。若找到该文件,则会通过 `CopyFileW` 命令用当前的占位文件覆盖它。
自我修复
自愈功能在 C2 循环的第 2 次迭代中运行,此后每隔 10 次迭代运行一次,且需满足延迟持久化标志为清零的条件。检查顺序:
- 首先检查注册表持久性。
CheckRegistryPersistence位于街区顶端。如果检测结果显示状态异常,该植入程序会立即重新运行FilelessPersist(重新解密并重新放置存根 DLL)以及InstallPersistence(重新注册任务触发器)。 - 任务验证。
SelfHealCheckTasks然后验证三个持久化任务(DotNetSvcUpdateTask、DotNetSvcCoreTask、DotNetSvcUserTask),并重新安装任何缺失的任务。启动任务检查受限于 SYSTEM 或 admin 上下文;无特权的调用者将跳过此检查。 - 更新音视频设备清单。
DetectInstalledAV在最后运行以刷新操作员可见的AV产品列表。
特权门控对驱逐程序至关重要。在非提升的环境中,会跳过启动任务检查,因此清理过程中不会检查启动任务。要完全卸载,必须在管理员权限下,从一个窗口中移除所有三个任务以及注册表项。
除了基于迭代的自愈功能外,该植入体还具备一种基于单一标志位的延迟持久化机制。在C2Loop_Main 中的心跳成功路径中,一旦心跳成功计数器在标志位设置的情况下超过1,植入体将重新执行FilelessPersist +InstallPersistence ,并清除该标志位。这为 PHANTOMPULSE 提供了一条第二条持久性修复路径,其触发条件与基于迭代的自我修复不同。
收集
带剪贴板监控功能的内联键盘记录器
该键盘记录器在C2循环中内联运行,不使用专用线程。它在运行时解析来自user32.dll 的 API:
| API | 用途 |
|---|---|
GetAsyncKeyState | 关键州的民调 |
GetForegroundWindow | 活动窗口检测 |
GetWindowTextA | 窗口标题捕获 |
MapVirtualKeyA / ToUnicode | 关键翻译 |
GetClipboardSequenceNumber | 剪贴板更改检测 |
OpenClipboard / GetClipboardData | 剪贴板读取 (CF_UNICODETEXT) |
日志文件使用0xE95CA237 种子进行了异或(XOR)加密。上传时仅发送增量数据,以避免重复传输。
屏幕截图
截图使用通过哈希解析的 GDI API。如果桌面宽度超过 960 像素,图片将在上传前被缩小。原始 BMP 文件在内存中构建,并通过 `Content-Type: image/bmp` 上传。由screenshot 的C2命令(哈希值0x9A37F083 )按需触发。
系统侦察
植入体收集的侦察数据:
| 数据 | 源 |
|---|---|
| cpu | 注册表: ProcessorNameString |
| GPU | 注册表显示适配器DriverDesc (过滤器"Microsoft Basic" ) |
| RAM | GlobalMemoryStatusEx |
| OS | RtlGetVersion 包含构建版本映射(Windows 7 至 Windows 11,Server 2008 至 Server 2025) |
| 用户名 | GetUserNameW 当explorer.exe 令牌不可用时,将回退至LookupAccountSidW |
| 权限 | 令牌提升类型:user 、admin 、admin_nouac 、system |
| AV | DetectInstalledAV 将正在运行的进程与一个包含约25至30个杀毒软件厂商进程名称的硬编码列表进行比对 |
| 应用程序 | DetectInstalledApps 检查一份精心挑选的、包含19个应用的推荐列表 |
| 防火墙状态 | 读取SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\{Domain,Standard,Public}Profile 以记录每个配置文件的启用状态 |
| 服务 | 通过服务枚举统计运行中的服务数量 |
| 机器ID | DJB2(模块名称) ^ 批量序列号 |
| 公共IP | 多API HTTPS链 |
该杀毒软件检测列表范围异常广泛,涵盖了Defender、Norton、McAfee、Avast、AVG、Avira、Bitdefender、ESET、F-Secure、G Data、卡巴斯基、Panda、Sophos、趋势科技、VIPRE等主流西方消费级杀毒产品, Webroot、ZoneAlarm、Comodo,以及EDR供应商(CrowdStrike、SentinelOne、Cylance、Malwarebytes、HitmanPro)均包含在内。该植入程序还会检测AhnLab V3(韩国)、奇虎 360 / 360 Total Security、腾讯QQPC(中国)以及K7 Computing(印度)。在针对西方市场的通用型窃取工具中,加入亚洲AV组件实属罕见,这与专为多个地区市场的受害者设计的植入程序相符。
该植入程序还会根据名称检查一份经过筛选的 19 高价值应用程序列表,并在心跳信号中标记匹配项(App detection: found %d apps ):
| 类别 | 目标 |
|---|---|
| 加密货币钱包 | ledger, trezor, bitcoin-core, electrum, exodus, atomic, guarda |
| 信使 | telegram, discord, signal, viber, slack, whatsapp |
| 邮件客户端 | thunderbird, outlook |
| 双因素认证应用 | authy |
| 文件传输 / SSH | filezilla, winscp |
| 游戏 | steam |
检测函数(DetectInstalledApps )不会扫描注册表或枚举进程。它会展开三个环境变量根路径(%LOCALAPPDATA% 、%APPDATA% 、%ProgramFiles(x86)% ),并为每个应用拼接一个硬编码的 UTF-16 相对路径后缀(例如\Telegram Desktop\,\Authy Desktop\,\Ledger Live\,\@trezor\trezor-suite\,\Steam\steam.exe), 并针对每条路径调用GetFileAttributesW 。如果返回结果未报错,则表示应用已安装,且其名称已被记录在心跳结果缓冲区中。
PHANTOMPULSE 本身不会从这些数据中提取任何信息。该清单旨在为后续任务执行进行目标侦察。攻击者通过“心跳”功能查看特定受害者拥有哪些高价值应用程序,并决定接下来通过inject 或drop 推送何种专用有效载荷。
在分析的样本中未发现任何钱包、浏览器、即时通讯工具或凭证窃取功能;该目标清单纯粹是用于检测设备存在情况,并据此为操作者的决策树提供依据。
卸载
由uninstall 命令、"status":"deleted" 在heartbeat响应中,或注册表中的kill标志触发的6步清理流程:
| 步骤 | 操作 |
|---|---|
| 1/6 | 将终止标志写入 HKCU 和 HKLM,终止主机进程 |
| 2/6 | 通过 COM+CreateProcessW 备用方案删除所有 3 计划任务 |
| 3/6 | 删除旧版注册表项:NTLoad 值、COM 劫持键、打印监视器键 |
| 4/6 | 删除临时 DLL 文件、休眠日志、注册表中的 PE 数据块以及 ProgramData 目录 |
| 5/6 | 从磁盘中删除安装路径和程序路径 |
| 6/6 | 终止残留的healthmon.exe 以及任何托管中的rundll32.exe 实例svcagent.dll |
步骤 3 揭示了遗留的持久化技术:针对COM劫持和打印监视器的清理逻辑,而此构建版本从未安装过这些组件。
归属
PHANTOMPULSE 的技术手法、攻击目标及基础设施选择,与朝鲜关联的加密货币攻击入侵集团(包括Lazarus、BlueNoroff、UNC5342(Contagious Interview)和APT38)一致。多个独立维度与近期关于这些聚集区的公开报道相符。
与朝鲜报道相符的迹象:
- 通过交易
input字段解析出的区块链C2地址,与Mandiant在《朝鲜采用EtherHiding》报告中归因于UNC5342(Contagious Interview)的“死信解析器”模式相符。PHANTOMPULSE 的具体特征(钱包字节异或、多链 Blockscout)并非完全匹配的指纹,但该技术类别现已被标记为 DPRK。 - 桌面加密钱包枚举集合(
ledger、trezor、bitcoin-core、electrum、exodus、atomic、guarda)与Unit 42发现的针对macOS的RustDoor/Koi Stealer目标列表高度吻合,该攻击被归因于朝鲜。 - 针对同一受害者档案的跨平台 Windows + macOS 植入程序(此前 REF6598 帖子记录了一个 macOS 变种,其 C2 服务器位于
0x666[.]info,Telegram 备用服务器位于t[.]me/ax03bot)是 BlueNoroff 的典型特征。 - 根据Arctic Wolf对BlueNoroff的报道 ,Telegram和Messenger定向投放 正是BlueNoroff的专长。
通过解析器钱包的已知明文签名搜寻新的 C2 域名
区块链解析器所采用的异或(XOR)方案会泄露一个固定的2字节签名,攻击者可以利用该签名对整个区块链进行攻击,而不仅仅是一个钱包。
有两个事实需要结合考虑:每个 C2 URL 都以ht 开头(源自http:// 或https:// ),且 XOR 密钥就是钱包的 ASCII 地址的字面值,因此其前两个密钥字节总是字符串0 和x 。将ht 与0x 进行异或运算,结果为\x58 \x0c 。由任何链上的PHANTOMPULSE风格解析器生成、并由任何相关钱包签名的每个加密input 字段,均以四个十六进制字符580c 开头。
这使得追踪工作不再局限于监控某个钱包,而是扩展到在整个区块链上搜索该签名。可以通过 BigQuery、Dune 或全节点查询以太坊主网、Base 和 Optimism 的交易数据。针对以太坊公开交易数据集,以0x580c 开头的input 值进行查询,并将范围限定在最近的区块时间戳窗口内,结果揭示了同一代码库所使用的此前未知的解析器钱包。每次匹配都会通过以发送方钱包的ASCII地址作为密钥进行解码来验证:真实的C2网址在解码后以http 开头。以下CyberChef 配方可用于解密 C2 URL。
SELECT
block_timestamp AS block_time,
from_address AS `from`,
to_address AS `to`,
input AS data
FROM `bigquery-public-data.crypto_ethereum.transactions`
WHERE block_timestamp >= '2026-04-01 00:00:00'
AND input LIKE '0x580c%'
ORDER BY block_timestamp DESC
LIMIT 10000;
CyberChef 可以对输入数据进行解密,从而揭示域名,如下图所示。
结论
PHANTOMPULSE 由已公开的组件构建而成,包括:模块踩踏、调试 API 状态机、手动映射、硬件断点 AMSI/WLDP/ETW 绕过、定时任务持久化以及区块链 C2。这种组合及其支撑的稳定性,表明这是一个正在积极开发中的成熟代码库。持久化信号属于行为类信号,受 Elastic针对 REF6598 的行为保护机制的保护。
PHANTOMPULSE 和 MITRE ATT&CK
Elastic 使用 MITRE ATT&CK 框架来记录高级持续性威胁针对企业网络使用的常见策略、技术和程序。
战术
战术阐释了技术或子技术的“为什么”。这是对手的战术目标:采取行动的原因。
技术
技术代表对手如何通过采取行动来实现战术目标。
- 网络钓鱼:通过服务进行鱼叉式网络钓鱼
- 命令和脚本解释器:PowerShell
- 进程注入
- 进程注入:DLL 注入
- 系统二进制代理执行:Msiexec
- 系统二进制代理执行:Rundll32
- 计划任务/作业:计划任务
- 启动或登录自动启动执行
- 修改注册表
- 削弱防御能力:禁用或修改工具
- 指标移除:文件删除
- 系统信息发现
- 发现系统所有者/用户
- 流程发现
- 软件发现:安全软件发现
- 输入捕获:键盘记录
- 剪贴板数据
- 屏幕截图
- 通过 C2 通道进行泄漏
- 应用层协议:网络协议
- 网络服务
- 加密通道
- 混淆文件或信息
- 对文件或信息进行反混淆/解码
- 访问令牌操控
- 滥用提升控制机制:绕过用户帐户控制
- 原生 API
- 虚拟化/沙盒逃避:基于时间的逃避
- 劫持执行流程:DLL 侧加载
- 反射式代码加载
修复
雅拉
Elastic Security 已创建 YARA 规则来识别此活动。
观察结果
| 可观测 | 类型 | 名称 | 参考 |
|---|---|---|---|
33dacf9f854f636216e5062ca252df8e5bed652efd78b86512f5b868b11ee70f | SHA-256 | 幻影脉冲鼠 | Final payload |
70bbb38b70fd836d66e8166ec27be9aa8535b3876596fc80c45e3de4ce327980 | SHA-256 | syncobs.exe | PHANTOMPULL 装载机 |
def66275fa3baffb16e6e4ae0297861d9790ae7161fbc271a2ba05d121f13c70 | SHA-256 | Go Beacon | GTESTIC_WIN 签入 |
panel.fefea22134[.]net | 域 | C2面板 | PHANTOMPULSE 硬编码备用方案 |
fea22134[.]net | 域 | C2 domain | 以二进制形式加密 |
195.3.222[.]251 | IPv4 地址 | 预发布服务器 | PowerShell/加载程序分发 |
0xc117688c530b660e15085bF3A2B664117d8672aA | 加密钱包 | Blockchain C2 钱包 | ETH/Base/Optimism |
0x38796B8479fDAE0A72e5E7e326c87a637D0Cbc0E | 加密钱包 | 资金钱包 | C2决议的资金 |
eth.blockscout[.]com | 域 | 区块链服务提供商 | C2 URL 解析 |
base.blockscout[.]com | 域 | 区块链服务提供商 | C2 URL 解析 |
optimism.blockscout[.]com | 域 | 区块链服务提供商 | C2 URL 解析 |
hVNBUORXNiFLhYYh | mutex | 单实例 | 经异或解密 |
svcagent.dll | 文件名 | 占位 DLL | 持久化有效载荷 |
AssetMon | 目录 | DLL 存根目录 | %ProgramData% 或%APPDATA% |
healthmon.exe | 文件名 | 滴管 | 原始可执行文件名称 |
diagcore.dll | 文件名 | 旧版 DLL 旁加载 | 由 MigrateSideload 迁移 |
.elevate | 文件名 | 高程标记 | Routes 重启高架项目 |
DotNetSvcUpdateTask | 计划任务 | 原发性持续性 | 3分钟间隔 |
DotNetSvcCoreTask | 计划任务 | 系统持久化 | 15分钟,隐藏 |
DotNetSvcUserTask | 计划任务 | 用户数据持久化 | 登录触发器 |
EdgeWebViewUpdateTask | 计划任务 | 遗留任务 | 卸载时进行清理 |
\Microsoft\Windows\NetFramework\DotNetSvcCoreTask | 任务-URI | 启动任务路径 | 隐藏的计划任务 |
Elevation:Administrator!new:{A6BFEA43-501F-456F-A845-983D3AD7B8F0} | com-名称 | 绕过用户账户控制 (UAC) | 提升权限的 ITaskService |
0x666[.]info | 域 | macOS C2 | macOS 释放器 |
t[.]me/ax03bot | URL | Telegram 备用方案 | macOS C2 死信箱 |
thoroughly-publisher-troy-clara[.]trycloudflare[.]com | 域 | 之前的 C2 | Cloudflare Tunnel |
参考资料
本分析中引用的先前报告和工具包: