跳转到帖子

逆向木马远控实战:从 DLL 侧载链定位 C2 配置与持久化留痕

精选回复

发布于

背景与场景

本文记录一次授权终端排查中的木马远控分析过程。告警主机出现周期性外联,但代理日志只显示一个带有效签名的 Update.exe 访问未知域名。进一步检查发现,该程序位于可写目录 C:\ProgramData\WinUpdate,而不是原厂安装目录。

样本目录包含 Update.exe、version.dll 和 cache.dat。Update.exe 是正常签名程序,version.dll 无签名,计划任务 WinUpdateCheck 在用户登录时启动 Update.exe。最终确认这是典型的 DLL 侧载链:攻击者利用正常程序的 DLL 搜索顺序加载恶意 version.dll,再由 DLL 解密配置、建立 C2 通信并向其他进程注入。

以下操作应在已授权环境中完成。动态调试必须使用快照虚拟机、Host-only 网络和模拟服务,不能让样本直接访问生产网络或互联网。

问题定位:先确认执行链,不急于删除文件

遇到签名程序外联时,不能只根据主程序签名判断是否可信。应同时检查程序路径、同目录 DLL、父进程、启动来源和模块加载记录。本例需要确认四个问题:

  • Update.exe 是否从异常目录运行,以及由谁启动。
  • version.dll 是否被 Update.exe 实际加载,而不是无关残留文件。
  • 计划任务、服务或注册表是否承担持久化。
  • 外联发生在 Update.exe 内部,还是发生在被注入的 dllhost.exe 等进程中。

处置前先隔离网络并保全证据。对于仍在运行的主机,优先记录进程树、网络连接、登录会话和内存状态。直接结束进程或删除计划任务,可能导致内存中的解密配置、注入代码和短生命周期连接丢失。

样本保全与静态初筛

在终端上只做不会执行样本的读取操作。所有时间统一记录为 UTC,并分别计算 SHA-256。除文件本体外,还要保留任务 XML、文件流、ACL 和目录中的配套文件。

$root = 'C:\ProgramData\WinUpdate'

Get-ChildItem -LiteralPath $root -Force |
  Select-Object FullName, Length, CreationTimeUtc, LastWriteTimeUtc

Get-ChildItem -LiteralPath $root -File -Force |
  Get-FileHash -Algorithm SHA256

Get-AuthenticodeSignature "$root\Update.exe" | Format-List Status,SignerCertificate,TimeStamperCertificate
Get-AuthenticodeSignature "$root\version.dll" | Format-List Status,SignerCertificate
Get-Item -LiteralPath "$root\version.dll" -Stream *
icacls.exe $root

schtasks.exe /Query /TN WinUpdateCheck /V /FO LIST
schtasks.exe /Query /TN WinUpdateCheck /XML

如果 Update.exe 的签名有效,只能说明该文件自签名后未被修改,不能证明它加载的 DLL 可信。侧载场景中尤其要核对签名文件的原始产品路径、版本信息和同目录依赖。正常厂商程序被复制到 ProgramData、Public 或用户临时目录,本身就是高价值线索。

PE 结构与导入表分析

本例 version.dll 导出了 Update.exe 需要的版本查询函数,但其导入表很小,只静态导入 LoadLibraryW、GetProcAddress 和少量基础 API。此类结构通常意味着关键 API 在运行时解析。进一步检查发现资源节熵值偏高,并存在 PE overlay,说明配置或下一阶段载荷可能附加在文件尾部。

下面的 pefile 脚本可以快速列出节区、导入项和 overlay 偏移。它不会执行目标文件,适合初筛可疑 DLL:

import sys
import pefile

path = sys.argv[1]
pe = pefile.PE(path, fast_load=False)

print('[sections]')
for section in pe.sections:
    name = section.Name.rstrip(b'\0').decode(errors='replace')
    print(name,
          'RVA=0x%x' % section.VirtualAddress,
          'RAW=0x%x' % section.SizeOfRawData,
          'ENTROPY=%.2f' % section.get_entropy())

print('[imports]')
if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
    for entry in pe.DIRECTORY_ENTRY_IMPORT:
        print(entry.dll.decode(errors='replace'))
        for item in entry.imports:
            name = item.name.decode(errors='replace') if item.name else 'ordinal:%s' % item.ordinal
            print('  ', name)

overlay = pe.get_overlay_data_start_offset()
print('[overlay]', hex(overlay) if overlay is not None else 'none')
print('[imphash]', pe.get_imphash())

节区熵高不能单独作为恶意结论。安装包、压缩组件和商业保护壳也会出现高熵。应把高熵资源、动态 API 解析、异常执行路径和网络行为结合起来判断。

动态逆向:确认解密点与 C2 行为

动态环境使用还原点虚拟机,关闭共享剪贴板、共享目录和宿主机凭据,网络设置为 Host-only。通过 FakeNet-NG 或内部模拟 DNS/HTTP 服务接收请求,不把未知样本直接放行到互联网。

Procmon 可先设置以下过滤条件:

  • Process Name is Update.exe 或 dllhost.exe。
  • Operation is Load Image、CreateFile、RegSetValue、Process Create。
  • Path contains C:\ProgramData\WinUpdate。

在 x64dbg 中重点观察 LoadLibraryExW、GetProcAddress、CryptDecrypt、BCryptDecrypt、WinHttpOpenRequest、WinHttpSendRequest、VirtualAllocEx、WriteProcessMemory 和 CreateRemoteThread。目标不是逐条跟踪全部指令,而是抓住三个边界:配置解密完成、网络请求构造完成、跨进程内存写入完成。

本例在 BCryptDecrypt 返回后,输出缓冲区出现以下字段:

  • 模拟 C2:sync-api.example.invalid,解析地址使用文档保留网段 203.0.113.27。
  • 请求路径:/api/v2/ping。
  • 心跳间隔:60 秒,并附加约 5 秒随机抖动。
  • 互斥体名称前缀:Global_WUCache_。
  • 宿主进程目标:dllhost.exe。

如果通信使用 TLS,不必依赖中间人解密。可以在 WinHttpSendRequest 调用前检查请求句柄及明文缓冲区,也可以结合 SNI、DNS、目标证书、连接周期和进程归属完成归因。对于自定义加密协议,优先在加密前、解密后设置断点,比直接分析网络密文更有效。

内存侧验证注入行为

当 Sysmon Event ID 10 或 EDR 显示 Update.exe 对 dllhost.exe 申请高权限句柄时,应保留内存。使用可信采集工具获取镜像后,可通过 Volatility 3 检查进程树、网络连接、模块列表和可执行私有内存:

python vol.py -f host.raw windows.pstree
python vol.py -f host.raw windows.netscan
python vol.py -f host.raw windows.dlllist --pid 4120
python vol.py -f host.raw windows.malfind --pid 4120
python vol.py -f host.raw windows.vadinfo --pid 4120

重点关注没有文件映射、同时具备写入和执行权限的 VAD,以及起始位置包含 MZ 头但不在正常模块列表中的区域。malfind 结果只是候选项,JIT、浏览器和安全软件也可能产生可执行私有内存,仍需结合调用栈、线程起始地址和文件来源判断。

日志留痕与时间线关联

侧载型远控的关键证据通常分散在多个日志源,建议围绕首次落地时间建立时间线:

  • Sysmon Event ID 1:Update.exe 的父进程、命令行、当前目录和哈希。
  • Sysmon Event ID 3:Update.exe 或 dllhost.exe 的目标地址、端口和发起时间。
  • Sysmon Event ID 7:签名 Update.exe 从可写目录加载无签名 version.dll。该事件需要提前启用 ImageLoad,并按路径或签名状态过滤,否则日志量较大。
  • Sysmon Event ID 10:Update.exe 对 dllhost.exe 的进程访问权限,用于辅助判断注入。
  • Sysmon Event ID 11、13:文件创建和注册表值修改。
  • Sysmon Event ID 22:域名查询,可与网络连接时间对应。
  • Security Event ID 4698、4702:计划任务创建和修改,事件内容中通常包含任务 XML。
  • TaskScheduler Operational Event ID 106、140、200、201:任务注册、更新、动作启动和结束。
  • Amcache、Prefetch:证明文件曾执行,并辅助确定首次运行时间。Shimcache 更适合作为存在性和路径线索,不应单独证明程序已执行。
$start = (Get-Date).AddDays(-7)

Get-WinEvent -FilterHashtable @{
  LogName='Microsoft-Windows-TaskScheduler/Operational'
  Id=106,140,200,201
  StartTime=$start
} | Where-Object { $_.Message -match 'WinUpdateCheck|ProgramData\\WinUpdate' } |
  Select-Object TimeCreated, Id, Message

Get-WinEvent -FilterHashtable @{
  LogName='Microsoft-Windows-Sysmon/Operational'
  Id=1,3,7,10,11,13,22
  StartTime=$start
} | Where-Object { $_.Message -match 'Update\.exe|version\.dll|sync-api' } |
  Select-Object TimeCreated, Id, Message

如果没有 Sysmon,可从 EDR、DNS 解析日志、代理日志、Windows 防火墙日志和计划任务日志反推。时间线中应特别检查同一哈希是否出现在其他终端,以及相同签名程序是否从相同异常目录启动。

针对样本族的检测规则

仅使用文件哈希容易漏掉重新打包的变种。可以将任务名、URI、互斥体前缀和解密后配置字段组合为样本族规则。以下规则是针对本例的静态线索,部署前必须先在软件仓库中回溯测试:

rule RAT_WinUpdate_Sideload_Family
{
    meta:
        description = "Detects configuration markers from the WinUpdate sideload RAT case"
        scope = "internal incident response"

    strings:
        $s1 = "WinUpdateCheck" wide ascii
        $s2 = "/api/v2/ping" ascii
        $s3 = "Global_WUCache_" wide ascii
        $s4 = "sync-api" ascii nocase
        $s5 = "cache.dat" wide ascii

    condition:
        uint16(0) == 0x5a4d and
        filesize < 3MB and
        3 of ($s*)
}

如果这些字符串只在运行时解密,磁盘 YARA 不一定命中,可将规则用于进程转储或内存扫描。生产检测还应增加行为条件,例如“签名程序从用户可写目录启动并加载同目录无签名 DLL”,其稳定性通常高于单个域名或 IP。

风险判断与修复建议

  • 不要只删除 version.dll。应检查同目录所有文件、计划任务、服务、Run 键、WMI 永久订阅和启动文件夹,并确认是否存在第二套持久化。
  • 若已确认跨进程注入、凭据访问或多阶段载荷,建议重装或使用可信镜像恢复。仅结束进程无法证明驻留能力已清除。
  • 若发现对 lsass.exe、浏览器凭据数据库或令牌文件的访问,应在干净设备上重置相关凭据,撤销会话令牌和 API 密钥。不要在疑似受控主机上直接改密。
  • 临时封禁域名、IP、证书指纹和文件哈希可以快速止血,但共享云 IP 和短期域名变化较快,不能作为唯一长期策略。
  • 通过 WDAC 或 AppLocker 限制 ProgramData、Public、用户下载目录中的可执行文件和 DLL。检查目录 ACL,避免普通用户对服务程序目录同时拥有写入和执行权限。
  • 对允许执行的签名程序建立路径基线。合法二进制被复制到异常目录运行时,即使签名有效也应触发告警。
  • 开启计划任务审计、PowerShell Script Block Logging,并为 Sysmon 的 Process Create、Network Connect、Image Load、Process Access、DNS Query 设置经过裁剪的规则。
  • 将样本提交公共沙箱前先评估数据敏感性。企业样本可能包含内网域名、租户标识、用户名或解密后的访问令牌。

清理后的验证

清理完成后至少重启一次,再验证计划任务不存在、异常目录未恢复、Update.exe 不再从该路径启动、version.dll 未被其他进程加载,并持续观察原 C2 周期的数倍时间。对于本例 60 秒心跳,短暂观察几分钟不足以排除带退避机制的重连,应结合 EDR 和 DNS 日志持续监控 24 至 72 小时。

总结

这类远控的难点不在于主程序本身,而在于签名文件、恶意 DLL、持久化任务和被注入进程共同组成的执行链。有效分析顺序应是先保全证据,再关联进程与模块加载,随后定位配置解密点和网络调用,最后把静态特征转化为路径、签名状态、模块加载和进程访问等行为检测。只封禁单个哈希或删除单个 DLL,通常不足以完成闭环处置。

逆向分析与样本观察示意图
逆向分析、样本行为与调试流程示意
服务器与云环境安全示意图
服务器、云资产与权限边界梳理示意

这类样本建议把“侧载入口、配置解密、持久化落点”按时间线串起来,别只停在提取一个 C2 地址。标题里没展开样本细节,先给几个值得核对的位置:

  • 侧载入口:用 Procmon 过滤宿主 EXE 的 Load Image 和 CreateFile,对照 DLL 实际加载路径、宿主导入表和恶意 DLL 导出表。重点看宿主从可写目录加载同目录 DLL 的情况;签名正常不等于加载链可信。动态分析放在快照隔离环境,关闭共享目录,禁止直接访问真实 C2。
  • C2 配置:若静态字符串没有结果,可从 WinHttpConnect、InternetConnectW、connect 或解析域名的调用点回溯参数来源。配置可能在资源节、附加数据或外部文件里,先定位“读取→解密→解析→网络调用”的数据流,再提取解密后的缓冲区。不要把下载地址、探测域名直接当成主 C2;记录协议、端口、URI,以及备用地址切换条件更有价值。
  • 持久化留痕:除 Run 键外,核对计划任务的动作、服务的 ImagePath 和工作目录。如果持久化指向的是合法宿主 EXE,真正需要关联的可能是它旁边的恶意 DLL,而不是启动项里直接出现木马路径。

下面几条适合先做只读核查,尽量在清理前保留输出和相关文件哈希:

reg query "HKCU\Software\Microsoft\Windows\CurrentVersion\Run"
reg query "HKLM\Software\Microsoft\Windows\CurrentVersion\Run"
schtasks /query /fo LIST /v

# PowerShell:检查服务启动路径
Get-CimInstance Win32_Service |
  Select-Object Name, StartMode, State, PathName

# 对发现的宿主和 DLL 分别执行
Get-FileHash -Algorithm SHA256 -LiteralPath 'C:\path\suspect.dll'
Get-AuthenticodeSignature -LiteralPath 'C:\path\host.exe'

如果已有 Sysmon 日志,可以关联事件 1(进程创建)、7(模块加载)、3(网络连接)和 13(注册表值设置);其中模块加载和网络事件是否可见取决于采集配置,没记录不能当作没发生。最终整理成“谁启动宿主→加载哪个 DLL→解析出哪份配置→连接哪里”的证据链,后续做同源样本排查会比单独封禁域名稳得多。

创建帐户或登录后发表意见

最近浏览 0

  • 没有会员查看此页面。